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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow