O conceito Basics apresentou duas formas de definir uma função.
De modo mais geral, a forma multilinha:
function muladd(x, y, z)
x * y + z
end
A forma de "atribuição", ou de "linha única", para definições curtas:
muladd(x, y, z) = x * y + z
Em uma terceira forma, ainda mais curta, é possível criar uma função curta e de uso único sem um 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
Nesse caso, x -> 2x é uma "função anônima".
Isso equivale ao que outras linguagens chamam de "função lambda".
Repare que vários argumentos precisam de parênteses, como em (x, y) -> x * y.
Funções anônimas são comuns no código Julia, especialmente quando combinadas com funções de ordem superior, como map() e filter().
Até agora, no syllabus, vimos apenas funções que têm um número exato de argumentos e exigem que as chamadas forneçam todos eles, na ordem correta. Isso seria limitante e inconveniente, então existem várias outras opções.
Como muitas linguagens, Julia permite que as definições de função forneçam valores padrão para argumentos individuais.
A chamada de função pode então fornecer um valor para esse argumento, ou omiti-lo e usar o valor padrão.
julia> f(x, y=10) = x * y
f (generic function with 2 methods)
julia> f(2, 3)
6
julia> f(2)
20
Todos os argumentos sem valor padrão devem vir antes de qualquer argumento com valor padrão, o que significa que f(x=2, y) seria inválido.
Todos os exemplos até aqui usam positional arguments, em que os valores fornecidos em uma chamada de função devem corresponder à ordem dos argumentos correspondentes na definição da função.
Como muitas linguagens, Julia também permite keyword arguments.
As chamadas de função precisam especificar o nome do argumento, mas vários argumentos de palavra-chave podem então ser especificados em qualquer ordem.
Uma característica marcante de Julia é que os argumentos de palavra-chave (se houver) na definição da função precisam ser precedidos por um ponto e vírgula ;, para separá-los de quaisquer argumentos posicionais.
Uma chamada de função pode usar ; ou , entre o último argumento posicional e o primeiro argumento de palavra-chave.
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.
Os valores padrão podem ser especificados opcionalmente, exatamente como para os argumentos posicionais.
É comum acabarmos com uma sintaxe como myarg=myarg dentro de uma chamada de função, quando uma variável com o mesmo nome do parâmetro já foi calculada antes.
Uma sintaxe abreviada é permitida nessa situação:
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
Esses são os nomes padrão de um aspecto útil da sintaxe de Julia, caso você tenha ficado curioso.
Ambos se referem ao operador ....
O splat é usado em chamadas de função, para expandir coleções em valores individuais exigidos pela função.
Talvez seja mais fácil demonstrar do que explicar:
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
Algumas "chamadas de função" ficam escondidas por açúcar sintático, então o splat também pode ser usado de formas menos óbvias.
Por exemplo, a atribuição múltipla usa internamente uma função construtora de tuplas:
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
Argumentos de palavra-chave também podem ser fornecidos por splat, normalmente usando uma named tuple.
Um Dict também funciona, mas as chaves precisam ser símbolos (strings não funcionam aqui).
# 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
O slurp é usado na definição da função, para empacotar um número arbitrário de valores individuais em uma coleção.
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
O nome do argumento capturado pelo slurp (nesse caso, more) não importa.
O tipo dessa variável é escolhido pelo compilador, mas, para argumentos posicionais, provavelmente será tuple ou algo parecido.
Argumentos de palavra-chave também podem passar por slurp, gerando um Dict (ou algo parecido).
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
Qualquer argumento de palavra-chave pode ser usado na chamada. Cabe à definição da função decidir a quais palavras-chave responder e quais ignorar.