Aquí, "valores", "tipos" y "tipos" tienen significados formales, por lo que considerar su uso común en inglés o analogías para clasificar automóviles solo lo llevará hasta cierto punto.
Mi respuesta se refiere a los significados formales de estos términos en el contexto de Haskell específicamente; Estos significados se basan en (aunque no son realmente idénticos a) los significados utilizados en la "teoría de tipos" matemática / CS. Entonces, esta no será una muy buena respuesta de "informática", pero debería servir como una muy buena respuesta de Haskell.
En Haskell (y otros lenguajes), resulta útil asignar un tipo a una expresión de programa que describe la clase de valores que se permite que tenga la expresión. Asumo aquí que has visto suficientes ejemplos para entender por qué sería útil saber que en la expresión sqrt (a**2 + b**2)
, las variables a
y b
siempre serán valores de tipo Double
y no, digamos, String
y Bool
respectivamente. Básicamente, tener tipos nos ayuda a escribir expresiones / programas que funcionarán correctamente en una amplia gama de valores .
Ahora, algo que quizás no se haya dado cuenta es que los tipos de Haskell, como los que aparecen en las firmas de tipos:
fmap :: Functor f => (a -> b) -> f a -> f b
en realidad están escritos en un sublenguaje de Haskell de tipo tipo. El texto del programa Functor f => (a -> b) -> f a -> f b
es, literalmente, una expresión de tipo escrita en este sublenguaje. El sublenguaje incluye operadores (p. Ej., ->
Es un operador infijo asociativo correcto en este idioma), variables (p. Ej f
. a
, Y b
) y "aplicación" de una expresión de tipo a otra (p. Ej., f a
Se f
aplica a a
).
¿Mencioné cómo fue útil en muchos idiomas asignar tipos a expresiones de programa para describir clases de valores de expresión? Bueno, en este sublenguaje de nivel de tipo, las expresiones evalúan los tipos (en lugar de los valores ) y termina siendo útil asignar tipos a expresiones de tipo para describir las clases de tipos que se les permite representar. Básicamente, tener clases nos ayuda a escribir expresiones de tipo que funcionarán correctamente en una amplia gama de tipos .
Entonces, los valores son para tipos como los tipos son para tipos , y los tipos nos ayudan a escribir programas de nivel de valor , mientras que los tipos nos ayudan a escribir programas de nivel de tipo .
¿Cómo son estos tipos ? Bueno, considere la firma de tipo:
id :: a -> a
Si la expresión de tipo a -> a
es válida, ¿qué tipo de tipos deberíamos permitir a
que sea la variable ? Bueno, las expresiones tipográficas:
Int -> Int
Bool -> Bool
parece válido, por lo que los tipos Int
y Bool
obviamente son del tipo correcto . Pero tipos aún más complicados como:
[Double] -> [Double]
Maybe [(Double,Int)] -> Maybe [(Double,Int)]
parece válido De hecho, ya que deberíamos poder invocar id
funciones, incluso:
(a -> a) -> (a -> a)
Se ve bien. Así, Int
, Bool
, [Double]
, Maybe [(Double,Int)]
, y a -> a
toda la mirada como tipos de la derecha tipo .
En otras palabras, parece que solo hay un tipo , llamémoslo *
como un comodín de Unix, y cada tipo tiene el mismo tipo *
, final de la historia.
¿Correcto?
Bueno, no del todo. Resulta que Maybe
, en sí mismo, es una expresión de tipo tan válida como Maybe Int
(de la misma manera sqrt
, en sí misma, es una expresión de valor tan válida como sqrt 25
). Sin embargo , la siguiente expresión de tipo no es válida:
Maybe -> Maybe
Porque, si bien Maybe
es una expresión de tipo, no representa el tipo de tipo que puede tener valores. Entonces, así es como debemos definir *
: es el tipo de tipos que tienen valores; incluye tipos "completos" como Double
o Maybe [(Double,Int)]
pero excluye tipos incompletos y sin valor como Either String
. Para simplificar, llamaré a estos tipos completos de tipo *
"tipos concretos", aunque esta terminología no es universal, y "tipos concretos" podrían significar algo muy diferente a, por ejemplo, un programador de C ++.
Ahora, en la expresión de tipo a -> a
, siempre y cuando el tipo a
tiene clase *
(la clase de tipos concretos), el resultado de la expresión de tipo a -> a
será también tienen clase *
(es decir, la clase de tipos concretos).
Entonces, ¿qué tipo de tipo es Maybe
? Bueno, Maybe
se puede aplicar a un tipo de concreto para producir otro tipo de concreto. Entonces, se Maybe
parece un poco a una función de nivel de tipo que toma un tipo de tipo *
y devuelve un tipo de tipo *
. Si tuviéramos una función de nivel de valor que tomara un valor de tipo Int
y devolviera un valor de tipo Int
, le daríamos una firma de tipoInt -> Int
, así que por analogía deberíamos dar Maybe
una firma amable* -> *
. GHCi está de acuerdo:
> :kind Maybe
Maybe :: * -> *
Volviendo a:
fmap :: Functor f => (a -> b) -> f a -> f b
En este tipo de firma, variable f
tiene kind * -> *
y variables a
y b
have kind *
; el operador incorporado ->
tiene kind * -> * -> *
(toma un tipo de tipo *
a la izquierda y uno a la derecha y devuelve un tipo también de tipo *
). A partir de esto y de las reglas de inferencia amable, puede deducir que a -> b
es un tipo válido con tipo *
, f a
y f b
también son tipos válidos con tipo *
, y (a -> b) -> f a -> f b
es un tipo de tipo válido *
.
En otras palabras, el compilador puede "verificar el tipo" de la expresión de tipo (a -> b) -> f a -> f b
para verificar que sea válida para las variables de tipo del tipo correcto de la misma manera que "comprueba de tipo" sqrt (a**2 + b**2)
para verificar que sea válida para las variables del tipo correcto.
La razón para usar términos separados para "tipos" versus "tipos" (es decir, no hablar de los "tipos de tipos") es principalmente para evitar confusiones. Los tipos anteriores se ven muy diferentes de los tipos y, al menos al principio, parecen comportarse de manera bastante diferente. (Por ejemplo, lleva algún tiempo comprender la idea de que cada tipo "normal" tiene el mismo tipo *
y el tipo no lo a -> b
es ).*
* -> *
Algo de esto también es histórico. A medida que GHC Haskell ha evolucionado, las distinciones entre valores, tipos y clases han comenzado a desdibujarse. En estos días, los valores se pueden "promocionar" en tipos, y los tipos y clases son realmente lo mismo. Por lo tanto, en Haskell moderno, los valores tienen tipos y tipos ARE (casi), y los tipos de tipos son simplemente más tipos.
@ user21820 pidió una explicación adicional de "los tipos y tipos son realmente lo mismo". Para ser un poco más claro, en el GHC Haskell moderno (desde la versión 8.0.1, creo), los tipos y clases se tratan de manera uniforme en la mayoría del código del compilador. El compilador hace un esfuerzo en los mensajes de error para distinguir entre "tipos" y "tipos", dependiendo de si se queja del tipo de un valor o del tipo de un tipo, respectivamente.
Además, si no hay extensiones habilitadas, se pueden distinguir fácilmente en el lenguaje de superficie. Por ejemplo, los tipos (de valores) tienen una representación en la sintaxis (por ejemplo, en las firmas de tipos), pero los tipos (de tipos) son, creo, completamente implícitos, y no hay una sintaxis explícita donde aparecen.
Pero, si activa las extensiones apropiadas, la distinción entre tipos y clases desaparece en gran medida. Por ejemplo:
{-# LANGUAGE GADTs, TypeInType #-}
data Foo where
Bar :: Bool -> * -> Foo
Aquí, Bar
es (tanto un valor como) un tipo. Como tipo, su tipo es Bool -> * -> Foo
, que es una función de nivel de tipo que toma un tipo de tipo Bool
(que es un tipo, pero también un tipo) y un tipo de tipo *
y produce un tipo de tipo Foo
. Asi que:
type MyBar = Bar True Int
correctamente verificaciones de tipo.
Como @AndrejBauer explica en su respuesta, esta incapacidad para distinguir entre tipos y clases no es segura: tener un tipo / tipo *
cuyo tipo / tipo es en sí mismo (que es el caso en Haskell moderno) conduce a paradojas. Sin embargo, el sistema de tipos de Haskell ya está lleno de paradojas debido a la no terminación, por lo que no se considera un gran problema.