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
#2 (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)
t = (4,5,6)
(4, 5, 6)
typeof(t)
Tuple{Int64, Int64, Int64}
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)
; Function Signature: ff1(Int64)
; @ In[35]:1 within `ff1`
define nonnull ptr @julia_ff1_4341(i64 signext %"x::Int64") #0 {
top:
%jlcallframe1 = alloca [2 x ptr], align 8
; @ In[35]:2 within `ff1`
%0 = call ptr @jl_get_binding_value_seqcst(ptr nonnull @"*Main.a#4343.jit")
%.not = icmp eq ptr %0, null
br i1 %.not, label %err, label %ok
err: ; preds = %top
call void @ijl_undefined_var_error(ptr nonnull @"jl_sym#a#4344.jit", ptr nonnull @"jl_global#4345.jit")
unreachable
ok: ; preds = %top
%box_Int64 = call nonnull align 8 dereferenceable(8) ptr @ijl_box_int64(i64 signext %"x::Int64") #8
store ptr %box_Int64, ptr %jlcallframe1, align 8
%1 = getelementptr inbounds ptr, ptr %jlcallframe1, i64 1
store ptr %0, ptr %1, align 8
%2 = call nonnull ptr @ijl_apply_generic(ptr nonnull @"jl_global#4346.jit", ptr nonnull %jlcallframe1, i32 2)
ret ptr %2
}
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)
; Function Signature: ff2(Int64)
; @ In[36]:2 within `ff2`
define i64 @julia_ff2_4405(i64 signext %"x::Int64") #0 {
top:
; @ In[36]:3 within `ff2`
; ┌ @ int.jl:87 within `+`
%0 = add i64 %"x::Int64", 1
ret i64 %0
; └
}
@code_lowered ff2(1)
CodeInfo(
1 ─ %1 = Main.:+
│ %2 = Main.α
│ %3 = dynamic (%1)(x, %2)
└── return %3
)
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
try
f(1:5) # il aurait fallu définir f(x)=x.^2.+1
catch e
display(e)
end
MethodError(^, (1:5, 2), 0x000000000000971a)
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
function f4!(y, x; a = 1, b = 2)
z = x + a * b
y .= z
end
@show z = collect(1:5)
f4!(view(z, 1:2), 7)
z = collect(1:5) = [1, 2, 3, 4, 5]
2-element view(::Vector{Int64}, 1:2) with eltype Int64:
9
9