Leyendo tipos
En la sección Lo esencial del lenguaje revisamos varios ejemplos interactivos para darnos una intuición general del lenguaje. Ahora vamos a volver a hacer lo mismo, pero con una nueva pregunta en mente. ¿Qué tipo de valor es este?
Valores primitivos y listas
Ingresemos algunas expresiones simples y veamos qué pasa:
Haz clic sobre esta caja negra ⬆️ y verás un cursor parpadeando. Escribe 3.1415
y apreta ENTER. Debería aparecer 3.1415
seguido del tipo Float
.
Okay, but what is going on here exactly? Each entry shows value along with what type of value it happens to be. You can read these examples out loud like this: Okay, pero ¿qué está ocurriendo aquí, exactamente? Cada fila muestra un valor junto con el tipo del valor al que corresponde. Puedes leer estos valores de esta forma:
- El valor
"hello"
es unString
. - El valor
False
es unBool
. - El valor
3
es unInt
. - El valor
3.1415
es unFloat
.
El es capaz de reconocer el tipo de cualquier valor que ingreses. Veamos qué pasa con listas:
Puedes leer estos tipos de esta forma:
- Tenemos una
List
rellena con valoresString
. - Tenemos una
List
rellena con valoresFloat
.
El tipo es una descripción general del valor particular que estamos viendo.
Funciones
Veamos el tipo de algunas funciones:
Ingresa round
o sqrt
para ver otros tipos de funciones ⬆️
La función String.length
tiene el tipo String -> Int
. Esto significa que tiene que recibir un argumento String
, y que definitivamente retornará un valor Int
. Probemos pasarle un argumento:
Tenemos una función String -> Int
y le pasamos un argumento String
. Esto resulta en un Int
.
¿Y qué pasa cuando no le pasas un String
? Prueba escribir String.length [1,2,3]
o String.length True
y ve lo que ocurre ⬆️
Vas a darte cuenta de que una función String -> Int
tiene que recibir un argumento String
.
Nota: Las funciones que reciben múltiples argumentos se escriben con varias flechas. Por ejemplo, esta es una función que recibe dos argumentos:
[ { "input": "String.repeat", "value": "\u001b[36m<function>\u001b[0m", "type_": "Int -> String -> String" } ]Si le das los dos argumentos
String.repeat 3 "ha"
, el resultado será"hahaha"
. Puedes considerar->
como una forma rara de separar los argumentos, pero explico su real significado aquí. ¡Es bastante interesante!
Anotaciones de tipo
Hasta ahora hemos permitido que Elm determine los tipos, pero también podemos escribir una anotación de tipo en la lina justo arriba de una definición. Es decir que en nuestro código podemos escribir cosas como estas:
half : Float -> Float
half n =
n / 2
-- half 256 == 128
-- half "3" -- error!
hypotenuse : Float -> Float -> Float
hypotenuse a b =
sqrt (a ^ 2 + b ^ 2)
-- hypotenuse 3 4 == 5
-- hypotenuse 5 12 == 13
checkPower : Int -> String
checkPower powerLevel =
if powerLevel > 9000 then
"It's over 9000!!!"
else
"Meh"
-- checkPower 9001 == "It's over 9000!!!"
-- checkPower True -- error!
No es necesario añadir anotaciones de tipo, pero definitivamente te lo recomiendo. Estos son algunos beneficios:
- Calidad de los mensajes de error — Cuando escribes una anotación de tipo, le estás contando al compilador tu intención. Tu implementación puede que tenga errores, y el compilador puede comparar eso con tu intención. “Dijiste que el argumento
powerLevel
eraInt
, pero está siendo usado comoString
”. - Documentación — Cuando vuelvas a enfrentarte a tu código más tarde (o cuando un colega lo haga por primera vez) va a ser muy útil ver exactamente lo que recibe y devuelve una función sin tener que leer la implementación en detalle.
Pero la gente puede cometer errores al escribir anotaciones de tipo, así que ¿qué pasa si la anotación no coincide con la implementación? El compilador determina todos los tipos por su cuenta y confirma que tu anotación coincide con la respuesta real. En otras palabras, el compilador siempre verificará que todas las anotaciones que escribas estén correctas. Así tendrás mejores mensajes de error y además tu documentación se mantendrá siempre al día.
Variables de tipo
A medida que revises más código Elm, te irás dando cuenta de que existen anotaciones de tipo con letras en minúscula. Un ejemplo común es el de la función List.length
:
Fíjate en esa a
minúscula en el tipo. Esto se llama una variable de tipo. Puede cambiar según cómo se use List.length
:
Sólo necesitamos el largo de la lista, así que no nos importa lo que contenga la lista. Así que el la variable de tipo a
nos dice que puede calzar con cualquier tipo. Veamos otro ejemplo común:
Otra vez, la variable de tipo a
puede cambiar según cómo List.reverse
sea usada. Pero en este caso, tenemos una a
en el argumento y en el resultado. Esto significa que si le das una List Int
deberás recibir una List Int
también. Una vez que decidimos lo que es esa a
, será lo mismo después también.
Nota: Las variables de tipo deben empezar con una letra minúscula, pero pueden ser palabras completas. Podemos escribir el tipo de
List.length
comoList value -> Int
y podríamos escribir el tipo deList.reverse
comoList element -> List element
. Funciona siempre y cuando comiencen con una letra minúscula. Las variables de tipoa
yb
son usadas por convención en muchos lugares, pero algunas anotaciones de tipo quedan mejor con nombres más específicos.
Variables limitadas de tipo
There is a special variant of type variables in Elm called constrained type variables. The most common example is the number
type. The negate
function uses it:
Hay una variante especial de las variables de tipo en Elm que se llama variables limitadas de tipo. El ejemplo más común es el del tipo number
. La función negate
la usa:
Prueba escribir expresiones como negate 3.1415
o negate (round 3.1415)
o negate "hi"
⬆️
Normalmente, las variables de tipo pueden rellenarse con cualquier cosa, pero number
sólo puede rellenarse con valores Int
y Float
. Limita las posibilidades.
La lista completa de variables limitadas de tipo es:
number
permiteInt
yFloat
appendable
permiteString
yList a
comparable
permiteInt
,Float
,Char
,String
, y listas o tuplas de valorescomparable
compappend
permiteString
yList comparable
Estas variables limitadas de tipo existen para que ciertos operadores como (+)
y (<)
puedan ser un poco más flexibles.
Ya cubrimos bastante bien los tipos de valores y funciones, pero ¿cómo se ve esto cuando empezamos a necesitar estructuras de datos más complejas?