Julia Language
Funciones
Buscar..
Sintaxis
- f (n) = ...
- función f (n) ... fin
- n :: Tipo
- x -> ...
- f (n) do ... end
Observaciones
Aparte de las funciones genéricas (que son las más comunes), también hay funciones incorporadas. Tales funciones incluyen is
, isa
, typeof
, throw
y funciones similares. Las funciones incorporadas normalmente se implementan en C en lugar de Julia, por lo que no pueden especializarse en tipos de argumentos para el envío.
Cuadrar un número
Esta es la sintaxis más fácil para definir una función:
square(n) = n * n
Para llamar a una función, use corchetes (sin espacios entre ellos):
julia> square(10)
100
Las funciones son objetos en Julia, y podemos mostrarlas en el REPL como con cualquier otro objeto:
julia> square
square (generic function with 1 method)
Todas las funciones de Julia son genéricas (también conocidas como polimórficas ) por defecto. Nuestra función square
funciona igual de bien con valores de punto flotante:
julia> square(2.5)
6.25
... o incluso matrices :
julia> square([2 4
2 1])
2×2 Array{Int64,2}:
12 12
6 9
Funciones recursivas
Recursion simple
Usando la recursión y el operador condicional ternario , podemos crear una implementación alternativa de la función factorial
incorporada:
myfactorial(n) = n == 0 ? 1 : n * myfactorial(n - 1)
Uso:
julia> myfactorial(10)
3628800
Trabajando con arboles
Las funciones recursivas son a menudo más útiles en estructuras de datos, especialmente en estructuras de datos de árbol. Como las expresiones en Julia son estructuras de árbol, la recursión puede ser bastante útil para la metaprogramación . Por ejemplo, la siguiente función reúne un conjunto de todas las cabezas utilizadas en una expresión.
heads(ex::Expr) = reduce(∪, Set((ex.head,)), (heads(a) for a in ex.args))
heads(::Any) = Set{Symbol}()
Podemos comprobar que nuestra función funciona según lo previsto:
julia> heads(:(7 + 4x > 1 > A[0]))
Set(Symbol[:comparison,:ref,:call])
Esta función es compacta y utiliza una variedad de técnicas más avanzadas, como la función de reduce
orden superior , el tipo de datos Set
y las expresiones del generador.
Introducción al Despacho
Podemos usar la ::
sintaxis para enviar el tipo de argumento.
describe(n::Integer) = "integer $n"
describe(n::AbstractFloat) = "floating point $n"
Uso:
julia> describe(10)
"integer 10"
julia> describe(1.0)
"floating point 1.0"
A diferencia de muchos idiomas, que normalmente proporcionan un envío múltiple estático o un envío único dinámico, Julia tiene un envío múltiple dinámico completo. Es decir, las funciones pueden ser especializadas para más de un argumento. Esto resulta útil cuando se definen métodos especializados para operaciones en ciertos tipos y métodos de reserva para otros tipos.
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"
Uso:
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"
Argumentos opcionales
Julia permite que las funciones tomen argumentos opcionales. Detrás de escena, esto se implementa como otro caso especial de despacho múltiple. Por ejemplo, resolvamos el problema popular de Fizz Buzz . Por defecto, lo haremos para números en el rango de 1:10
, pero permitiremos un valor diferente si es necesario. También permitiremos que se usen diferentes frases para Fizz
o 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
Si inspeccionamos fizzbuzz
en el REPL, dice que hay cuatro métodos. Se creó un método para cada combinación de argumentos permitidos.
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
Podemos verificar que nuestros valores predeterminados se utilizan cuando no se proporcionan parámetros:
julia> fizzbuzz()
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
pero que los parámetros opcionales son aceptados y respetados si los proveemos:
julia> fizzbuzz(5:8, "fuzz", "bizz")
bizz
fuzz
7
8
Despacho paramétrico
Es frecuente que una función deba enviarse en tipos paramétricos, como Vector{T}
o Dict{K,V}
, pero los parámetros de tipo no son fijos. Este caso puede tratarse mediante el envío paramétrico:
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"
Uno puede tener la tentación de simplemente escribir xs::Vector{Number}
. Pero esto solo funciona para objetos cuyo tipo es explícitamente Vector{Number}
:
julia> isa(Number[1, 2], Vector{Number})
true
julia> isa(Int[1, 2], Vector{Number})
false
Esto se debe a la invariancia paramétrica : el objeto Int[1, 2]
no es un Vector{Number}
, porque solo puede contener Int
s, mientras que un Vector{Number}
podría contener cualquier tipo de número.
Escribir código genérico
El envío es una característica increíblemente poderosa, pero a menudo es mejor escribir código genérico que funcione para todos los tipos, en lugar de un código especializado para cada tipo. Escribir código genérico evita la duplicación de código.
Por ejemplo, aquí está el código para calcular la suma de cuadrados de un vector de enteros:
function sumsq(v::Vector{Int})
s = 0
for x in v
s += x ^ 2
end
s
end
Pero este código solo funciona para un vector de Int
s. No funcionará en un UnitRange
:
julia> sumsq(1:10)
ERROR: MethodError: no method matching sumsq(::UnitRange{Int64})
Closest candidates are:
sumsq(::Array{Int64,1}) at REPL[8]:2
No funcionará en un 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
Una mejor manera de escribir esta función sumsq
debería ser
function sumsq(v::AbstractVector)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Esto funcionará en los dos casos mencionados anteriormente. Pero hay algunas colecciones de las que podríamos querer sumar los cuadrados que no son vectores, en ningún sentido. Por ejemplo,
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
Demuestra que no podemos sumar los cuadrados de un perezoso iterable .
Una implementación aún más genérica es simplemente
function sumsq(v)
s = zero(eltype(v))
for x in v
s += x ^ 2
end
s
end
Que funciona en todos los casos:
julia> sumsq(take(countfrom(1), 100))
338350
Este es el código de Julia más idiomático, y puede manejar todo tipo de situaciones. En algunos otros idiomas, la eliminación de anotaciones de tipo puede afectar el rendimiento, pero ese no es el caso en Julia; Sólo la estabilidad del tipo es importante para el rendimiento.
Factorial imperativo
Hay una sintaxis de formato largo disponible para definir funciones multilínea. Esto puede ser útil cuando usamos estructuras imperativas como los bucles. Se devuelve la expresión en posición cola. Por ejemplo, la siguiente función utiliza un bucle for
para calcular el factorial de algún entero n
:
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
fact
end
Uso:
julia> myfactorial(10)
3628800
En funciones más largas, es común ver la declaración de return
utilizada. La declaración de return
no es necesaria en la posición de cola, pero a veces todavía se utiliza para mayor claridad. Por ejemplo, otra forma de escribir la función anterior sería
function myfactorial(n)
fact = one(n)
for m in 1:n
fact *= m
end
return fact
end
que es idéntico en comportamiento a la función anterior.
Funciones anonimas
Sintaxis de flecha
Se pueden crear funciones anónimas usando la sintaxis ->
. Esto es útil para pasar funciones a funciones de orden superior , como la función de map
. La siguiente función calcula el cuadrado de cada número en una matriz A
squareall(A) = map(x -> x ^ 2, A)
Un ejemplo del uso de esta función:
julia> squareall(1:10)
10-element Array{Int64,1}:
1
4
9
16
25
36
49
64
81
100
Sintaxis multilínea
Las funciones anónimas multilínea se pueden crear utilizando la sintaxis de la function
. Por ejemplo, el siguiente ejemplo calcula los factoriales de los primeros n
números, pero utilizando una función anónima en lugar del factorial
incorporado.
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
Hacer bloque de sintaxis
Debido a que es tan común pasar una función anónima como el primer argumento a una función, hay una sintaxis de bloque do
. La sintaxis
map(A) do x
x ^ 2
end
es equivalente a
map(x -> x ^ 2, A)
pero el primero puede ser más claro en muchas situaciones, especialmente si se realizan muchos cálculos en la función anónima. do
sintaxis de bloques es especialmente útil para la entrada y salida de archivos por razones de administración de recursos.