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