Il concetto Basics ha introdotto due modi per definire una funzione.
Il più generale è la forma multilinea:
function muladd(x, y, z)
x * y + z
end
La forma «assegnazione», o «a riga singola», per definizioni brevi:
muladd(x, y, z) = x * y + z
In una terza forma, ancora più breve, si può creare una funzione breve e monouso senza darle un nome:
julia> map(x -> 2x, 1:3)
4-element Vector{Int64}:
2
4
6
julia> map((x, y) -> x * y, 1:3, 4:6)
3-element Vector{Int64}:
4
10
18
In questo caso, x -> 2x è una «funzione anonima».
Equivale a quella che in altri linguaggi viene chiamata «funzione lambda».
Nota che più argomenti richiedono le parentesi, come in (x, y) -> x * y.
Le funzioni anonime sono comuni nel codice Julia, soprattutto se combinate con le funzioni di ordine superiore come map() e filter().
Finora, nel programma, abbiamo visto solo funzioni che hanno un numero preciso di argomenti e che richiedono che le chiamate li forniscano tutti, nell'ordine corretto. Sarebbe limitante e poco pratico, quindi esistono diverse altre possibilità.
Come molti linguaggi, Julia permette alle definizioni di funzione di specificare valori predefiniti per singoli argomenti.
La chiamata di funzione può quindi fornire un valore per quell'argomento, oppure ometterlo e affidarsi al valore predefinito.
julia> f(x, y=10) = x * y
f (generic function with 2 methods)
julia> f(2, 3)
6
julia> f(2)
20
Tutti gli argomenti senza valori predefiniti devono venire prima di ogni argomento con un valore predefinito, il che significa che f(x=2, y) non sarebbe valido.
Tutti gli esempi visti finora usano positional arguments, dove i valori forniti in una chiamata di funzione devono rispettare l'ordine degli argomenti corrispondenti nella definizione della funzione.
Come molti linguaggi, Julia permette anche di usare keyword arguments.
Le chiamate di funzione devono specificare il nome dell'argomento, ma più argomenti keyword possono poi essere indicati in qualsiasi ordine.
Una caratteristica peculiare di Julia è che gli argomenti keyword (se ce ne sono) nella definizione della funzione devono essere preceduti da un punto e virgola ; per separarli dagli eventuali argomenti posizionali.
Una chiamata di funzione può usare ; oppure , tra l'ultimo argomento posizionale e il primo argomento keyword.
julia> b(x; y) = x + y
b (generic function with 1 method)
julia> b(2, y=3)
5
# keyword is required when calling
julia> b(2, 3)
ERROR: MethodError: no method matching b(::Int64, ::Int64)
The function `b` exists, but no method is defined for this combination of argument types.
I valori predefiniti possono essere specificati facoltativamente, esattamente come per gli argomenti posizionali.
Capita spesso di ritrovarsi con una sintassi come myarg=myarg in una chiamata di funzione, quando una variabile con lo stesso nome del parametro è stata calcolata in anticipo.
In questa situazione è ammessa una sintassi abbreviata:
julia> width = 4.0
4.0
julia> height = √ width
2.0
julia> area(; width, height) = width * height
area (generic function with 1 method)
# repetition
julia> area(; width=width, height=height)
8.0
# shorthand form
julia> area(; width, height)
8.0
Nel caso te lo stessi chiedendo, questi sono i nomi standard di un aspetto utile della sintassi di Julia.
Entrambi si riferiscono all'operatore ....
Lo splatting si usa nelle chiamate di funzione, per espandere le collezioni nei singoli valori richiesti dalla funzione.
Forse è più facile da mostrare che da spiegare:
julia> fxyz(x, y, z) = x * y * z
fxyz (generic function with 1 method)
julia> xyz = [2, 3, 4]
3-element Vector{Int64}:
2
3
4
# Using the vector directly in a function call is invalid
julia> fxyz(xyz)
ERROR: MethodError: no method matching fxyz(::Vector{Int64})
The function `fxyz` exists, but no method is defined for this combination of argument types.
# splatting converts the vector to 3 numbers, used as positional arguments
julia> fxyz(xyz...)
24
Alcune «chiamate di funzione» sono nascoste dallo zucchero sintattico, quindi lo splatting può essere usato anche in modi meno ovvi.
Per esempio, l'assegnazione multipla usa internamente una funzione costruttrice di tuple:
julia> first, rest... = [1, 2, 3, 4]
4-element Vector{Int64}:
1
2
3
4
julia> first
1
julia> rest
3-element Vector{Int64}:
2
3
4
Gli argomenti keyword possono anche essere forniti tramite splatting, in genere usando un named tuple.
Anche un Dict funziona, ma le chiavi devono essere simboli (qui le stringhe non funzionano).
# function with 3 keyword arguments
julia> fabc(; a, b, c) = a + b + c
fabc (generic function with 1 method)
# named tuple
julia> abc_nt = (a=2, b=3, c=4)
(a = 2, b = 3, c = 4)
# there are no positional arguments, so need to use ; before kw argument
julia> fabc(;abc_nt...)
9
# Dict
julia> abc_dict = Dict(:a=>2, :b=>3, :c=>4)
Dict{Symbol, Int64} with 3 entries:
:a => 2
:b => 3
:c => 4
julia> fabc(;abc_dict...)
9
Lo slurping si usa nella definizione di funzione, per impacchettare un numero arbitrario di singoli valori in una collezione.
julia> f_more(i, j, more...) = i + j + sum(more)
f_more (generic function with 1 method)
julia> f_more(1, 3, 5, 7, 9, 11)
36
Il nome dell'argomento assorbito dallo slurp (in questo caso more) non ha importanza.
Il tipo di questa variabile è scelto dal compilatore, ma per gli argomenti posizionali sarà probabilmente tuple o qualcosa di simile.
Anche gli argomenti keyword possono essere assorbiti dallo slurp, producendo un Dict (o simile).
julia> f_kwslurp(x, y; switches...) = :mult in keys(switches) ? x * y : x + y
f_kwslurp (generic function with 1 method)
julia> f_kwslurp(5, 6; mult=true)
30
julia> f_kwslurp(5, 6)
11
Nella chiamata si può usare qualsiasi argomento keyword. È la definizione della funzione a decidere a quali keyword rispondere e quali ignorare.