Buscar..


Sintaxis

  • mi tipo inmutable; campo; campo; fin
  • escriba MyType; campo; campo; fin

Observaciones

Los tipos son clave para el desempeño de Julia. Una idea importante para el rendimiento es la estabilidad del tipo , que se produce cuando el tipo que devuelve una función solo depende de los tipos, no de los valores, de sus argumentos.

Despacho de tipos

En Julia, puedes definir más de un método para cada función. Supongamos que definimos tres métodos de la misma función:

foo(x) = 1
foo(x::Number) = 2
foo(x::Int) = 3

Al decidir qué método usar (llamado despacho ), Julia elige el método más específico que coincida con los tipos de los argumentos:

julia> foo('one')
1

julia> foo(1.0)
2

julia> foo(1)
3

Esto facilita el polimorfismo . Por ejemplo, podemos crear fácilmente una lista enlazada definiendo dos tipos inmutables, denominados Nil y Cons . Estos nombres se usan tradicionalmente para describir una lista vacía y una lista no vacía, respectivamente.

abstract LinkedList
immutable Nil <: LinkedList end
immutable Cons <: LinkedList
    first
    rest::LinkedList
end

Representaremos la lista vacía con Nil() y cualquier otra lista con Cons(first, rest) , donde first es el primer elemento de la lista vinculada y el rest es la lista vinculada que consta de todos los elementos restantes. Por ejemplo, la lista [1, 2, 3] se representará como

julia> Cons(1, Cons(2, Cons(3, Nil())))
Cons(1,Cons(2,Cons(3,Nil())))

¿Está la lista vacía?

Supongamos que deseamos ampliar la función isempty la biblioteca estándar, que funciona en una variedad de colecciones diferentes:

julia> methods(isempty)
# 29 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394
...

Simplemente podemos utilizar la sintaxis de despacho de la función y definir dos métodos adicionales de isempty . Como esta función es del módulo Base , debemos calificarla como Base.isempty para poder extenderla.

Base.isempty(::Nil) = true
Base.isempty(::Cons) = false

Aquí, no necesitamos los valores de los argumentos para determinar si la lista está vacía. Simplemente el tipo solo basta para calcular esa información. Julia nos permite omitir los nombres de los argumentos, manteniendo solo su tipo de anotación, si no necesitamos usar sus valores.

Podemos probar que nuestros métodos isempty funcionan:

julia> using Base.Test

julia> @test isempty(Nil())
Test Passed
  Expression: isempty(Nil())

julia> @test !isempty(Cons(1, Cons(2, Cons(3, Nil()))))
Test Passed
  Expression: !(isempty(Cons(1,Cons(2,Cons(3,Nil())))))

y de hecho, el número de métodos para la isempty ha aumentado en 2 :

julia> methods(isempty)
# 31 methods for generic function "isempty":
isempty(v::SimpleVector) at essentials.jl:180
isempty(m::Base.MethodList) at reflection.jl:394

Claramente, determinar si una lista vinculada está vacía o no es un ejemplo trivial. Pero conduce a algo más interesante:

¿Cuánto dura la lista?

La función de length de la biblioteca estándar nos da la longitud de una colección o ciertos iterables . Hay muchas formas de implementar la length de una lista enlazada. En particular, el uso de un while de bucle es eficaz memoria de Julia probablemente más rápido y más. Pero debe evitarse la optimización prematura , por lo que supongamos por un segundo que nuestra lista enlazada no necesita ser eficiente. ¿Cuál es la forma más sencilla de escribir una función de length ?

Base.length(::Nil) = 0
Base.length(xs::Cons) = 1 + length(xs.rest)

La primera definición es sencilla: una lista vacía tiene una longitud de 0 . La segunda definición también es fácil de leer: para contar la longitud de una lista, contamos el primer elemento y luego contamos la longitud del resto de la lista. Podemos probar este método de manera similar a como probamos isempty :

julia> @test length(Nil()) == 0
Test Passed
  Expression: length(Nil()) == 0
   Evaluated: 0 == 0

julia> @test length(Cons(1, Cons(2, Cons(3, Nil())))) == 3
Test Passed
  Expression: length(Cons(1,Cons(2,Cons(3,Nil())))) == 3
   Evaluated: 3 == 3

Próximos pasos

Este ejemplo de juguete está bastante lejos de implementar toda la funcionalidad que se desearía en una lista vinculada. Falta, por ejemplo, la interfaz de iteración. Sin embargo, ilustra cómo se puede utilizar el envío para escribir código corto y claro.

Tipos inmutables

El tipo compuesto más simple es un tipo inmutable. Las instancias de tipos inmutables, como las tuplas , son valores. Sus campos no se pueden cambiar después de que se crean. En muchos sentidos, un tipo inmutable es como una Tuple con nombres para el tipo en sí y para cada campo.

Tipos singleton

Los tipos compuestos, por definición, contienen una serie de tipos más simples. En Julia, este número puede ser cero; es decir, un tipo inmutable no puede contener campos. Esto es comparable a la tupla vacía () .

¿Por qué podría ser útil? Tales tipos inmutables se conocen como "tipos singleton", ya que solo una instancia de ellos podría existir. Los valores de estos tipos se conocen como "valores singleton". La Base biblioteca estándar contiene muchos de estos tipos de singleton. Aquí hay una breve lista:

  • Void , el tipo de nothing . Podemos verificar que Void.instance (que es una sintaxis especial para recuperar el valor singleton de un tipo singleton) no es nothing .
  • Cualquier tipo de medio, como MIME"text/plain" , es un tipo singleton con una sola instancia, MIME("text/plain") .
  • Irrational{:π} , Irrational{:e} , Irrational{:φ} y tipos similares son tipos singleton, y sus instancias singleton son los valores irracionales π = 3.1415926535897... , etc.
  • Los rasgos de tamaño del iterador Base.HasLength , Base.HasShape , Base.IsInfinite y Base.SizeUnknown son todos tipos de singleton.
0.5.0
  • ¡En la versión 0.5 y posteriores, cada función es una instancia de un tipo de singleton! Como cualquier otro valor de singleton, podemos recuperar la función sin , por ejemplo, de typeof(sin).instance .

Debido a que no contienen nada, los tipos singleton son increíblemente ligeros, y con frecuencia pueden ser optimizados por el compilador para que no tengan una sobrecarga de tiempo de ejecución. Por lo tanto, son perfectos para rasgos, valores de etiquetas especiales y para funciones como las que uno quisiera especializarse.

Para definir un tipo de singleton,

julia> immutable MySingleton end

Para definir la impresión personalizada para el tipo de singleton,

julia> Base.show(io::IO, ::MySingleton) = print(io, "sing")

Para acceder a la instancia de singleton,

julia> MySingleton.instance
MySingleton()

A menudo, uno asigna esto a una constante:

julia> const sing = MySingleton.instance
MySingleton()

Tipos de envoltura

Si los tipos inmutables de campo cero son interesantes y útiles, quizás los tipos inmutables de un campo sean aún más útiles. Tales tipos se denominan comúnmente "tipos de envoltura" porque envuelven algunos datos subyacentes, proporcionando una interfaz alternativa a dichos datos. Un ejemplo de un tipo de envoltorio en Base es String . Definiremos un tipo similar a String , llamado MyString . Este tipo estará respaldado por un vector ( matriz unidimensional) de bytes ( UInt8 ).

Primero, la definición del tipo en sí y algunas demostraciones personalizadas:

immutable MyString <: AbstractString
    data::Vector{UInt8}
end

function Base.show(io::IO, s::MyString)
    print(io, "MyString: ")
    write(io, s.data)
    return
end

¡Ahora nuestro tipo MyString está listo para usar! Podemos proporcionarle algunos datos UTF-8 sin procesar, y se muestra como nos gusta:

julia> MyString([0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x57,0x6f,0x72,0x6c,0x64,0x21])
MyString: Hello, World!

Obviamente, este tipo de cadena necesita mucho trabajo antes de que sea tan utilizable como el tipo Base.String .

Tipos compuestos verdaderos

Quizás más comúnmente, muchos tipos inmutables contienen más de un campo. Un ejemplo es el estándar de biblioteca Rational{T} tipo, que contiene dos fieds: a num campo para el numerador y un den campo para el denominador. Es bastante sencillo emular este tipo de diseño:

immutable MyRational{T}
    num::T
    den::T
    MyRational(n, d) = (g = gcd(n, d); new(n÷g, d÷g))
end
MyRational{T}(n::T, d::T) = MyRational{T}(n, d)

Hemos implementado con éxito un constructor que simplifica nuestros números racionales:

julia> MyRational(10, 6)
MyRational{Int64}(5,3)


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