Julia Language
Funktionen
Suche…
Syntax
- f (n) = ...
- Funktion f (n) ... Ende
- n :: Typ
- x -> ...
- f (n) do ... end
Bemerkungen
Neben generischen Funktionen (die am häufigsten vorkommen) gibt es auch integrierte Funktionen. Solche Funktionen umfassen is
, isa
, typeof
, throw
und ähnliche Funktionen. Integrierte Funktionen werden normalerweise in C anstelle von Julia implementiert. Sie können daher nicht auf Argumenttypen für den Versand spezialisiert werden.
Platzieren Sie eine Zahl
Dies ist die einfachste Syntax zum Definieren einer Funktion:
square(n) = n * n
Um eine Funktion aufzurufen, verwenden Sie runde Klammern (ohne Leerzeichen dazwischen):
julia> square(10)
100
Funktionen sind Objekte in Julia und wir können sie in der REPL wie alle anderen Objekte zeigen:
julia> square
square (generic function with 1 method)
Alle Julia-Funktionen sind standardmäßig generisch (auch als polymorph bezeichnet ). Unsere square
funktioniert genauso gut mit Fließkommazahlen:
julia> square(2.5)
6.25
... oder sogar Matrizen :
julia> square([2 4
2 1])
2×2 Array{Int64,2}:
12 12
6 9
Rekursive Funktionen
Einfache Rekursion
Mit Rekursion und dem ternären bedingten Operator können wir eine alternative Implementierung der eingebauten factorial
Funktion erstellen:
myfactorial(n) = n == 0 ? 1 : n * myfactorial(n - 1)
Verwendungszweck:
julia> myfactorial(10)
3628800
Mit Bäumen arbeiten
Rekursive Funktionen sind oft am nützlichsten für Datenstrukturen, insbesondere für Baumdatenstrukturen. Da Ausdrücke in Julia Baumstrukturen sind, kann Rekursion für die Metaprogrammierung sehr nützlich sein. Die folgende Funktion sammelt beispielsweise eine Menge aller Köpfe, die in einem Ausdruck verwendet werden.
heads(ex::Expr) = reduce(∪, Set((ex.head,)), (heads(a) for a in ex.args))
heads(::Any) = Set{Symbol}()
Wir können überprüfen, ob unsere Funktion wie vorgesehen funktioniert:
julia> heads(:(7 + 4x > 1 > A[0]))
Set(Symbol[:comparison,:ref,:call])
Diese Funktion ist kompakt und verwendet eine Vielzahl fortgeschrittener Techniken, z. B. die Funktion zum reduce
höherer Ordnung , den Datentyp Set
und Generatorausdrücke.
Einführung in den Versand
Wir können die ::
-Syntax verwenden, um den Typ eines Arguments abzusetzen.
describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"
Verwendungszweck:
julia> describe(10)
"integer 10"
julia> describe(1.0)
"floating point 1.0"
Im Gegensatz zu vielen Sprachen, die normalerweise entweder statische Mehrfachzustellung oder dynamische Einzelzustellung bereitstellen, verfügt Julia über vollständige dynamische Mehrfachzustellung. Das heißt, Funktionen können auf mehrere Argumente spezialisiert sein. Dies ist praktisch, wenn Sie spezielle Methoden für Operationen mit bestimmten Typen und Fallback-Methoden für andere Typen definieren.
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"
Verwendungszweck:
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"
Optionale Argumente
Julia erlaubt Funktionen, optionale Argumente zu übernehmen. Hinter den Kulissen wird dies als ein weiterer Sonderfall des Mehrfachversands implementiert. Lösen wir zum Beispiel das beliebte Fizz Buzz-Problem . Standardmäßig machen wir dies für Zahlen im Bereich 1:10
, aber wenn nötig, erlauben wir einen anderen Wert. Wir werden auch verschiedene Sätze für Fizz
oder Buzz
zulassen.
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
Wenn wir fizzbuzz
in der REPL untersuchen, heißt es, dass es vier Methoden gibt. Für jede zulässige Kombination von Argumenten wurde eine Methode erstellt.
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
Wir können überprüfen, ob unsere Standardwerte verwendet werden, wenn keine Parameter angegeben werden:
julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
Die optionalen Parameter werden jedoch akzeptiert und respektiert, wenn wir sie angeben:
julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8
Parametrischer Versand
Es ist häufig der Fall, dass eine Funktion auf parametrische Typen wie Vector{T}
oder Dict{K,V}
, aber die Typparameter sind nicht festgelegt. Dieser Fall kann mit dem parametrischen Versand behandelt werden:
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"
Man könnte versucht sein, einfach xs::Vector{Number}
zu schreiben. Dies funktioniert jedoch nur für Objekte, deren Typ explizit Vector{Number}
:
julia> isa(Number[1, 2], Vector{Number})
true
julia> isa(Int[1, 2], Vector{Number})
false
Dies liegt an der parametrischen Invarianz : Das Objekt Int[1, 2]
ist kein Vector{Number}
, da es nur Int
s enthalten kann, wohingegen ein Vector{Number}
alle möglichen Zahlen enthalten kann.
Generischen Code schreiben
Dispatch ist eine unglaublich leistungsstarke Funktion, aber häufig ist es besser, generischen Code zu schreiben, der für alle Typen geeignet ist, anstatt den Code für jeden Typ zu spezialisieren. Durch das Schreiben von generischem Code wird die Duplizierung von Code vermieden.
Hier ist zum Beispiel Code, um die Summe der Quadrate eines Vektors von Ganzzahlen zu berechnen:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
Dieser Code funktioniert jedoch nur für einen Vektor von Int
s. Es funktioniert nicht bei einem UnitRange
:
julia> sumsq(1:10)
ERROR: MethodError: no method matching sumsq(::UnitRange{Int64})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
Es funktioniert nicht auf einem 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
Eine bessere Methode zum Schreiben dieser sumsq
Funktion sollte sein
function sumsq(v::AbstractVector)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Dies funktioniert in den beiden oben aufgeführten Fällen. Aber es gibt einige Sammlungen, in denen wir vielleicht die Quadrate davon zusammenfassen wollen, die in keiner Weise Vektoren sind. Zum Beispiel,
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
zeigt, dass wir die Quadrate eines faulen Iterablen nicht summieren können.
Eine noch allgemeinere Implementierung ist einfach
function sumsq(v)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Was funktioniert in allen Fällen:
julia> sumsq(take(countfrom(1), 100))
338350
Dies ist der idiomatischste Julia-Code und kann mit allen möglichen Situationen umgehen. In einigen anderen Sprachen kann das Entfernen von Typanmerkungen die Leistung beeinträchtigen. Dies ist jedoch in Julia nicht der Fall. Nur die Typenstabilität ist wichtig für die Leistung.
Imperative Fakultät
Für die Definition von mehrzeiligen Funktionen steht eine Langform-Syntax zur Verfügung. Dies kann nützlich sein, wenn wir imperative Strukturen wie Schleifen verwenden. Der Ausdruck in Endposition wird zurückgegeben. Zum Beispiel verwendet die untenstehende Funktion eine for
Schleife , um die Fakultät einiger Integer n
zu berechnen:
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
fact
end
Verwendungszweck:
julia> myfactorial(10)
3628800
Bei längeren Funktionen wird häufig die verwendete return
Anweisung angezeigt. Die return
Anweisung ist in Endposition nicht erforderlich, wird jedoch aus Gründen der Klarheit manchmal verwendet. Eine andere Schreibweise für die obige Funktion wäre beispielsweise
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
return fact
end
was im Verhalten mit der Funktion oben identisch ist.
Anonyme Funktionen
Pfeilsyntax
Anonyme Funktionen können mit der Syntax ->
erstellt werden. Dies ist hilfreich, wenn Sie Funktionen an Funktionen höherer Ordnung übergeben , z. B. die map
Funktion. Die folgende Funktion berechnet das Quadrat jeder Zahl in einem Feld A
squareall(A) = map(x -> x ^ 2, A)
Ein Beispiel für die Verwendung dieser Funktion:
julia> squareall(1:10)
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
Mehrzeilige Syntax
Anonyme Funktionen mit mehreren Linien können mithilfe der function
erstellt werden. Im folgenden Beispiel werden beispielsweise die Fakultäten der ersten n
Zahlen berechnet, wobei jedoch eine anonyme Funktion anstelle der eingebauten factorial
.
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
Blockieren Sie die Syntax
Da es so üblich ist, eine anonyme Funktion als erstes Argument an eine Funktion zu übergeben, gibt es eine do
Block-Syntax. Die Syntax
map(A) do x
x ^ 2
end
ist äquivalent zu
map(x -> x ^ 2, A)
Ersteres kann jedoch in vielen Situationen klarer sein, insbesondere wenn in der anonymen Funktion viel berechnet wird. do
Blocksyntax ist besonders für die Dateieingabe und -ausgabe aus Gründen der Ressourcenverwaltung hilfreich.