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.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow