Hasta ahora, el temario no ha dicho mucho sobre los tipos, pero está claro que existen en Julia:
julia> vals = (42, 4.3, π, "hello", 'Q')
(42, 4.3, π, "hello", 'Q')
julia> typeof(vals)
Tuple{Int64, Float64, Irrational{:π}, String, Char}
Nunca especificamos los tipos, pero Julia los asignó de todos modos.
types, que son centrales en su diseño.Type Inference.El compilador JIT revisará (todo) el código, verá cómo se usa una variable e inferirá un tipo predeterminado adecuado y compatible con ese uso.
Confiar en la inferencia de tipos está bien para resolver ejercicios sencillos de tutorial, pero para programas más grandes es probable que necesites un control más preciso.
En la mayoría de los procesadores modernos, un entero tiene como valor predeterminado Int64.
Vimos en el concepto Numbers que un valor se puede convertir a un tipo particular que no es el predeterminado.
julia> x = Int16(42)
42
julia> typeof(x)
Int16
Sin embargo, a la variable x todavía se le puede asignar un tipo diferente:
julia> x = "changed"
"changed"
julia> typeof(x)
String
Esto es la type instability, que es:
En cambio, podemos establecer el tipo de x usando el operador :::
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> y = "changed"
ERROR: MethodError: Cannot `convert` an object of type String to an object of type Int16
The function `convert` exists, but no method is defined for this combination of argument types.
Ahora y es, y siempre será, de tipo Int16.
Así, el compilador sabe cuántos bytes reservar para ella y puede optimizar el resto del código para apoyarse en un tipo estable.
En esto, la variable es más o menos similar a las de un lenguaje de tipado estático como C.
Int64, Int16, String, Char: ¿de dónde «vienen» estos tipos?
En muchos lenguajes orientados a objetos (OO), cada tipo es una clase, las subclases los organizan en una jerarquía de clases y los métodos de clase definen los comportamientos.
Java y Ruby son ejemplos obvios de este patrón, pero incluso Python es similar internamente.
Julia no tiene clases.
La razón documentada es que las características de OO interfieren con el compilador JIT y perjudican el rendimiento en tiempo de ejecución.
Y sin embargo, mira este código:
julia> y::Int16 = 42
42
julia> typeof(y)
Int16
julia> supertype(Int16)
Signed
julia> supertypes(Int16)
(Int16, Signed, Integer, Real, Number, Any)
Añadiendo algunos detalles:
Int16 es un tipo, y podemos crear variables de este tipo.Int16 es un subtipo de Signed, y la función supertype() nos lo muestra.Integer, Real y Number hasta Any en la cima, y subtypes() nos listará esta rama de la jerarquía.Todas las ramas terminan en Any, que es único por ser su propio supertipo.
julia> supertypes(String)
(String, AbstractString, Any)
julia> supertype(Any)
Any
Así que Julia no tiene una jerarquía de class, pero sí tiene una jerarquía de type.
Con el tiempo, intentaremos desentrañar cómo funciona esto, pero antes hay mucho más por explorar.
Podemos usar typeof() para comprobar la igualdad de la forma habitual.
julia> typeof(11)
Int64
julia> typeof(11) == Int64
true
julia> typeof(11) == Number
false
La igualdad de tipos debe ser exacta, ya que esta forma de comparación no entiende la jerarquía de tipos.
De forma más flexible, isa nos dirá si un valor tiene el mismo tipo que un comparador o un subtipo de este.
Se puede usar tanto en forma de función como de infijo.
julia> 12 isa Int64
true
julia> 12 isa Number
true
julia> isa(12, Number)
true
julia> 12 isa String
false
Ten en cuenta que isa espera un value a la izquierda, no un type.
Intentar comparar dos tipos de esta forma dará resultados inesperados.
El operador correcto es <:, que veremos mucho más en conceptos futuros.
julia> Int64 isa Number ## Don't do this!
false
julia> Int64 <: Number
true
Vimos que la jerarquía de tipos forma una estructura de árbol (en el sentido de las ciencias de la computación, con la raíz arriba).
Cada elemento del árbol es un node, y estos se pueden dividir en categorías:
abstract.concrete.julia> subtypes(Integer) # an abstract type
3-element Vector{Any}:
Bool
Signed
Unsigned
julia> subtypes(Int64) # a concrete type
Type[]
Esta es una distinción importante, porque solo los tipos concretos se pueden instantiated como variables.
julia> a::Int16 = 42
42
julia> typeof(a)
Int16
julia> b::Integer = 42
42
julia> typeof(b)
Int64
Ten en cuenta que intentar usar un tipo abstracto no da ningún mensaje de error (en este caso), pero el compilador crea un tipo concreto adecuado: Int64 en lugar de Integer.
En este ejercicio, vas a hacer un poco de ingeniería de datos al preprocesar algunos datos de calificaciones. Una escuela con muchos, muchos, muchos (¿demasiados?) estudiantes está tratando de hacer un análisis de datos de las calificaciones que reciben los estudiantes y quiere hacerlo de manera eficiente, así que la idea es minimizar y organizar los datos.
La escala de calificaciones suele ser de 0 a 10, así que lo más eficiente es usar un tipo de dato con solo ocho bits por calificación.
UInt8.Int8.Una vez que hayas implementado la función para convertir las calificaciones, vas a tener que escribir otra para manejar las colecciones que las contienen, degradando las calificaciones y devolviéndolas en orden descendente.
Vectors ordenados de forma ascendente para guardar sus calificaciones, porque son meticulosos con sus datos.Sets, que no están ordenados, para guardar sus calificaciones, porque son un poco más perezosos.En ambas funciones, vas a tener que manejar los argumentos no válidos lanzando un MethodError.
El manejo de excepciones se cubrirá en un concepto posterior, así que para efectos de este ejercicio puedes usar la siguiente sintaxis:
throw(MethodError(f, args))
Donde f es la función y args es una tupla con los argumentos que se pasan a la función.
Con la función demote(n), f es demote y args es (n,):
throw(MethodError(demote, (n,)))
Implementa el método demote(n).
Float64, debe redondear hacia arriba al número entero más cercano y devolver un dato de tipo UInt8.Integer arbitrario, debe devolver el mismo entero en un dato de tipo Int8.julia> demote(4.2)::UInt8
5
julia> demote(4)::Int8
4
julia> demote("hi")
MethodError: no method matching demote(::String) #output truncated
Implementa el método preprocess(coll).
Vector, debe degradar todos los números e invertir el vector.Set, debe degradar todos los números y devolver un vector ordenado de forma descendente.julia> preprocess([1, 2, 3])
3-element Vector{Int8}:
3
2
1
julia> preprocess(Set([2.2, 5.8, 3.4]))
3-element Vector{UInt8}:
6
4
3
julia> preprocess(42)
MethodError: no method matching preprocess(::Int64) #output truncated
Regístrate en Exercism para aprender y dominar Julia con 35 conceptos128 ejercicios y mentoría humana real, todo gratis.