Les fonctions#
Il est possible de définir une fonction de trois façons différentes :
Définition en ligne#
f(x) = 3x + 1
f (generic function with 1 method)
f(2)
7
g(x,y)=[x*y+1, x-y]
g (generic function with 1 method)
g(1,2)
2-element Vector{Int64}:
3
-1
Il est possible d’utiliser begin-end ou ( ; ) pour délimiter la fonction la dernière valeur calculée est retournée
h(x) = begin
y = 2
x + y
end
h (generic function with 1 method)
h(3)
5
h(x) = (y=2; x+y) # equivalent à la première écriture
h (generic function with 1 method)
h(3)
5
Structurée#
Julia possède une structure plus classique à l’aide de function-end comme précédemment la dernière valeur calculée est par défaut la variable de retour autrement l’utilisation de return spécifie la ou les variables de sortie.
function h(x,y)
z = x+y
return z^2/(abs(x)+1)
end
h (generic function with 2 methods)
h(1,2)
4.5
L’usage de return pour fixer la sortie
function choix(x)
if x>0
return "Positif"
else
return "Négatif"
end
end
choix (generic function with 1 method)
txt=choix(3)
"Positif"
choix(x) = x > 0 ? "Positif" : "Négatif"
choix (generic function with 1 method)
Anonyme#
Le concept peut paraître abstrait mais on peut définir une fonction sans la nommer puis l’affecter à une variable…
x -> x^2
#1 (generic function with 1 method)
λ = x -> sqrt(x)
#5 (generic function with 1 method)
λ(1)
1.0
typeof(λ)
var"#5#6"
Arguments de sortie#
Pour avoir plusieurs arguments de sortie il faut utiliser un “tuple” autrement dit une collection d’éléments
function multi_output(x,y)
return x+y, x-y
end
multi_output (generic function with 1 method)
a = multi_output(1,2) # un seul argument de sortie qui contient un tuple
(3, -1)
typeof(a)
Tuple{Int64, Int64}
a[1]
3
a[2]
-1
a, b = multi_output(1,2) # assignation aux 2 arguments de sortie
(3, -1)
a
3
b
-1
Portée des variables#
Quelle est la portée des variables, autrement dit une variable définie peut elle être accessible, modifiable dans une fonction sans la passer en paramètre ?
a = 1
f(x) = a + x
f(1)
2
Attention, lorsque l’on utilise des variables globales à l’intérieur d’une fonction. La compilation est plus difficile car
la fonction doit prendre en compte les modifications éventuelles de la variable a
function ff1(x)
x + a
end
@code_llvm ff1(1)
; @ In[30]:1 within `ff1`
define nonnull {}* @julia_ff1_1363(i64 signext %0) #0 {
top:
%1 = alloca [2 x {}*], align 8
%gcframe2 = alloca [4 x {}*], align 16
%gcframe2.sub = getelementptr inbounds [4 x {}*], [4 x {}*]* %gcframe2, i64 0, i64 0
%.sub = getelementptr inbounds [2 x {}*], [2 x {}*]* %1, i64 0, i64 0
%2 = bitcast [4 x {}*]* %gcframe2 to i8*
call void @llvm.memset.p0i8.i64(i8* align 16 %2, i8 0, i64 32, i1 true)
%thread_ptr = call i8* asm "movq %fs:0, $0", "=r"() #6
%tls_ppgcstack = getelementptr i8, i8* %thread_ptr, i64 -8
%3 = bitcast i8* %tls_ppgcstack to {}****
%tls_pgcstack = load {}***, {}**** %3, align 8
; @ In[30]:2 within `ff1`
%4 = bitcast [4 x {}*]* %gcframe2 to i64*
store i64 8, i64* %4, align 16
%5 = getelementptr inbounds [4 x {}*], [4 x {}*]* %gcframe2, i64 0, i64 1
%6 = bitcast {}** %5 to {}***
%7 = load {}**, {}*** %tls_pgcstack, align 8
store {}** %7, {}*** %6, align 8
%8 = bitcast {}*** %tls_pgcstack to {}***
store {}** %gcframe2.sub, {}*** %8, align 8
%a = load atomic {}*, {}** inttoptr (i64 140122390545264 to {}**) unordered, align 16
%9 = getelementptr inbounds [4 x {}*], [4 x {}*]* %gcframe2, i64 0, i64 2
store {}* %a, {}** %9, align 16
%10 = call nonnull {}* @ijl_box_int64(i64 signext %0)
%11 = getelementptr inbounds [4 x {}*], [4 x {}*]* %gcframe2, i64 0, i64 3
store {}* %10, {}** %11, align 8
store {}* %10, {}** %.sub, align 8
%12 = getelementptr inbounds [2 x {}*], [2 x {}*]* %1, i64 0, i64 1
store {}* %a, {}** %12, align 8
%13 = call nonnull {}* @ijl_apply_generic({}* inttoptr (i64 140122214190464 to {}*), {}** nonnull %.sub, i32 2)
%14 = load {}*, {}** %5, align 8
%15 = bitcast {}*** %tls_pgcstack to {}**
store {}* %14, {}** %15, align 8
ret {}* %13
}
Lorsqu’une variable est globale il est conseillé de lui donner un caractère constant. Le compilateur vous remerciera dans ce cas.
const α = 1
function ff2(x)
x + α
end
@code_llvm ff2(1)
; @ In[31]:2 within `ff2`
define i64 @julia_ff2_1388(i64 signext %0) #0 {
top:
; @ In[31]:3 within `ff2`
; ┌ @ int.jl:87 within `+`
%1 = add i64 %0, 1
; └
ret i64 %1
}
@code_lowered ff2(1)
CodeInfo(
1 ─ %1 = x + Main.α
└── return %1
)
Donc par défaut une variable définie est connue et utilisable par toute fonction appelée (de même à l’intérieur d’une fonction).
Si on redéfinit localement dans la fonction la variable alors “elle écrase localement” la dite variable et en sortie de fonction rien n’est modifié.
Attention à l’utilisation dans la fonction d’une variable extérieure puis d’affecter une valeur à cette variable…
Le mapping#
Souvent on écrit une fonction sous une forme scalaire sans chercher à utiliser les opérations vectorielles pour appliquer cette fonction à un tableau
f(x) = x^2 + 1
f(1:5) # il aurait fallu définir f(x)=x.^2.+1
MethodError: no method matching ^(::UnitRange{Int64}, ::Int64)
Closest candidates are:
^(::Float16, ::Integer)
@ Base math.jl:1283
^(::Regex, ::Integer)
@ Base regex.jl:863
^(::Float32, ::Integer)
@ Base math.jl:1277
...
Stacktrace:
[1] literal_pow
@ ./intfuncs.jl:351 [inlined]
[2] f(x::UnitRange{Int64})
@ Main ./In[33]:1
[3] top-level scope
@ In[33]:2
La fonction map
permet de palier à ce manquement
map(f,1:5)
5-element Vector{Int64}:
2
5
10
17
26
f.(1:5)
5-element Vector{Int64}:
2
5
10
17
26
v = [1 2; 3 4]
map(f,v)
2×2 Matrix{Int64}:
2 5
10 17
reduce(+, v )
10
sum(v)
10
f.([1 2; 3 4])
2×2 Matrix{Int64}:
2 5
10 17
g(x,y)=x+y
map(g,0:3,1:4)
4-element Vector{Int64}:
1
3
5
7
map(g,[1 2;3 4],[2 3;4 5])
2×2 Matrix{Int64}:
3 5
7 9
g(f,x)=f(x)+1
g (generic function with 1 method)
g(sin,1)
1.8414709848078965
Il existe également un opérateur pour “composer” les fonctions \circ
f1(x) = 2x + 1
f2(x) = 1 - 4x
(f1 ∘ f2)(3)
-21
f1(f2(3))
-21