Julia Language
funzioni
Ricerca…
Sintassi
- f (n) = ...
- funzione f (n) ... fine
- n :: Tipo
- x -> ...
- f (n) do ... end
Osservazioni
Oltre alle funzioni generiche (che sono le più comuni), esistono anche funzioni integrate. Tali funzioni includono is
, isa
, typeof
, throw
e funzioni simili. Le funzioni built-in sono in genere implementate in C anziché in Julia, quindi non possono essere specializzate sui tipi di argomenti per la spedizione.
Piazza un numero
Questa è la sintassi più semplice per definire una funzione:
square(n) = n * n
Per chiamare una funzione, usa parentesi tonde (senza spazi intermedi):
julia> square(10)
100
Le funzioni sono oggetti in Julia e possiamo mostrarle in REPL come con qualsiasi altro oggetto:
julia> square
square (generic function with 1 method)
Tutte le funzioni di Julia sono generiche (altrimenti conosciute come polimorfiche ) per impostazione predefinita. La nostra funzione square
funziona altrettanto bene con valori in virgola mobile:
julia> square(2.5)
6.25
... o anche matrici :
julia> square([2 4
2 1])
2×2 Array{Int64,2}:
12 12
6 9
Funzioni ricorsive
Ricorsione semplice
Usando la ricorsione e l' operatore condizionale ternario , possiamo creare un'implementazione alternativa della funzione factorial
integrata:
myfactorial(n) = n == 0 ? 1 : n * myfactorial(n - 1)
Uso:
julia> myfactorial(10)
3628800
Lavorare con gli alberi
Le funzioni ricorsive sono spesso le più utili su strutture dati, in particolare strutture di dati ad albero. Poiché le espressioni in Julia sono strutture ad albero, la ricorsione può essere molto utile per la metaprogrammazione . Ad esempio, la funzione seguente raccoglie un insieme di tutte le teste utilizzate in un'espressione.
heads(ex::Expr) = reduce(∪, Set((ex.head,)), (heads(a) for a in ex.args))
heads(::Any) = Set{Symbol}()
Possiamo verificare che la nostra funzione funzioni come previsto:
julia> heads(:(7 + 4x > 1 > A[0]))
Set(Symbol[:comparison,:ref,:call])
Questa funzione è compatta e utilizza una varietà di tecniche più avanzate, come la reduce
funzione di ordine superiore , il tipo di dati Set
e le espressioni del generatore.
Introduzione alla spedizione
Possiamo usare la ::
sintassi per inviare il tipo di argomento.
describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"
Uso:
julia> describe(10)
"integer 10"
julia> describe(1.0)
"floating point 1.0"
A differenza di molte lingue, che in genere forniscono una distribuzione multipla statica o una singola spedizione dinamica, Julia ha una distribuzione multipla dinamica completa. Cioè, le funzioni possono essere specializzate per più di un argomento. Ciò è utile quando si definiscono metodi specializzati per operazioni su determinati tipi e metodi di fallback per altri tipi.
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"
Uso:
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"
Argomenti opzionali
Julia consente alle funzioni di prendere argomenti opzionali. Dietro le quinte, questo è implementato come un altro caso speciale di dispacciamento multiplo. Ad esempio, risolviamo il famoso problema di Fizz Buzz . Per impostazione predefinita, lo faremo per i numeri compresi nell'intervallo 1:10
, ma consentiremo un valore diverso se necessario. Permetteremo anche l'uso di frasi diverse per Fizz
o 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
Se ispezioniamo fizzbuzz
nel REPL, si dice che ci sono quattro metodi. È stato creato un metodo per ciascuna combinazione di argomenti consentita.
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
Possiamo verificare che i nostri valori predefiniti vengano utilizzati quando non vengono forniti parametri:
julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
ma che i parametri opzionali sono accettati e rispettati se li forniamo:
julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8
Invio parametrico
È frequente il caso che una funzione debba essere inviata su tipi parametrici, come Vector{T}
o Dict{K,V}
, ma i parametri del tipo non sono corretti. Questo caso può essere risolto utilizzando la spedizione parametrica:
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"
Si potrebbe essere tentati di scrivere semplicemente xs::Vector{Number}
. Ma questo funziona solo per oggetti il cui tipo è esplicitamente Vector{Number}
:
julia> isa(Number[1, 2], Vector{Number})
true
julia> isa(Int[1, 2], Vector{Number})
false
Ciò è dovuto all'invarianza parametrica : l'oggetto Int[1, 2]
non è un Vector{Number}
, perché può contenere solo Int
s, mentre un Vector{Number}
dovrebbe essere in grado di contenere qualsiasi tipo di numero.
Scrivere codice generico
Dispatch è una funzionalità incredibilmente potente, ma spesso è meglio scrivere codice generico che funzioni per tutti i tipi, invece di specializzare il codice per ogni tipo. La scrittura di codice generico evita la duplicazione del codice.
Ad esempio, ecco il codice per calcolare la somma dei quadrati di un vettore di numeri interi:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
Ma questo codice funziona solo per un vettore di Int
s. Non funzionerà su 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
Non funzionerà su 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
Dovrebbe essere un modo migliore per scrivere questa funzione sumsq
function sumsq(v::AbstractVector)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Questo funzionerà sui due casi sopra elencati. Ma ci sono alcune collezioni che potremmo voler sommare i quadrati di quello non sono affatto vettori, in nessun senso. Per esempio,
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
dimostra che non possiamo sommare i quadrati di un pigro iterabile .
Un'implementazione ancora più generica è semplicemente
function sumsq(v)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Che funziona in tutti i casi:
julia> sumsq(take(countfrom(1), 100))
338350
Questo è il codice Julia più idiomatico e può gestire ogni tipo di situazione. In alcuni altri linguaggi, la rimozione delle annotazioni di tipo può influire sulle prestazioni, ma non è questo il caso in Julia; solo la stabilità del tipo è importante per le prestazioni.
Fattoriale imperativo
Una sintassi di lunga durata è disponibile per la definizione di funzioni multi-linea. Questo può essere utile quando usiamo strutture imperative come i loop. L'espressione nella posizione di coda viene restituita. Ad esempio, la funzione seguente usa un ciclo for
per calcolare il fattoriale di qualche intero n
:
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
fact
end
Uso:
julia> myfactorial(10)
3628800
Nelle funzioni più lunghe, è comune vedere la dichiarazione di return
utilizzata. Il return
affermazione non è necessaria in posizione di coda, ma è ancora talvolta utilizzato per chiarezza. Ad esempio, un altro modo di scrivere la funzione sopra sarebbe
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
return fact
end
che è identico nel comportamento alla funzione di cui sopra.
Funzioni anonime
Sintassi della freccia
Le funzioni anonime possono essere create usando la sintassi ->
. Questo è utile per passare funzioni a funzioni di ordine superiore , come la funzione map
. La funzione seguente calcola il quadrato di ciascun numero in una matrice A
squareall(A) = map(x -> x ^ 2, A)
Un esempio di utilizzo di questa funzione:
julia> squareall(1:10)
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
Sintassi multilinea
Le funzioni anonime multilinea possono essere create usando la sintassi della function
. Ad esempio, il seguente esempio calcola i fattoriali dei primi n
numeri, ma usando una funzione anonima al posto del factorial
incorporato.
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
Do la sintassi del blocco
Perché è così comune per passare una funzione anonima come primo argomento a una funzione, c'è un do
sintassi del blocco. La sintassi
map(A) do x
x ^ 2
end
è equivalente a
map(x -> x ^ 2, A)
ma il primo può essere più chiaro in molte situazioni, specialmente se viene eseguita molta computazione nella funzione anonima. do
sintassi dei blocchi è particolarmente utile per l' input e l'output dei file per ragioni di gestione delle risorse.