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.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow