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)
#3 (generic function with 1 method)
λ(1)
1.0
typeof(λ)
var"#3#4"

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[29]:1 within `ff1`
define nonnull ptr @julia_ff1_9215(i64 signext %"x::Int64") #0 {
top:
  %jlcallframe1 = alloca [2 x ptr], align 8
  %gcframe2 = alloca [4 x ptr], align 16
  call void @llvm.memset.p0.i64(ptr align 16 %gcframe2, i8 0, i64 32, i1 true)
  %pgcstack = call ptr inttoptr (i64 140703173070805 to ptr)(i64 261) #9
  store i64 8, ptr %gcframe2, align 16
  %task.gcstack = load ptr, ptr %pgcstack, align 8
  %frame.prev = getelementptr inbounds ptr, ptr %gcframe2, i64 1
  store ptr %task.gcstack, ptr %frame.prev, align 8
  store ptr %gcframe2, ptr %pgcstack, align 8
;  @ In[29]:2 within `ff1`
  %a.checked = load atomic ptr, ptr @"*Main.a#9217.jit" unordered, align 16
  %.not = icmp eq ptr %a.checked, null
  br i1 %.not, label %err, label %ok

err:                                              ; preds = %top
  call void @ijl_undefined_var_error(ptr nonnull @"jl_sym#a#9218.jit", ptr nonnull @"jl_global#9219.jit")
  unreachable

ok:                                               ; preds = %top
  %gc_slot_addr_1 = getelementptr inbounds ptr, ptr %gcframe2, i64 3
  store ptr %a.checked, ptr %gc_slot_addr_1, align 8
  %box_Int64 = call nonnull align 8 dereferenceable(8) ptr @ijl_box_int64(i64 signext %"x::Int64") #3
  %gc_slot_addr_0 = getelementptr inbounds ptr, ptr %gcframe2, i64 2
  store ptr %box_Int64, ptr %gc_slot_addr_0, align 16
  store ptr %box_Int64, ptr %jlcallframe1, align 8
  %0 = getelementptr inbounds ptr, ptr %jlcallframe1, i64 1
  store ptr %a.checked, ptr %0, align 8
  %1 = call nonnull ptr @ijl_apply_generic(ptr nonnull @"jl_global#9221.jit", ptr nonnull %jlcallframe1, i32 2)
  %frame.prev6 = load ptr, ptr %frame.prev, align 8
  store ptr %frame.prev6, ptr %pgcstack, align 8
  ret ptr %1
}

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[30]:2 within `ff2`
define i64 @julia_ff2_9327(i64 signext %"x::Int64") #0 {
top:
;  @ In[30]:3 within `ff2`
; ┌ @ int.jl:87 within `+`
   %0 = add i64 %"x::Int64", 1
   ret i64 %0
; └
}
@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)
The function `^` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  ^(::Regex, ::Integer)
   @ Base regex.jl:895
  ^(::Missing, ::Integer)
   @ Base missing.jl:155
  ^(::Missing, ::Number)
   @ Base missing.jl:123
  ...


Stacktrace:
 [1] literal_pow
   @ ./intfuncs.jl:389 [inlined]
 [2] f(x::UnitRange{Int64})
   @ Main ./In[32]:1
 [3] top-level scope
   @ In[32]: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
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