Julia Language
Funkcje
Szukaj…
Składnia
- f (n) = ...
- funkcja f (n) ... koniec
- n :: Rodzaj
- x -> ...
- f (n) do ... koniec
Uwagi
Oprócz funkcji ogólnych (które są najczęściej), istnieją również funkcje wbudowane. Funkcje te obejmują is
, isa
, typeof
, throw
, i podobne funkcje. Wbudowane funkcje są zazwyczaj implementowane w C zamiast w Julii, więc nie można ich wyspecjalizować w typach argumentów do wysłania.
Wyprostuj liczbę
Jest to najłatwiejsza składnia do zdefiniowania funkcji:
square(n) = n * n
Aby wywołać funkcję, użyj nawiasów okrągłych (bez odstępów między nimi):
julia> square(10)
100
Funkcje są obiektami w Julii i możemy je pokazać w REPL, jak w przypadku innych obiektów:
julia> square
square (generic function with 1 method)
Wszystkie funkcje Julii są domyślnie ogólne (inaczej znane jako polimorficzne ). Nasza funkcja square
działa równie dobrze z wartościami zmiennoprzecinkowymi:
julia> square(2.5)
6.25
... a nawet matryce :
julia> square([2 4
2 1])
2×2 Array{Int64,2}:
12 12
6 9
Funkcje rekurencyjne
Prosta rekurencja
Korzystając z rekurencji i trójskładnikowego operatora warunkowego , możemy stworzyć alternatywną implementację wbudowanej funkcji factorial
:
myfactorial(n) = n == 0 ? 1 : n * myfactorial(n - 1)
Stosowanie:
julia> myfactorial(10)
3628800
Praca z drzewami
Funkcje rekurencyjne są często najbardziej przydatne w strukturach danych, zwłaszcza strukturach drzewa. Ponieważ wyrażenia w Julii są strukturami drzewnymi, rekurencja może być bardzo przydatna do metaprogramowania . Na przykład poniższa funkcja gromadzi zestaw wszystkich głów użytych w wyrażeniu.
heads(ex::Expr) = reduce(∪, Set((ex.head,)), (heads(a) for a in ex.args))
heads(::Any) = Set{Symbol}()
Możemy sprawdzić, czy nasza funkcja działa zgodnie z przeznaczeniem:
julia> heads(:(7 + 4x > 1 > A[0]))
Set(Symbol[:comparison,:ref,:call])
Ta funkcja jest zwarta i wykorzystuje szereg bardziej zaawansowanych technik, takich jak funkcja reduce
wyższego rzędu , Set
typ danych i wyrażenia generatora.
Wprowadzenie do wysyłki
Możemy użyć składni ::
aby wywołać typ argumentu.
describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"
Stosowanie:
julia> describe(10)
"integer 10"
julia> describe(1.0)
"floating point 1.0"
W przeciwieństwie do wielu języków, które zwykle zapewniają statyczną wielokrotną wysyłkę lub dynamiczną pojedynczą wysyłkę, Julia ma pełną dynamiczną wielokrotną wysyłkę. Oznacza to, że funkcje mogą być wyspecjalizowane dla więcej niż jednego argumentu. Jest to przydatne przy definiowaniu specjalistycznych metod dla operacji na niektórych typach i metod awaryjnych dla innych typów.
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"
Stosowanie:
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"
Opcjonalne argumenty
Julia pozwala funkcjom przyjmować opcjonalne argumenty. Za kulisami jest to realizowane jako kolejny specjalny przypadek wielokrotnej wysyłki. Na przykład rozwiążmy popularny problem Fizz Buzz . Domyślnie zrobimy to dla liczb z zakresu 1:10
, ale w razie potrzeby zezwolimy na inną wartość. Umożliwimy również użycie różnych wyrażeń w przypadku Fizz
lub 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
Jeśli sprawdzimy fizzbuzz
w REPL, fizzbuzz
, że istnieją cztery metody. Dla każdej dozwolonej kombinacji argumentów utworzono jedną metodę.
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
Możemy sprawdzić, czy nasze wartości domyślne są używane, gdy nie podano żadnych parametrów:
julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
ale parametry opcjonalne są akceptowane i przestrzegane, jeśli je zapewnimy:
julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8
Wysyłka parametryczna
Często zdarza się, że funkcja powinna wywoływać typy parametryczne, takie jak Vector{T}
lub Dict{K,V}
, ale parametry typu nie są ustalone. Ten przypadek można rozwiązać za pomocą wysyłki parametrycznej:
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"
Można pokusić się o napisanie xs::Vector{Number}
. Ale działa to tylko w przypadku obiektów, których typ jest wyraźnie Vector{Number}
:
julia> isa(Number[1, 2], Vector{Number})
true
julia> isa(Int[1, 2], Vector{Number})
false
Wynika to z niezmienności parametrycznej : obiekt Int[1, 2]
nie jest Vector{Number}
, ponieważ może zawierać tylko Int
, podczas gdy Vector{Number}
powinien zawierać dowolne liczby.
Pisanie kodu ogólnego
Wysyłanie to niezwykle potężna funkcja, ale często lepiej jest pisać ogólny kod, który działa dla wszystkich typów, zamiast specjalizować kod dla każdego typu. Pisanie kodu ogólnego pozwala uniknąć powielania kodu.
Na przykład, oto kod do obliczenia sumy kwadratów wektora liczb całkowitych:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
Ale ten kod działa tylko dla wektora Int
. To nie będzie działać na UnitRange
:
julia> sumsq(1:10)
ERROR: MethodError: no method matching sumsq(::UnitRange{Int64})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
To nie będzie działać na 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
Lepszym sposobem na napisanie tej funkcji sumsq
powinno być
function sumsq(v::AbstractVector)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Będzie to działać w dwóch przypadkach wymienionych powyżej. Ale istnieją pewne kolekcje, które moglibyśmy chcieć podsumować, że kwadraty w ogóle nie są wektorami. Na przykład,
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
pokazuje, że nie możemy zsumować kwadratów leniwej iteracji .
Jeszcze bardziej ogólna implementacja jest po prostu
function sumsq(v)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Który działa we wszystkich przypadkach:
julia> sumsq(take(countfrom(1), 100))
338350
Jest to najbardziej idiomatyczny kod Julii, który obsługuje różne sytuacje. W niektórych innych językach usunięcie adnotacji typu może wpłynąć na wydajność, ale w przypadku Julii tak nie jest; tylko stabilność typu jest ważna dla wydajności.
Tryb rozkazujący
Dostępna jest długa forma do definiowania funkcji wieloliniowych. Może to być przydatne, gdy używamy struktur imperatywnych, takich jak pętle. Zwracane jest wyrażenie w pozycji ogona. Na przykład poniższa funkcja używa pętli for
do obliczenia silni niektórych liczb całkowitych n
:
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
fact
end
Stosowanie:
julia> myfactorial(10)
3628800
W dłuższych funkcjach często jest używana instrukcja return
. Instrukcja return
nie jest konieczna w pozycji ogona, ale wciąż jest używana do zachowania przejrzystości. Na przykład innym sposobem pisania powyższej funkcji byłby
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
return fact
end
który jest identyczny w działaniu z powyższą funkcją.
Funkcje anonimowe
Składnia strzałki
Funkcje anonimowe można tworzyć za pomocą składni ->
. Jest to przydatne do przekazywania funkcji do funkcji wyższego rzędu , takich jak funkcja map
. Poniższa funkcja oblicza kwadrat każdej liczby w tablicy A
squareall(A) = map(x -> x ^ 2, A)
Przykład użycia tej funkcji:
julia> squareall(1:10)
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
Składnia wieloliniowa
Wieloliniowe anonimowe funkcje można tworzyć za pomocą składni function
. Na przykład w poniższym przykładzie obliczono silnię pierwszych n
liczb, ale przy użyciu funkcji anonimowej zamiast wbudowanej 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
Blokuj składnię
Ponieważ przekazywanie anonimowej funkcji jako pierwszego argumentu do funkcji jest tak powszechne, istnieje składnia do
block. Składnia
map(A) do x
x ^ 2
end
jest równa
map(x -> x ^ 2, A)
ale to pierwsze może być bardziej zrozumiałe w wielu sytuacjach, zwłaszcza jeśli anonimowa funkcja wykonuje wiele obliczeń. Składnia do
block jest szczególnie przydatna do wprowadzania i wysyłania plików ze względu na zarządzanie zasobami.