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(Real)
Real
├─ AbstractFloat
│ ├─ BigFloat
│ ├─ BFloat16
│ ├─ Float16
│ ├─ Float32
│ └─ Float64
├─ AbstractIrrational
│ └─ Irrational
├─ Integer
│ ├─ Bool
│ ├─ Signed
│ │ ├─ BigInt
│ │ ├─ Int128
│ │ ├─ Int16
│ │ ├─ Int32
│ │ ├─ Int64
│ │ └─ Int8
│ └─ Unsigned
│ ├─ UInt128
│ ├─ UInt16
│ ├─ UInt32
│ ├─ UInt64
│ └─ UInt8
└─ Rational
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[5]:1
- f(x) in Main at In[4]:1
f(3)
3
f(1im)
1.2984575814159773 + 0.6349639147847361im
f(-2)
-2
+
+ (generic function with 191 methods)
Les opérateurs sont aussi des fonctions
*(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 OneDimensionalGrid
start::Float64
stop::Float64
length::Int32
end
@show a = OneDimensionalGrid(0, 1, 5)
a = OneDimensionalGrid(0, 1, 5) = OneDimensionalGrid(0.0, 1.0, 5)
OneDimensionalGrid(0.0, 1.0, 5)
a.start
0.0
a.stop
1.0
a.length
5
try
a.length = 10
catch e
showerror(stdout, e)
end
setfield!: immutable struct of type OneDimensionalGrid cannot be changed
a
OneDimensionalGrid(0.0, 1.0, 5)
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::OneDimensionalGrid)
print(io, "Grid 1D : start $(g.start) , end $(g.stop) , $(g.length) points\n")
end
Base.show(a)
Grid 1D : start 0.0 , end 1.0 , 5 points
println(a)
Grid 1D : start 0.0 , end 1.0 , 5 points
a
Grid 1D : start 0.0 , end 1.0 , 5 points
@show a
a = Grid 1D : start 0.0 , end 1.0 , 5 points
Grid 1D : start 0.0 , end 1.0 , 5 points
Addition, soustraction …#
Ces fonctions sont de la forme +(), -() c’est à dire
import Base:+
function +(g::OneDimensionalGrid, n::Int)
return OneDimensionalGrid(g.start, g.stop, g.length + n)
end
+ (generic function with 192 methods)
a = OneDimensionalGrid(0,1,2)
Grid 1D : start 0.0 , end 1.0 , 2 points
a + 2
Grid 1D : start 0.0 , end 1.0 , 4 points
a += 1
Grid 1D : start 0.0 , end 1.0 , 3 points
Attention l’addition n’est pas forcément commutative !
try
2 + a
catch e
showerror(stdout, e)
end
MethodError: no method matching +(::Int64, ::OneDimensionalGrid)
The function `+` exists, but no method is defined for this combination of argument types.
Closest candidates are:
+(::Any, ::Any, ::Any, ::Any...)
@ Base operators.jl:642
+(::Real, ::Complex{Bool})
@ Base complex.jl:322
+(::Real, ::Complex)
@ Base complex.jl:334
...
ni unaire !
try
-a
catch e
showerror(stdout, e)
end
MethodError: no method matching -(::OneDimensionalGrid)
The function `-` exists, but no method is defined for this combination of argument types.
Closest candidates are:
-(::Pkg.Resolve.FieldValue, ::Pkg.Resolve.FieldValue)
@ Pkg /Applications/Julia-1.12.app/Contents/Resources/julia/share/julia/stdlib/v1.12/Pkg/src/Resolve/fieldvalues.jl:42
-(::Bool, ::Complex{Bool})
@ Base complex.jl:310
-(::Bool, ::Bool)
@ Base bool.jl:169
...
Notez le message d’erreur qui est très claire !
try
a+[1,2]
catch e
showerror(stdout, e)
end
MethodError: no method matching +(::OneDimensionalGrid, ::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:642
+(::OneDimensionalGrid, ::Int64)
@ Main In[27]:3
+(::Array, ::Array...)
@ Base arraymath.jl:12
...
Autres surcharges#
Toutes les fonctions usuelles sont surchargeable sans limite : size(); det() …
function Base.length(g::OneDimensionalGrid)
return g.length
end
length(a)
3
using LinearAlgebra
function LinearAlgebra.det(g::OneDimensionalGrid)
g.stop-g.start
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
start::Float64
stop::Float64
length::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 "Grid length must be positive"
new(a,b,c)
end
end
try
b = Grid1D(0, 1, -1)
catch e
showerror(stdout, e)
end
AssertionError: Grid length must be positive
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.start) = begin
state > g.stop ? nothing : (state, state+(g.stop-g.start)/g.length)
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…