Julia Language
Los tipos
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 denothing
. Podemos verificar queVoid.instance
(que es una sintaxis especial para recuperar el valor singleton de un tipo singleton) no esnothing
. - 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
yBase.SizeUnknown
son todos tipos de singleton.
- ¡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, detypeof(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)