Neste ponto do curso, você já viu muitas definições de função. Normalmente, elas eram apresentadas sem muitos comentários sobre a saída no REPL:
julia> f(x) = x^2
f (generic function with 1 method)
Por que "generic function with 1 method"?
Suponha que você queira ser mais específico quanto aos tipos dos argumentos.
julia> f(x::Integer) = x^2
f (generic function with 2 methods)
julia> f(x::Real) = x^2
f (generic function with 3 methods)
Ou poderíamos mudar o número de argumentos:
julia> f(x, y) = x * y
f (generic function with 4 methods)
Parece que novas definições de uma mesma função são chamadas de "métodos". Os métodos são adicionados a uma lista, em vez de sobrescrever a definição antiga.
Podemos listar os métodos facilmente:
julia> methods(f) # output edited for brevity
# 4 methods for generic function "f" from Main:
[1] f(x, y)
[2] f(x::Integer)
[3] f(x::Real)
[4] f(x)
Se chamarmos a função f, Julia de alguma forma sabe qual método usar:
julia> f(2.5) # Float64
6.25
julia> f(2) # Int64
4
julia> f(2, 3) # Int64, Int64
6
De acordo com o manual, uma função é um objeto que mapeia uma tupla de argumentos para um valor de retorno.
Normalmente pensamos nos argumentos da função em f(2, 3) como dois itens separados, mas o compilador os vê como uma única tupla.
Isso vale até para um único argumento (embora seja preciso adicionar uma vírgula para deixar isso mais claro):
julia> typeof( (2, 3) )
Tuple{Int64, Int64}
julia> typeof( (2,) )
Tuple{Int64}
Vendo a função como uma caixa-preta, podemos dizer que entra uma tupla e sai um valor de retorno. De forma mais formal, a tupla é mapeada para um valor de retorno.
Isso costuma ser chamado de "polimorfismo".
Esse é um tema importante na ciência da computação há várias décadas, e diferentes abordagens foram adotadas.
A abordagem de Julia é incomum, embora não seja única.
+ é uma função com 161 métodos, atualmente).... possa contornar isso parcialmente).dispatched com os argumentos dados e retorna um resultado.MethodError é lançado.Repare que Julia tem tipagem dinâmica, então o despacho de métodos acontece em tempo de execução.
A seção anterior teve muitas palavras, então vamos traduzir essas ideias para código. Por simplicidade, as funções abaixo têm apenas um argumento, mas não é incomum encontrar funções com dezenas de argumentos (como em pacotes de plotagem ou de aprendizado de máquina).
Comece definindo alguns tipos personalizados para representar formas geométricas.
julia> abstract type Shape end
julia> struct Circle <: Shape
radius::Float64
end
julia> struct Square <: Shape
side::Float64
end
julia> struct Rectangle <: Shape
length::Float64
width::Float64
end
Em seguida, podemos definir métodos para calcular a área de cada forma. Cada um recebe um argumento, de um tipo concreto e definido pelo usuário diferente em cada caso.
julia> area(c::Circle) = π * c.radius^2
area (generic function with 1 method)
julia> area(s::Square) = s.side^2
area (generic function with 2 methods)
julia> area(r::Rectangle) = r.length * r.width
area (generic function with 3 methods)
Agora podemos criar algumas formas e calcular as áreas, contando com o mecanismo de despacho de Julia para usar a fórmula correta em cada caso.
julia> circle = Circle(2.3)
Circle(2.3)
julia> area(circle) # call signature Tuple{Circle}
16.619025137490002
julia> square = Square(1.6)
Square(1.6)
julia> area(square) # call signature Tuple{Square}
2.5600000000000005
julia> rectangle = Rectangle(1.4, 2.1)
Rectangle(1.4, 2.1)
julia> area(rectangle) # call signature Tuple{Rectangle}
2.94
As assinaturas de métodos podem receber valores de tipo fictícios, mais ou menos como os tipos paramétricos.
A sintaxe é peculiar, exigindo uma cláusula where:
julia> same_type(v::Vector{T}, x::T) where {T} = true
same_type (generic function with 1 method)
julia> same_type([1, 2], 3)
true
julia> same_type([1, 2], "three")
ERROR: MethodError: no method matching same_type(::Vector{Int64}, ::String)
O exemplo acima exige que os dois argumentos usem o mesmo tipo T, seja ele qual for.
Podemos ser mais específicos impondo uma restrição a T:
julia> same_type(v::Vector{T}, x::T) where {T<:Number} = true
same_type (generic function with 2 methods)
Com esse método, apenas tipos numéricos idênticos retornam true.
É possível ter várias variáveis fictícias, separadas por vírgula na cláusula where.
julia> myfunc(a::S, b::T) where {S, T} = true
myfunc (generic function with 1 method)
O despacho múltiplo é muito flexível e tem alto desempenho em Julia, por isso é usado amplamente.
Como sugere o exemplo dos 161 métodos para +, quem programa em Julia busca definir métodos com tipos bem específicos.
Somar dois inteiros é bem diferente de somar dois números de ponto flutuante no nível do bytecode, e somar um inteiro a um número de ponto flutuante traz a complicação extra da promoção de tipos.
Quanto mais específico o tipo, mais otimização o compilador pode fazer: algo de importância marginal em um exercício simples no Exercism, mas vital para o desempenho nas grandes simulações numéricas para as quais Julia foi projetada.
Se você sabe quais entradas um método vai receber, conte para o compilador!
Neste exercício você vai implementar uma simulação de encontros. Isso vai te familiarizar com o básico de multiple dispatch, o principal paradigma de Julia.
For this exercise, all function definitions should be a single-line declaration with no if/else logic.
De modo geral, um encontro envolve uma entidade a que encontra outra entidade b e reage a ela.
encounter(a, b) = "$(name(a)) meets $(name(b)) and $(meets(a, b))."
A princípio, vamos simular o que acontece quando gatos e cachorros se encontram.
Primeiro, adicione um tipo abstrato Pet.
Depois, defina os tipos Cat e Dog como subtipos de Pet.
Cada um tem um único campo name.
Implemente uma função name() que retorna o nome de um animal de estimação.
julia> fido = Dog("Fido")
Dog("Fido")
julia> name(fido)
"Fido"
Implemente os métodos de meets() para os seguintes encontros:
julia> ginger = Cat("Ginger")
Cat("Ginger")
julia> meets(ginger, fido)
"hisses"
Implemente a função encounter() com um método, para relatar o que acontece quando duas entidades nomeadas se encontram.
julia> encounter(ginger, fido)
"Ginger meets Fido and hisses."
O que acontece se eles encontrarem um animal de estimação diferente durante o passeio, como um cavalo?
julia> bella = Horse("Bella")
Horse("Bella")
julia> encounter(fido, bella)
"Fido meets Bella and is cautious."
Existem muitas outras coisas que os animais de estimação podem encontrar e que não são animais de estimação: carros, humanos, plantas, desastres naturais, asteroides… E o que acontece então?
julia> colnago = Bicycle("Colnago")
Bicycle("Colnago")
julia> encounter(ginger, colnago)
"Ginger meets Colnago and runs away."
Existem muitos outros encontros que podem acontecer na nossa simulação. Um carro encontra um cachorro, dois elétrons se encontram e interagem…
É impossível prever todos os encontros de antemão, então vamos implementar um fallback genérico.
julia> γ1 = Photon("γ1")
Photon("γ1")
julia> γ2 = Photon("γ2")
Photon("γ2")
julia> encounter(γ1, γ2)
"γ1 meets γ2 and nothing happens."
# This is true, photons just pass through each other.
Crie sua conta no Exercism para aprender e dominar Julia com 35 conceitos128 exercícios e mentoria humana de verdade, tudo de graça.