O conceito Basics apresentou duas formas de definir uma função.
De forma mais geral, a forma com várias linhas:
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
Numa terceira forma, ainda mais curta, é possível criar uma função pequena, de uso único, sem 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
Neste caso, x -> 2x é uma "função anónima".
Isto equivale ao que outras linguagens chamam de "função lambda".
Repara que vários argumentos precisam de parênteses, como em (x, y) -> x * y.
As funções anónimas são comuns no código Julia, sobretudo quando combinadas com funções de ordem superior como map() e filter().
Até agora, no programa, só vimos funções que têm um número exato de argumentos e que exigem que as chamadas à função forneçam todos eles, pela ordem correta. Isso seria limitativo e inconveniente, por isso existem várias outras opções.
Como muitas linguagens, o Julia permite que as definições de funções forneçam valores por omissão para argumentos individuais.
Depois, a chamada à função pode fornecer um valor para esse argumento, ou omiti-lo e usar o valor por omissã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 valores por omissão têm de vir antes de qualquer argumento com valores por omissão, o que significa que f(x=2, y) seria inválido.
Todos os exemplos até agora usam positional arguments, em que os valores fornecidos numa chamada à função têm de corresponder à ordem dos argumentos correspondentes na definição da função.
Como muitas linguagens, o Julia também permite keyword arguments.
As chamadas à função têm de indicar o nome do argumento, mas depois podem especificar-se vários argumentos de palavra-chave por qualquer ordem.
Uma característica distintiva do Julia é que os argumentos de palavra-chave (caso existam) na definição da função têm de ser precedidos por um ponto e vírgula ; para os separar de eventuais argumentos posicionais.
Uma chamada à 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 por omissão podem ser especificados opcionalmente, exatamente como nos argumentos posicionais.
É comum acabarmos com uma sintaxe como myarg=myarg numa chamada à função, quando uma variável com o mesmo nome do parâmetro já foi calculada previamente.
Nesta situação, é permitida uma sintaxe abreviada:
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
Estes são os nomes habituais de um aspeto útil da sintaxe do Julia, caso tenhas curiosidade.
Ambos se referem ao operador ....
O splatting usa-se nas chamadas às funções, para expandir coleções em valores individuais exigidos pela função.
Talvez seja mais fácil mostrar 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 a funções" estão escondidas por açúcar sintático, por isso o splatting também pode ser usado de formas menos óbvias.
Por exemplo, a atribuição múltipla usa internamente uma função construtora de tuplos:
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
Os argumentos de palavra-chave também podem ser fornecidos por splatting, normalmente com um named tuple.
Um Dict também funciona, mas as chaves têm de ser símbolos (as 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 slurping usa-se na definição da função, para empacotar um número arbitrário de valores individuais numa 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 recolhido pelo slurp (neste caso, more) não tem importância.
O tipo desta variável é escolhido pelo compilador, mas, para argumentos posicionais, é provavelmente tuple ou algo semelhante.
Os argumentos de palavra-chave também podem ser recolhidos por slurp, dando um Dict (ou algo semelhante).
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
Pode usar-se qualquer argumento de palavra-chave na chamada. Cabe à definição da função decidir a que palavras-chave responder e quais ignorar.