Julia Language
Les fonctions
Recherche…
Syntaxe
- f (n) = ...
- fonction f (n) ... fin
- n :: Type
- x -> ...
- f (n) do ... fin
Remarques
Outre les fonctions génériques (les plus courantes), il existe également des fonctions intégrées. Ces fonctions comprennent is
, isa
, typeof
, throw
, et des fonctions similaires. Les fonctions intégrées sont généralement implémentées dans C au lieu de Julia, elles ne peuvent donc pas être spécialisées sur les types d'argument pour dispatch.
Carré un numéro
C'est la syntaxe la plus simple pour définir une fonction:
square(n) = n * n
Pour appeler une fonction, utilisez des parenthèses (sans espaces entre):
julia> square(10)
100
Les fonctions sont des objets dans Julia, et on peut les afficher dans la REPL comme avec tout autre objet:
julia> square
square (generic function with 1 method)
Toutes les fonctions de Julia sont génériques (autrement dit polymorphes ) par défaut. Notre fonction square
fonctionne aussi bien avec des valeurs à virgule flottante:
julia> square(2.5)
6.25
... ou même des matrices :
julia> square([2 4
2 1])
2×2 Array{Int64,2}:
12 12
6 9
Fonctions récursives
Récursion simple
En utilisant la récursivité et l' opérateur conditionnel ternaire , nous pouvons créer une implémentation alternative de la fonction factorial
intégrée:
myfactorial(n) = n == 0 ? 1 : n * myfactorial(n - 1)
Usage:
julia> myfactorial(10)
3628800
Travailler avec des arbres
Les fonctions récursives sont souvent les plus utiles sur les structures de données, en particulier les structures de données arborescentes. Comme les expressions dans Julia sont des structures arborescentes, la récursivité peut être très utile pour la métaprogrammation . Par exemple, la fonction ci-dessous regroupe un ensemble de toutes les têtes utilisées dans une expression.
heads(ex::Expr) = reduce(∪, Set((ex.head,)), (heads(a) for a in ex.args))
heads(::Any) = Set{Symbol}()
Nous pouvons vérifier que notre fonction fonctionne comme prévu:
julia> heads(:(7 + 4x > 1 > A[0]))
Set(Symbol[:comparison,:ref,:call])
Cette fonction est compacte et utilise une variété de techniques plus avancées, telles que la fonction de reduce
l'ordre supérieur , le type de données Set
et les expressions du générateur.
Introduction à la répartition
Nous pouvons utiliser la syntaxe ::
pour envoyer le type d'un argument.
describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"
Usage:
julia> describe(10)
"integer 10"
julia> describe(1.0)
"floating point 1.0"
Contrairement à de nombreux langages, qui fournissent généralement soit une distribution multiple statique, soit une distribution unique dynamique, Julia dispose d'une répartition multiple dynamique. C'est-à-dire que les fonctions peuvent être spécialisées pour plusieurs arguments. Cela s'avère pratique lorsque vous définissez des méthodes spécialisées pour des opérations sur certains types et des méthodes de secours pour d'autres types.
describe(n::Integer, m::Integer) = "integers n=$n and m=$m"
describe(n, m::Integer) = "only m=$m is an integer"
describe(n::Integer, m) = "only n=$n is an integer"
Usage:
julia> describe(10, 'x')
"only n=10 is an integer"
julia> describe('x', 10)
"only m=10 is an integer"
julia> describe(10, 10)
"integers n=10 and m=10"
Arguments optionnels
Julia permet aux fonctions de prendre des arguments optionnels. Dans les coulisses, ceci est mis en œuvre comme un autre cas particulier d'expédition multiple. Par exemple, résolvons le problème populaire de Fizz Buzz . Par défaut, nous le ferons pour les nombres dans la gamme 1:10
, mais nous autoriserons une valeur différente si nécessaire. Nous autoriserons également différentes phrases à utiliser pour Fizz
ou Buzz
.
function fizzbuzz(xs=1:10, fizz="Fizz", buzz="Buzz")
for i in xs
if i % 15 == 0
println(fizz, buzz)
elseif i % 3 == 0
println(fizz)
elseif i % 5 == 0
println(buzz)
else
println(i)
end
end
end
Si nous inspectons fizzbuzz
dans le REPL, il y a quatre méthodes. Une méthode a été créée pour chaque combinaison d'arguments autorisée.
julia> fizzbuzz
fizzbuzz (generic function with 4 methods)
julia> methods(fizzbuzz)
# 4 methods for generic function "fizzbuzz":
fizzbuzz() at REPL[96]:2
fizzbuzz(xs) at REPL[96]:2
fizzbuzz(xs, fizz) at REPL[96]:2
fizzbuzz(xs, fizz, buzz) at REPL[96]:2
Nous pouvons vérifier que nos valeurs par défaut sont utilisées quand aucun paramètre n'est fourni:
julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
mais que les paramètres optionnels sont acceptés et respectés si nous les fournissons:
julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8
Envoi paramétrique
Il est fréquent qu'une fonction soit envoyée sur des types paramétriques, tels que Vector{T}
ou Dict{K,V}
, mais les paramètres de type ne sont pas fixes. Ce cas peut être traité en utilisant la répartition paramétrique:
julia> foo{T<:Number}(xs::Vector{T}) = @show xs .+ 1
foo (generic function with 1 method)
julia> foo(xs::Vector) = @show xs
foo (generic function with 2 methods)
julia> foo([1, 2, 3])
xs .+ 1 = [2,3,4]
3-element Array{Int64,1}:
2
3
4
julia> foo([1.0, 2.0, 3.0])
xs .+ 1 = [2.0,3.0,4.0]
3-element Array{Float64,1}:
2.0
3.0
4.0
julia> foo(["x", "y", "z"])
xs = String["x","y","z"]
3-element Array{String,1}:
"x"
"y"
"z"
On peut être tenté d'écrire simplement xs::Vector{Number}
. Mais cela ne fonctionne que pour les objets dont le type est explicitement Vector{Number}
:
julia> isa(Number[1, 2], Vector{Number})
true
julia> isa(Int[1, 2], Vector{Number})
false
Cela est dû à l' invariance paramétrique : l'objet Int[1, 2]
n'est pas un Vector{Number}
, car il ne peut contenir que des Int
, alors qu'un Vector{Number}
devrait pouvoir contenir n'importe quel type de nombres.
Rédaction de code générique
Dispatch est une fonctionnalité incroyablement puissante, mais il est souvent préférable d'écrire du code générique qui fonctionne pour tous les types, au lieu de spécialiser le code pour chaque type. L'écriture de code générique évite la duplication de code.
Par exemple, voici le code pour calculer la somme des carrés d'un vecteur d'entiers:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
Mais ce code ne fonctionne que pour un vecteur de Int
s. Cela ne fonctionnera pas sur un UnitRange
:
julia> sumsq(1:10)
ERROR: MethodError: no method matching sumsq(::UnitRange{Int64})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
Cela ne fonctionnera pas sur un Vector{Float64}
:
julia> sumsq([1.0, 2.0])
ERROR: MethodError: no method matching sumsq(::Array{Float64,1})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
Une meilleure façon d’écrire cette fonction sumsq
devrait être
function sumsq(v::AbstractVector)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Cela fonctionnera sur les deux cas énumérés ci-dessus. Mais il y a des collections que nous voudrions peut-être additionner à des carrés qui ne sont pas du tout des vecteurs. Par exemple,
julia> sumsq(take(countfrom(1), 100))
ERROR: MethodError: no method matching sumsq(::Base.Take{Base.Count{Int64}})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
sumsq(::AbstractArray{T,1}) at REPL[11]:2
montre que nous ne pouvons pas additionner les carrés d'une itération paresseuse .
Une implémentation encore plus générique est simplement
function sumsq(v)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Ce qui fonctionne dans tous les cas:
julia> sumsq(take(countfrom(1), 100))
338350
C'est le code de Julia le plus idiomatique, capable de gérer toutes sortes de situations. Dans certaines autres langues, la suppression des annotations de type peut affecter les performances, mais ce n'est pas le cas dans Julia; seule la stabilité de type est importante pour la performance.
Factorielle impérative
Une syntaxe longue est disponible pour définir des fonctions multilignes. Cela peut être utile lorsque nous utilisons des structures impératives telles que des boucles. L'expression en queue est renvoyée. Par exemple, la fonction ci-dessous utilise une boucle for
pour calculer la factorielle d'un entier n
:
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
fact
end
Usage:
julia> myfactorial(10)
3628800
Dans les fonctions plus longues, il est courant de voir la déclaration de return
utilisée. L'instruction de return
n'est pas nécessaire en position de queue, mais elle est parfois utilisée pour plus de clarté. Par exemple, une autre manière d'écrire la fonction ci-dessus serait
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
return fact
end
qui a un comportement identique à la fonction ci-dessus.
Fonctions anonymes
Syntaxe de flèche
Les fonctions anonymes peuvent être créées en utilisant la syntaxe ->
. Ceci est utile pour transmettre des fonctions à des fonctions de niveau supérieur , telles que la fonction de map
. La fonction ci-dessous calcule le carré de chaque nombre dans un tableau A
squareall(A) = map(x -> x ^ 2, A)
Un exemple d'utilisation de cette fonction:
julia> squareall(1:10)
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
Syntaxe multiligne
Des fonctions anonymes multilignes peuvent être créées à l'aide de la syntaxe de function
. Par exemple, l'exemple suivant calcule les factorielles des n
premiers nombres, mais en utilisant une fonction anonyme au lieu de la factorial
intégrée.
julia> map(function (n)
product = one(n)
for i in 1:n
product *= i
end
product
end, 1:10)
10-element Array{Int64,1}:
1
2
6
24
120
720
5040
40320
362880
3628800
Faire une syntaxe de bloc
Comme il est si courant de passer une fonction anonyme comme premier argument à une fonction, il existe une syntaxe de bloc do
. La syntaxe
map(A) do x
x ^ 2
end
est équivalent à
map(x -> x ^ 2, A)
mais le premier peut être plus clair dans de nombreuses situations, surtout si beaucoup de calculs sont effectués dans la fonction anonyme. do
syntaxe de bloc est particulièrement utile pour les entrées et sorties de fichiers pour des raisons de gestion des ressources.