Funções¶
Em Julia, uma função é um objeto que mapeia uma tupla de valores, os argumentos, a um valor de retorno. As funções, em Julia, são diferentes das funções matemáticas, pois as funções podem se alterar e afetadas pelo estado global do programa. A sintaxe básica para definir uma funções em Julia é:
function f(x,y)
x + y
end
Esta sintaxe é similar a do MATLAB, mas há algumas diferenças significativas:
- No MATLAB, esta definição deve ser salvar em um arquivo, nomeado
f.m
, enquanto que que em Julia, esta declaração pode aparecer em qualquer lugar, incluindo em uma sessão interativa. - No MATLAB, a declaração
end
final é opcional, sendo implicado pelo fim do arquivo. Em Julia, essa declaraçãoend
é obrigatória. - No MATLAB, esta função irá imprimir o valor
x + y
mas não retornará nenhum valor, enquanto que em Julia, a última expressão avaliada é o valor de retorno da função. - Os valores de uma expressão nunca são mostrados automaticamente exceto em sessões interativas. Ponto-e-vírgula são exigidos somente para separar expressões na mesma linha.
Geralmente, enquanto a sintaxe da definição de função é remanescente do MATLAB, a similaridade é apenas superficial. Logo, ao invés de continuar comparando as duas, a seguir, nós simplesmente descreveremos o comportamento das funções em Julia.
Existe uma forma mais compacta de definir uma função em Julia. A sintaxe tradicional de declaração de função apresentada acima é equivalente a forma compactada a seguir:
f(x,y) = x + y
Nessa forma compacta, o corpo da função deve ser uma única expressão, embora possa ser uma expressão composta (veja Compound Expressions). Definições de funções de forma curta e simples são comuns em Julia. A sintaxe curta da função é bastante idiomática, reduzindo consideravelmente a digitação e a poluição visual.
Uma função é chamada usando a sintaxe tradicional de parêntese:
julia> f(2,3)
5
Sem parênteses, a expressão f
refere-se ao objeto da função, e pode ser
passada como qualquer valor:
julia> g = f;
julia> g(2,3)
5
Há outras duas maneiras que as funções podem ser aplicadas: usando operadores
com sintaxe especial para certos nomes de funções (veja Operadores são Funções), ou com a função apply
:
julia> apply(f,2,3)
5
A função apply
aplicam seu primeiro argumento - um objeto de função - a
seus argumentos restantes.
A declaração “return”¶
O valor retornado por uma função é o valor da última expressão avaliada, que,
por padrão é a última expressão no corpo da definição da função. Na função de
exemplo, f
, da seção anterior isto é o valor da expressão x + y
.
Como em C e na maioria das outras línguas imperativas ou funcionais, a
declaração return
faz com que uma função retorne imediatamente,
fornecendo uma expressão cujo o valor será retornado:
function g(x,y)
return x * y
x + y
end
Como definições de funções podem ser feitas em sessões interativas, é fácil comparar estas definições:
f(x,y) = x + y
function g(x,y)
return x * y
x + y
end
julia> f(2,3)
5
julia> g(2,3)
6
Naturalmente, em uma função cujo corpo é linear como g
, o uso do return
é injustificado pois a expressão x + y
nunca é avaliada e nós poderíamos
simplesmente tornar x * y
a última expressão na função e omitir return
.
Já em conjunto com outras declarações de controle deo fluxo, contudo, o
return
é do uso real. A seguir, por exemplo, está uma funciona que calcula
o comprimento da hipotenusa de um triângulo retângulo com lados de comprimento
x e y, evitando overflow:
function hypot(x,y)
x = abs(x)
y = abs(y)
if x > y
r = y/x
return x*sqrt(1+r*r)
end
if y == 0
return zero(x)
end
r = x/y
return y*sqrt(1+r*r)
end
Há três possíveis pontos de retorno nesta função, retornando os valores de três
expressões diferentes, dependendo dos valores de x e y. O return
na
última linha podia ser omitido pois ele é o último expressão.
Operadores são funções¶
Em Julia, a maioria dos operadores são apenas funções com suport para sintaxe
especial. As exceções são operadores com semântica especial como o &&
e
||
. Estes operadores não podem ser funções pois o short-circuit
evaluation (veja Short-Circuit Evaluation) exige que seus operandos
não sejam avaliados antes da avaliação do operador. Logo, você também pode
aplicá-los usam uma lista de argumento entre parênteses, de forma semelhante
como qualquer outra função:
julia> 1 + 2 + 3
6
julia> +(1,2,3)
6
A forma infixa é exatamente equivalente a forma padrão - na verdade a primeira
forma é convertida para uma chamada de função internamente. Isto significa que
você também pode atribuir e passar operadores como +
e *
da mesma forma
como você faria para outra função:
julia> f = +;
julia> f(1,2,3)
6
Sob o nome f
, a função suporta a forma infixa.
Funções Anônimas¶
Funções em Julia são objetos de primeira classe: podem ser atribuídos a variáveis, chamadas usando a sintaxe padrão para chamada de função a partir da variável que foram atribuídas. Podem ser usadas como argumentos, e podem ser retornadas como valores. Também pode ser criadas anonimamente, sem ter um nome:
julia> x -> x^2 + 2x - 1
#<function>
Isto cria uma função sem nome que possue um argumento e que retorna o valor do
polinômio x ^2 + 2 x - 1. O uso principal para funções anônimas é
serem passadas para funções que recebem outras funções como argumentos. Um
exemplo clássico é a função do map
, que aplica uma função a cada valor de
um vetor e retorna um novo vetor que contem os valores resultantes:
julia> map(round, [1.2, 3.5, 1.7])
3-element Float64 Array:
1.0
4.0
2.0
Não existe problema se uma função, já nomeada, que efetua a transformação
desejada já existe para ser passada como o primeiro argumento da função
map
. Entretanto, frequentemente, não existe a função desejada pronta para
uso. Nestas situações, a função anónima permite a criação de um objeto função
para um único uso sem precisar atribuir um nome:
julia> map(x -> x^2 + 2x - 1, [1,3,-1])
3-element Int64 Array:
2
14
-2
Uma função anónima que aceita mais de um argumentos pode ser escrita usando a
sintaxe (x,y,z)->2x+y-z
. Uma função anónima sem argumento é escrita como
()->3
. A ideia de uma função sem argumentos pode parecer estranha, mas é
útil para “atrasar” algum cálculo. Neste uso, um bloco de código é envolvido
em uma função sem argumento, que é posteriormente invocada chamando f()
.
Retornando mais de um valor¶
Em Julia, uma tupla deve ser retornada para simular o retorno de mais de um valor. Contudo, os tuplas podem ser criadas e destruidas sem precisar de parênteses, fornecendo a ilusão de que mais de um valor esta sendo retornado, ao invés de uma única tuple. Por exemplo, a função a seguir retorna um par de valores:
function foo(a,b)
a+b, a*b
end
Se você chama essa função em uma sessão interativa sem atribuir o valor de retorno em nenhum lugar, você verá a tupla sendo retornada:
julia> foo(2,3)
(5,6)
Um uso típico de funções que retornam mais de um valor, contudo, extrai cada valor em uma variável. Julia suporta a “destruição” simplificada de tuplas que facilitam isto:
julia> x, y = foo(2,3);
julia> x
5
julia> y
6
Você também pode retornar mais de um valores através do uso explícito da expressão``return``:
function foo(a,b)
return a+b, a*b
end
Isto tem exatamente mesmo efeito que a definição anterior de foo
.
Funções com Número Variado de Argumentos¶
Frequentemente, é conveniente poder escrever funções que tomam um número
arbitrário de argumentos. Tais funções são tradicional conhecidas como funções
varargs, que um acrônimo para “variable number of arguments” (ou “número
variável de argumentos”, em tradução literal). Você pode definir uma função
varargs utilizando depois do último argumento uma elipse (...
):
bar(a,b,x...) = (a,b,x)
As variávies a
e b
são atribuidas aos primeiros dois argumento como é
o costume, e a variável x
é atribuida para coleção de zero ou mais valores
passados para bar
depois dos seus primeiros dois argumentos:
julia> bar(1,2)
(1,2,())
julia> bar(1,2,3)
(1,2,(3,))
julia> bar(1,2,3,4)
(1,2,(3,4))
julia> bar(1,2,3,4,5,6)
(1,2,(3,4,5,6))
Em todos estes casos, x
corresponde a uma tupla dos valores passado a
bar
.
Por outros lado, é frequentemente necessário “dividir” os valores presentes em
uma coleção iterável em argumentos individuais para uma chamda de função. Para
fazer isso, usa-se de forma análoga ...
mas na chamada da função:
julia> x = (3,4)
(3,4)
julia> bar(1,2,x...)
(1,2,(3,4))
Neste caso uma tupla de valores é dividido na chamada de uma função varargs precisamente onde o número de argumentos variável vai. Isso não precisar necessidade ser o caso:
julia> x = (2,3,4)
(2,3,4)
julia> bar(1,x...)
(1,2,(3,4))
julia> x = (1,2,3,4)
(1,2,3,4)
julia> bar(x...)
(1,2,(3,4))
Além disso, não é necessário dividir uma tupla para passá-la para uma função:
julia> x = [3,4]
2-element Int64 Array:
3
4
julia> bar(1,2,x...)
(1,2,(3,4))
julia> x = [1,2,3,4]
4-element Int64 Array:
1
2
3
4
julia> bar(x...)
(1,2,(3,4))
Além disso, a função não precisa ser varargs para que os argumentos sejam divididos (embora é frequentemente):
baz(a,b) = a + b
julia> args = [1,2]
2-element Int64 Array:
1
2
julia> baz(args...)
3
julia> args = [1,2,3]
3-element Int64 Array:
1
2
3
julia> baz(args...)
no method baz(Int64,Int64,Int64)
Como você pode ver, se o objeto a ser dividido na chamada da função resultar em um número de argumentos diferente do esperado, a função irá falhar, de forma semelhante se um muitos argumentos tivessem sido passados de forma explícita.
Argumentos opcionais¶
Em muitos casos, os argumentos de uma função possuem valores padrões que não
precisam ser passados explicitamentes em toda chamada de função. Por exemplo,
a função parseint(num,base)
interpreta interpreta uma string como um
número em alguma base. O valor padrão para o argumento base
é 10
. Este
comportamento pode ser expresso como:
function parseint(num, base=10)
###
end
Com esta definição, a função pode ser chamada com um ou dois argumentos, e
10
é passado automaticamente quando um segundo argumento não é
especificado:
julia> parseint("12",10)
12
julia> parseint("12",3)
5
julia> parseint("12")
12
Argumentos opcionais são na verdade apenas uma sintaxe conveniente para escrever mais de uma definição para um método com números diferentes de argumentos (veja Methods).
Argumento nomeado¶
Algumas funções precisam de um grande número de argumentos, ou têm um grande número de comportamentos. Recordar como chamar tais funções pode ser difícil. Argumentos nomeados, ou keyword arguments, podem facilitar o uso destas funções complexas e estendida ao permitindo que os argumentos sejam identificados por nome em vez de apenas pela da posição.
Por exemplo, considere uma função plot
que traça uma linha. Esta função
deve ter muitas opções, para controlar o estilo, largura, cor, ... da linha.
Se ela aceitar argumentos nomeados, um possível a chamada pode parecer com
plot(x, y, width=2)
, onde escolhemos especificar somente a largura da
linha. Observe que isto serve para duas finalidades. A chamada da função é mais
fácil de ler, desde que podemos etiquetar os argumentos com seu significado. E
também, torna-se possível passar qualquer subconjunto de argumentos em qualquer
ordem.
As funções com argumentos nomeados são definidas usando um ponto-e-vírgula na declaração:
function plot(x, y; style="solid", width=1, color="black")
###
end
Argumentos nomeados adicionais podem ser informados utilizando ...
, como
nas funções vargargs:
function f(x; args...)
###
end
Dentro de f
, args
será uma coleção de tuplas do tipo (chave,valor)
,
onde cada chave
é um símbolo. Tais coleções podem ser passadas como
argumentos nomeados usando um ponto-e-vírgula na chamada da função, f(x;
k...…)
. Dicionários podem ser usados para esta finalidade.
Sintaxe de bloco para argumentos de função¶
Passar funções como argumentos a outras funções é uma técnica poderosa,
mas a sintaxe para isso não é sempre conveniente. Tais chamadas são
especialmente difíceis de escrever quando o argumento da função exige mais de uma linhas.
Por um exemplo, considere a chamada da função map
passando uma função em diversos casos:
map(x->begin
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end,
[A, B, C])
Julia possue uma palavra reservado do
para reescrevendo este código de
forma mais clara:
map([A, B, C]) do x
if x < 0 && iseven(x)
return 0
elseif x == 0
return 1
else
return x
end
end
A sintaxe do x
cria uma função anónima com o argumento x
e passa essa
função como o primeiro argumento de mapa
. Esta sintaxe facilita usar
funções para estender a línguagem, pois as chamadas parecem com blocos de
código convencional. Há muitos usos diferentes da função mapa
, como
gerenciar o estado do sistema. Por exemplo, a biblioteca padrão fornece uma
função cd
para rodar código em um diretório especificado, e retornar ao
diretório anterior quando o código terminar ou abortar. Existe também uma
função open
que roda código garantindo que o arquivo aberto será
eventualmente fechado. Podemos combinar estas funções para escrever com
segurança um arquivo em um determinado diretório:
cd("data") do
open("outfile", "w") do f
write(f, data)
end
end
O argumento da função cd
não recebe nenhum argumento; é apenas um bloco de
código. O argumento da função open
recebe informações de como lidar com o
arquivo aberto.
Leitura adicional¶
Devemos mencionar aqui que esta não é uma imagem completa sobre definições de funções. Julia tem um sofisticado sistema de tipos e permite mais de uma declarações baseada no tipo de argumentos. Nenhuns dos exemplos dados aqui fornecem qualquer tipo de anotações sobre seus argumentos, significando que são aplicáveis a todos os tipos de argumentos. O sistema de tipos é descrito em Types e a definição de funções em termos de métodos escolhidos com base no tipo dos argumentos em tempo de execução é descrito em :ref: man-methods.