Type et méthodes#
Julia possède un système de type et de méthode qui lui confère une approche objet.
La fonction typeof()
renvoie le type d’une variable de base Int32
, Float64
… Julia est conçu pour permettre facilement d’étendre l’environnement à de nouveau type de variable.
Le types sont organisés suivant un hiérarchie comme on peut le voir sur l’arborescence partielle ci-dessous
using AbstractTrees
AbstractTrees.children(x::Type) = subtypes(x)
print_tree(AbstractArray)
AbstractArray
├─ AbstractRange
│ ├─ LinRange
│ ├─ OrdinalRange
│ │ ├─ AbstractUnitRange
│ │ │ ├─ IdentityUnitRange
│ │ │ ├─ OneTo
│ │ │ ├─ Slice
│ │ │ └─ UnitRange
│ │ └─ StepRange
│ └─ StepRangeLen
├─ AbstractSlices
│ └─ Slices
├─ ExceptionStack
├─ LogRange
├─ LogicalIndex
├─ MethodList
├─ ReinterpretArray
├─ ReshapedArray
├─ SCartesianIndices2
├─ WithoutMissingVector
├─ BitArray
├─ CartesianIndices
├─ AbstractRange
├─ LinRange
│ ├─ OrdinalRange
│ │ ├─ AbstractUnitRange
│ │ │ ├─ IdentityUnitRange
│ │ │ ├─ OneTo
│ │ │ ├─ Slice
│ │ │ ├─ StmtRange
│ │ │ └─ UnitRange
│ │ └─ StepRange
│ └─ StepRangeLen
├─ BitArray
├─ ExceptionStack
├─ LinearIndices
├─ LogRange
├─ MethodList
├─ TwoPhaseDefUseMap
├─ TwoPhaseVectorView
├─ DenseArray
│ ├─ Array
│ ├─ CodeUnits
│ ├─ Const
│ ├─ GenericMemory
│ └─ UnsafeView
├─ AbstractTriangular
│ ├─ LowerTriangular
│ ├─ UnitLowerTriangular
│ ├─ UnitUpperTriangular
│ └─ UpperTriangular
├─ Adjoint
├─ Bidiagonal
├─ Diagonal
├─ Hermitian
├─ SymTridiagonal
├─ Symmetric
├─ Transpose
├─ Tridiagonal
├─ UpperHessenberg
├─ LinearIndices
├─ PermutedDimsArray
├─ SubArray
└─ Message
Dans cette arborescence, certains types sont “abstraits” et d’autres “concrets”.
isconcretetype(Rational{Int32}), isconcretetype(Float64)
(true, true)
Un réel sera forcemment de type concret Float64
ou Float32
par exemple, et pourra être utilisé comme argument par toutes les fonctions acceptant le type abstrait AbstractFloat
Int32 <: Complex
false
Méthodes#
A chaque fonction est associée une méthode dépendante du type d’entrée comme dans ce qui suit suivant que l’entrée soit un entier ou pas.
function f(x::Any)
sin(x+1)
end
f (generic function with 1 method)
function f(n::Integer)
n
end
f (generic function with 2 methods)
f(3.0)
-0.7568024953079282
f(3)
3
methods(f)
- f(n::Integer) in Main at In[7]:1
- f(x) in Main at In[6]:1
f(3)
3
f(im)
1.2984575814159773 + 0.6349639147847361im
f(-2)
-2
+
+ (generic function with 198 methods)
*(3,2)
6
f(sqrt(2))
0.6649143126867011
Construction d’un nouveau Type de variable#
En premier lieu il faut définir un type abstrait puis une instance sous-hiérarchiquement concrète :
struct Grid1
debut::Float64
fin::Float64
n::Int32
end
@show a = Grid1(0, 1, 2)
a = Grid1(0, 1, 2) = Grid1(0.0, 1.0, 2)
Grid1(0.0, 1.0, 2)
a.debut
0.0
a.fin
1.0
a.n
2
a.n = 10
setfield!: immutable struct of type Grid1 cannot be changed
Stacktrace:
[1] setproperty!(x::Grid1, f::Symbol, v::Int64)
@ Base ./Base.jl:53
[2] top-level scope
@ In[22]:1
a
Grid1(0.0, 1.0, 2)
Surcharge des opérateurs#
La surcharge des opérations usuelles se fait en définissant une nouvelle méthode associé au nouveau type pour chaque opérateur, commençons par surcharger l’affichage à l’écran de notre nouveau type. Pour cela on va ajouter une méthode à la fonction “show”
function Base.show(io::IO,g::Grid1)
print(io, "Grid 1d : début $(g.debut) , fin $(g.fin) , $(g.n) éléments\n")
end
Base.show(a)
Grid 1d : début 0.0 , fin 1.0 , 2 éléments
println(a)
Grid 1d : début 0.0 , fin 1.0 , 2 éléments
a
Grid 1d : début 0.0 , fin 1.0 , 2 éléments
@show a
a = Grid 1d : début 0.0 , fin 1.0 , 2 éléments
Grid 1d : début 0.0 , fin 1.0 , 2 éléments
Addition, soustraction …#
Ces fonctions sont de la forme +(), -() c’est à dire
import Base:+
function +(g::Grid1, n::Int)
g.n += n
return g
end
+ (generic function with 199 methods)
a = Grid1(0,1,2)
Grid 1d : début 0.0 , fin 1.0 , 2 éléments
a+2
setfield!: immutable struct of type Grid1 cannot be changed
Stacktrace:
[1] setproperty!(x::Grid1, f::Symbol, v::Int64)
@ Base ./Base.jl:53
[2] +(g::Grid1, n::Int64)
@ Main ./In[29]:4
[3] top-level scope
@ In[31]:1
a += 1
setfield!: immutable struct of type Grid1 cannot be changed
Stacktrace:
[1] setproperty!(x::Grid1, f::Symbol, v::Int64)
@ Base ./Base.jl:53
[2] +(g::Grid1, n::Int64)
@ Main ./In[29]:4
[3] top-level scope
@ In[32]:1
Attention l’addition n’est pas forcément commutative !
try
2 + a
catch e
showerror(stdout, e)
end
MethodError: no method matching +(::Int64, ::Grid1)
The function `+` exists, but no method is defined for this combination of argument types.
Closest candidates are:
+(::Any, ::Any, ::Any, ::Any...)
@ Base operators.jl:596
+(::Real, ::Complex{Bool})
@ Base complex.jl:322
+(::Integer, ::AbstractChar)
@ Base char.jl:247
...
ni unaire !
try
-a
catch e
showerror(stdout, e)
end
MethodError: no method matching -(::Grid1)
The function `-` exists, but no method is defined for this combination of argument types.
Closest candidates are:
-(::Bool, ::Complex{Bool})
@ Base complex.jl:310
-(::Bool, ::Bool)
@ Base bool.jl:167
-(::Bool)
@ Base bool.jl:164
...
Notez le message d’erreur qui est très claire !
try
a+[1,2]
catch e
showerror(stdout, e)
end
MethodError: no method matching +(::Grid1, ::Vector{Int64})
The function `+` exists, but no method is defined for this combination of argument types.
Closest candidates are:
+(::Any, ::Any, ::Any, ::Any...)
@ Base operators.jl:596
+(::Grid1, ::Int64)
@ Main In[29]:3
+(::Array, ::Array...)
@ Base arraymath.jl:12
...
Autres surcharges#
Toutes les fonctions usuelles sont surchargeable sans limite : size(); det() …
function Base.size(g::AbstractGrid)
return g.n
end
UndefVarError: `AbstractGrid` not defined in `Main`
Suggestion: check for spelling errors or missing imports.
Stacktrace:
[1] top-level scope
@ In[36]:1
size(a)
MethodError: no method matching size(::Grid1)
The function `size` exists, but no method is defined for this combination of argument types.
Closest candidates are:
size(::ZMQ.Message)
@ ZMQ ~/.julia/packages/ZMQ/Lz9O7/src/message.jl:164
size(::Base.MethodList)
@ Base reflection.jl:1193
size(::Base.ExceptionStack)
@ Base errorshow.jl:1066
...
Stacktrace:
[1] top-level scope
@ In[37]:1
using LinearAlgebra
function LinearAlgebra.det(g::Grid1)
g.fin-g.debut
end
det(a)
1.0
Type et constructeurs#
Chaque langage “objet” définit un constructeur pour ces objets. Nous avons déjà utilisé un constructeur générique qui rempli chaque champ du nouveau type. Il est possible de faire une variante suivant le nombre d’arguments d’entrée et de leur type
abstract type AbstractGrid end # juste en dessous de Any
struct Grid1D <: AbstractGrid
debut::Float64
fin::Float64
n::Int32
# constructeurs par défaut sans argument
function Grid1D()
new(0,0,0)
end
# constructeurs par défaut avec argument
function Grid1D(a,b,c)
@assert c > 0 "Attention la taille de la grille est négative ou nulle"
new(a,b,c)
end
end
b = Grid1D(0, 1, -1)
AssertionError: Attention la taille de la grille est négative ou nulle
Stacktrace:
[1] Grid1D(a::Int64, b::Int64, c::Int64)
@ Main ./In[40]:15
[2] top-level scope
@ In[41]:1
Il devient possible de déterminer un constructeurs pour différentes entrées.
Il faut au préalable bien penser sa hiérarchie de type et écrire autant de fonctions constructeurs que de cas d’initialisation du nouveau type.
Les Itérateurs#
Il est possible sur un type nouveau de définir un itérateur, comme ici de parcourir les points de la grille, définissons (surchargeons) de nouvelles fonctions ou plutôt méthodes :
Base.iterate(g::Grid1D, state=g.debut) = begin
state > g.fin ? nothing : (state, state+(g.fin-g.debut)/g.n)
end
grid = Grid1D(0, 2 , 10)
Grid1D(0.0, 2.0, 10)
for x in grid
println(x)
end
0.0
0.2
0.4
0.6000000000000001
0.8
1.0
1.2
1.4
1.5999999999999999
1.7999999999999998
1.9999999999999998
Il devient possible de construire des itérateurs sur une grille 2d, 3d renvoyant les coordonnées des points de la grille… Mais on peut imaginer sur une triangulation etc…