Estoy empezando a entender cómo forall
se usa la palabra clave en los llamados "tipos existenciales" como este:
data ShowBox = forall s. Show s => SB s
Sin embargo, esto es solo un subconjunto de cómo forall
se usa y simplemente no puedo entender cómo se usa en cosas como esta:
runST :: forall a. (forall s. ST s a) -> a
O explicando por qué son diferentes:
foo :: (forall a. a -> a) -> (Char, Bool)
bar :: forall a. ((a -> a) -> (Char, Bool))
O todo el RankNTypes
asunto ...
Tiendo a preferir un inglés claro y sin jerga en lugar de los tipos de lenguaje que son normales en entornos académicos. La mayoría de las explicaciones que intento leer sobre esto (las que puedo encontrar a través de los motores de búsqueda) tienen estos problemas:
- Están incompletos Explican una parte del uso de esta palabra clave (como "tipos existenciales") que me hace sentir feliz hasta que leo el código que lo usa de una manera completamente diferente (como
runST
,foo
ybar
arriba). - Están repletos de suposiciones que he leído lo último en cualquier rama de matemáticas discretas, teoría de categorías o álgebra abstracta que es popular esta semana. (Si no leí las palabras "consultar el documento sea cual sea para los detalles de la implementación" de nuevo, será demasiado pronto.)
- Están escritos de manera que con frecuencia convierten incluso conceptos simples en gramática y semántica tortuosamente retorcida y fracturada.
Entonces...
A la pregunta real. ¿Alguien puede explicar completamente la forall
palabra clave en un inglés claro y simple (o, si existe en algún lugar, señalar una explicación tan clara que me haya perdido) que no asume que soy un matemático inmerso en la jerga?
Editado para agregar:
Hubo dos respuestas destacadas de las de mayor calidad a continuación, pero desafortunadamente solo puedo elegir una como la mejor. La respuesta de Norman fue detallada y útil, explicando las cosas de una manera que mostró algunos de los fundamentos teóricos forall
y al mismo tiempo me mostró algunas de las implicaciones prácticas de la misma. la respuesta de yairchucubrió un área que nadie más mencionó (variables de tipo de ámbito) e ilustró todos los conceptos con código y una sesión de GHCi. Si fuera posible seleccionar ambos de la mejor manera, lo haría. Desafortunadamente, no puedo y, después de mirar detenidamente ambas respuestas, he decidido que Yairchu supera ligeramente a Norman debido al código ilustrativo y la explicación adjunta. Sin embargo, esto es un poco injusto, porque realmente necesitaba ambas respuestas para entender esto hasta el punto que forall
no me deja con una leve sensación de temor cuando lo veo en una firma de tipo.
Respuestas:
Comencemos con un ejemplo de código:
Este código no se compila (error de sintaxis) en Haskell 98. Requiere una extensión para admitir la
forall
palabra clave.Básicamente, hay 3 diferentes usos comunes de la
forall
palabra clave (o al menos así lo parece ), y cada uno tiene su propia extensión de Haskell:ScopedTypeVariables
,RankNTypes
/Rank2Types
,ExistentialQuantification
.El código anterior no obtiene un error de sintaxis con ninguno de los habilitados, sino solo verificaciones de tipo con
ScopedTypeVariables
habilitado.Variables de tipo con ámbito:
Las variables de tipo con ámbito ayudan a especificar tipos para el código dentro de las
where
cláusulas. Esto hace que elb
enval :: b
el mismo que elb
defoob :: forall a b. (b -> b) -> b -> (a -> b) -> Maybe a -> b
.Un punto confuso : puede escuchar que cuando omite el
forall
de un tipo, en realidad todavía está implícitamente allí. ( de la respuesta de Norman: "normalmente estos idiomas omiten el forall de los tipos polimórficos" ). Esta afirmación es correcta, pero se refiere a los otros usosforall
y no alScopedTypeVariables
uso.Rango-N-Tipos:
Comencemos con eso
mayb :: b -> (a -> b) -> Maybe a -> b
es equivalente amayb :: forall a b. b -> (a -> b) -> Maybe a -> b
, excepto cuandoScopedTypeVariables
está habilitado.Esto significa que funciona para todos
a
y cada unob
.Digamos que quieres hacer algo como esto.
¿Cuál debe ser el tipo de esto
liftTup
? EsliftTup :: (forall x. x -> f x) -> (a, b) -> (f a, f b)
. Para ver por qué, intentemos codificarlo:"Hmm ... ¿por qué GHC infiere que la tupla debe contener dos del mismo tipo? Digamos que no tienen que ser"
Hmm así que aquí GHC no apliquemos
liftFunc
elv
debidov :: b
yliftFunc
quiere unax
. ¡Realmente queremos que nuestra función obtenga una función que acepte cualquier posiblex
!Entonces, no es
liftTup
que funcione para todosx
, es la función que obtiene lo que hace.Cuantificación existencial:
Usemos un ejemplo:
¿Cómo es eso diferente de Rank-N-types?
Con Rango-N-Tipos,
forall a
significa que su expresión debe ajustarse a todos losa
s posibles . Por ejemplo:Una lista vacía funciona como una lista de cualquier tipo.
Entonces, con la cuantificación existencial,
forall
s en lasdata
definiciones significa que, el valor contenido puede ser de cualquier tipo adecuado, no que debe ser de todos los tipos adecuados.fuente
ScopedTypeVariables
pareces peor de lo que es. Si escribe el tipob -> (a -> b) -> Maybe a -> b
con esta extensión, seguirá siendo exactamente equivalente aforall a b. b -> (a -> b) -> Maybe a -> b
. Sin embargo, si desea referirse a lo mismob
(y no tenerlo cuantificado implícitamente), entonces debe escribir la versión cuantificada explícitamente. DeSTV
lo contrario, sería una extensión extremadamente intrusiva.ScopedTypeVariables
, y no creo que sea malo. En mi opinión, es una herramienta muy útil para el proceso de programación, y especialmente para los principiantes de Haskell, y estoy agradecido de que exista.No. (Bueno, tal vez Don Stewart pueda).
Aquí están las barreras para una explicación simple y clara o
forall
:Es un cuantificador. Debe tener al menos un poco de lógica (cálculo de predicados) para haber visto un cuantificador universal o existencial. Si nunca ha visto el cálculo de predicados o no se siente cómodo con los cuantificadores (y he visto estudiantes durante los exámenes de calificación de doctorado que no están cómodos), entonces no hay una explicación fácil para usted
forall
.Es un cuantificador de tipo . Si no ha visto el Sistema F y ha tenido algo de práctica escribiendo tipos polimórficos, le resultará
forall
confuso. La experiencia con Haskell o ML no es suficiente, porque normalmente estos idiomas omiten losforall
tipos polimórficos. (En mi opinión, este es un error de diseño del lenguaje).En Haskell en particular,
forall
se usa de formas que me parecen confusas. (No soy un teórico de tipos, pero mi trabajo me pone en contacto con mucha teoría de tipos, y me siento bastante cómodo con eso). Para mí, la principal fuente de confusión es queforall
se usa para codificar un tipo que Yo mismo preferiría escribir conexists
. Está justificado por un poco complicado de isomorfismo de tipo que involucra cuantificadores y flechas, y cada vez que quiero entenderlo, tengo que buscar cosas y resolver el isomorfismo yo mismo.Si no se siente cómodo con la idea del isomorfismo de tipo, o si no tiene ninguna práctica pensando en isomorfismos de tipo, este uso
forall
lo va a obstaculizar.Si bien el concepto general de
forall
siempre es el mismo (vinculante para introducir una variable de tipo), los detalles de los diferentes usos pueden variar significativamente. El inglés informal no es una herramienta muy buena para explicar las variaciones. Para comprender realmente lo que está sucediendo, necesitas algunas matemáticas. En este caso, las matemáticas relevantes se pueden encontrar en el texto introductorio Tipos y lenguajes de programación de Benjamin Pierce , que es un libro muy bueno.En cuanto a sus ejemplos particulares,
runST
debería hacerte doler la cabeza. Los tipos de rango superior (a la izquierda de una flecha) rara vez se encuentran en la naturaleza. Le animo a leer el documento que introdujorunST
: "Hilos de estado funcional perezoso" . Este es un documento realmente bueno, y le dará una intuición mucho mejor para el tiporunST
en particular y para los tipos de rango superior en general. La explicación toma varias páginas, está muy bien hecha, y no voy a tratar de condensarla aquí.Considerar
Si llamo
bar
, simplemente puedo elegir cualquier tipoa
que me guste y puedo pasarle una función de tipoa
a tipoa
. Por ejemplo, puedo pasar la función(+1)
o la funciónreverse
. Puedes pensarforall
que dice "Ahora tengo que elegir el tipo". (La palabra técnica para elegir el tipo es instanciar ).Las restricciones a las llamadas
foo
son mucho más estrictas: el argumentofoo
debe ser una función polimórfica. Con ese tipo, las únicas funciones a las que puedo pasarfoo
sonid
o una función que siempre diverge o errores, comoundefined
. La razón es quefoo
, con ,forall
está a la izquierda de la flecha, de modo que la persona que llamafoo
no puedo elegir lo quea
es, sino que la implementación defoo
eso es elegir lo quea
es. Debido a queforall
está a la izquierda de la flecha, en lugar de encima de la flecha como enbar
, la instanciación tiene lugar en el cuerpo de la función en lugar de en el sitio de la llamada.Resumen: Una explicación completa de la
forall
palabra clave requiere matemática y solo puede ser entendida por alguien que haya estudiado matemática. Incluso las explicaciones parciales son difíciles de entender sin las matemáticas. Pero tal vez mis explicaciones parciales, no matemáticas, ayuden un poco. ¡Ve a leer Launchbury y Peyton JonesrunST
!Anexo: Jerga "arriba", "abajo", "a la izquierda de". Estos no tienen nada que ver con las formas textuales en que se escriben los tipos y tienen mucho que ver con los árboles de sintaxis abstracta. En la sintaxis abstracta, a
forall
toma el nombre de una variable de tipo, y luego hay un tipo completo "debajo" del forall. Una flecha toma dos tipos (argumento y tipo de resultado) y forma un nuevo tipo (el tipo de función). El tipo de argumento está "a la izquierda de" la flecha; es el hijo izquierdo de la flecha en el árbol de sintaxis abstracta.Ejemplos:
En
forall a . [a] -> [a]
, el forall está por encima de la flecha; lo que está a la izquierda de la flecha es[a]
.En
el tipo entre paréntesis se llamaría "un poco a la izquierda de una flecha". (Estoy usando tipos como este en un optimizador en el que estoy trabajando).
fuente
forall a . [a] -> [a]
, el más cercano está a la izquierda de la flecha.forall
en esas circunstancias como, efectivamente, línea ruido. Revisaré el documento al que se vinculó (¡gracias por el enlace también!) Y veré si está en mi ámbito de comprensión. Prestigio.exists
nunca se implementó. (¡No es parte del Sistema F!) En Haskell, parte del Sistema F se hace implícito, peroforall
es una de las cosas que no se puede barrer completamente debajo de la alfombra. Es como si comenzaran con Hindley-Milner, lo que permitiríaforall
hacerse implícito, y luego optaron por un sistema de tipo más poderoso, confundiendo a aquellos de nosotros que estudiamos el 'forall' y 'existe' de FOL y nos detuvimos allí.Mi respuesta original:
Como indica Norman, es muy difícil dar una explicación clara y clara en inglés de un término técnico de la teoría de tipos. Sin embargo, todos lo intentamos.
Solo hay una cosa para recordar sobre 'forall': vincula los tipos a algún alcance . Una vez que entiendes eso, todo es bastante fácil. Es el equivalente de 'lambda' (o una forma de 'let') en el nivel de tipo: Norman Ramsey utiliza la noción de "izquierda" / "arriba" para transmitir este mismo concepto de alcance en su excelente respuesta .
La mayoría de los usos de 'forall' son muy simples, y puede encontrarlos introducidos en el Manual del usuario de GHC, S7.8 ., Particularmente el excelente S7.8.5 sobre formas anidadas de 'forall'.
En Haskell, generalmente dejamos el aglutinante para los tipos, cuando el tipo está universalmente cuantificado, así:
es equivalente a:
Eso es.
Como puede vincular las variables de tipo ahora a cierto alcance, puede tener ámbitos distintos del nivel superior (" universalmente cuantificado "), como su primer ejemplo, donde la variable de tipo solo es visible dentro de la estructura de datos. Esto permite tipos ocultos (" tipos existenciales "). O podemos tener anidamiento arbitrario de enlaces ("tipos de rango N").
Para comprender a fondo los sistemas de tipos, deberá aprender algo de jerga. Esa es la naturaleza de la informática. Sin embargo, los usos simples, como los anteriores, deberían poder captarse intuitivamente, por analogía con 'let' en el nivel de valor. Una gran introducción es Launchbury y Peyton Jones .
fuente
length :: forall a. [a] -> Int
no es equivalente alength :: [a] -> Int
cuandoScopedTypeVariables
está habilitado. Cuandoforall a.
está allí, afectalength
lawhere
cláusula (si tiene una) y cambia el significado de las variables de tipo nombradasa
en ella.Er, ¿y qué hay de la lógica simple de primer orden?
forall
es bastante claro en referencia a la cuantificación universal , y en ese contexto el término existencial también tiene más sentido, aunque sería menos incómodo si hubiera unaexists
palabra clave. Si la cuantificación es efectivamente universal o existencial depende de la ubicación del cuantificador en relación con el lugar donde se usan las variables en qué lado de una flecha de función y todo es un poco confuso.Entonces, si eso no ayuda, o si simplemente no le gusta la lógica simbólica, desde una perspectiva más funcional de programación, puede pensar que las variables de tipo son solo parámetros de tipo (implícitos) para la función. Las funciones que toman parámetros de tipo en este sentido se escriben tradicionalmente usando una lambda mayúscula por cualquier razón, que escribiré aquí como
/\
.Entonces, considere la
id
función:Podemos reescribirlo como lambdas, moviendo el "parámetro de tipo" fuera de la firma de tipo y agregando anotaciones de tipo en línea:
Aquí está lo mismo hecho para
const
:Entonces su
bar
función podría ser algo como esto:Tenga en cuenta que el tipo de la función dada
bar
como argumento depende delbar
parámetro de tipo de. Considere si tenía algo como esto en su lugar:Aquí
bar2
está aplicando la función a algo de tipoChar
, por lo que darbar2
cualquier parámetro de tipo distinto deChar
causará un error de tipo.Por otro lado, esto es lo que
foo
podría parecer:¡A diferencia
bar
, enfoo
realidad no toma ningún tipo de parámetro! Toma una función que en sí misma toma un parámetro de tipo, luego aplica esa función a dos tipos diferentes .Entonces, cuando vea una
forall
firma de tipo, piense en ella como una expresión lambda para firmas de tipo . Al igual que las lambdas regulares, el alcance de seforall
extiende lo más hacia la derecha posible, hasta encerrar paréntesis, y al igual que las variables unidas en un lambda regular, las variables de tipo unidas por aforall
solo están dentro del alcance dentro de la expresión cuantificada.Post scriptum : Quizás te preguntes, ahora que estamos pensando en funciones que toman parámetros de tipo, ¿por qué no podemos hacer algo más interesante con esos parámetros que ponerlos en una firma de tipo? ¡La respuesta es que podemos!
Una función que pone variables de tipo junto con una etiqueta y devuelve un nuevo tipo es un constructor de tipos , que podría escribir de la siguiente manera:
Pero necesitaríamos una notación completamente nueva, porque la forma en que se escribe tal tipo, como
Either a b
, ya sugiere "aplicar la funciónEither
a estos parámetros".Por otro lado, una función que tipo de "patrón coincide" en sus parámetros de tipo, que devuelve diferentes valores para diferentes tipos, es un método de una clase de tipo . Una ligera expansión de mi
/\
sintaxis anterior sugiere algo como esto:Personalmente, creo que prefiero la sintaxis real de Haskell ...
Una función que "coincide" con sus parámetros de tipo y devuelve un tipo arbitrario existente es una familia de tipos o dependencia funcional; en el primer caso, incluso se parece mucho a una definición de función.
fuente
λ
para el caso, pero la extensión de sintaxis unicode de GHC no lo admite porque λ es una letra , un hecho desafortunado que hipotéticamente también se aplicaría a mis hipotéticas abstracciones de gran lambda. Por lo tanto,/\
por analogía a\
. Supongo que podría haberlo usado,∀
pero estaba tratando de evitar el cálculo de predicados ...Aquí hay una explicación rápida y sucia en términos simples con los que probablemente ya esté familiarizado.
La
forall
palabra clave realmente solo se usa de una manera en Haskell. Siempre significa lo mismo cuando lo ves.Cuantificación universal
Un tipo universalmente cuantificado es un tipo de la forma
forall a. f a
. Un valor de ese tipo puede considerarse como una función que toma un tipoa
como argumento y devuelve un valor de tipof a
. Excepto que en Haskell estos argumentos de tipo son pasados implícitamente por el sistema de tipos. Esta "función" tiene que darle el mismo valor sin importar el tipo que reciba, por lo que el valor es polimórfico .Por ejemplo, considere el tipo
forall a. [a]
. Un valor de ese tipo toma otro tipoa
y le devuelve una lista de elementos del mismo tipoa
. Solo hay una implementación posible, por supuesto. Tendría que darle la lista vacía porquea
podría ser absolutamente cualquier tipo. La lista vacía es el único valor de lista que es polimórfico en su tipo de elemento (ya que no tiene elementos).O el tipo
forall a. a -> a
. El llamador de dicha función proporciona tanto un tipoa
como un valor de tipoa
. La implementación debe devolver un valor del mismo tipoa
. Solo hay una posible implementación nuevamente. Tendría que devolver el mismo valor que se le dio.Cuantificación existencial
Un tipo cuantificado existencialmente tendría la forma
exists a. f a
, si Haskell apoyara esa notación. Un valor de ese tipo puede considerarse como un par (o un "producto") que consiste en un tipoa
y un valor de tipof a
.Por ejemplo, si tiene un valor de tipo
exists a. [a]
, tiene una lista de elementos de algún tipo. Podría ser de cualquier tipo, pero incluso si no sabe qué es, hay mucho que podría hacer para dicha lista. Puede revertirlo, contar el número de elementos o realizar cualquier otra operación de lista que no dependa del tipo de elementos.OK, espera un minuto. ¿Por qué Haskell usa
forall
para denotar un tipo "existencial" como el siguiente?Puede ser confuso, pero realmente describe el tipo del constructor de datos
SB
:Una vez construido, puede pensar en un valor de tipo
ShowBox
que consta de dos cosas. Es un tipos
junto con un valor de tipos
. En otras palabras, es un valor de un tipo cuantificado existencialmente.ShowBox
realmente podría escribirse comoexists s. Show s => s
si Haskell apoyara esa notación.runST
y amigosDado eso, ¿cómo son diferentes?
Primero tomemos
bar
. Toma un tipoa
y una función de tipoa -> a
, y produce un valor de tipo(Char, Bool)
. Podríamos elegirInt
como ela
y darle una función de tipo,Int -> Int
por ejemplo. Perofoo
es diferente. Requiere que la implementación defoo
poder pasar cualquier tipo que desee a la función que le damos. Entonces, la única función que razonablemente podríamos darle esid
.Ahora deberíamos poder abordar el significado del tipo de
runST
:Por lo tanto,
runST
debe ser capaz de producir un valor de tipoa
, sin importar el tipo que le demosa
. Para hacerlo, utiliza un argumento de tipoforall s. ST s a
que ciertamente debe de alguna manera producir ela
. Además, debe ser capaz de producir un valor de tipo,a
sin importar el tipo de implementación querunST
decida dars
.¿OK y eso qué? El beneficio es que esto impone una restricción a la persona
runST
que llama, ya que el tipoa
no puede involucrar al tipos
en absoluto. No puede pasarle un valor de tipoST s [s]
, por ejemplo. Lo que eso significa en la práctica es que la implementación derunST
es libre de realizar una mutación con el valor de tipos
. El tipo garantiza que esta mutación sea local para la implementación derunST
.El tipo de
runST
es un ejemplo de un tipo polimórfico de rango 2 porque el tipo de su argumento contiene unforall
cuantificador. El tipo defoo
arriba también es de rango 2. Un tipo polimórfico ordinario, como el debar
, es rango-1, pero se convierte en rango-2 si se requiere que los tipos de argumentos sean polimórficos, con su propioforall
cuantificador. Y si una función toma argumentos de rango 2, entonces su tipo es rango-3, y así sucesivamente. En general, un tipo que toma argumentos polimórficos de rangon
tiene rangon + 1
.fuente
Voy a tratar de explicar solo el significado y quizás la aplicación
forall
en el contexto de Haskell y sus sistemas de tipos.Pero antes de que comprendan que me gustaría dirigirlos a una charla muy accesible y agradable de Runar Bjarnason titulada " Restricciones liberan, liberan restricciones ". La charla está llena de ejemplos de casos de uso del mundo real, así como ejemplos en Scala para respaldar esta afirmación, aunque no se menciona
forall
. Trataré de explicar laforall
perspectiva a continuación.Es muy importante digerir y creer que esta declaración proceda con la siguiente explicación, por lo que le insto a que vea la charla (al menos partes de ella).
Ahora, un ejemplo muy común, que muestra la expresividad del sistema de tipos Haskell es esta firma de tipo:
foo :: a -> a
Se dice que dada esta firma de tipo, solo hay una función que puede satisfacer este tipo y esa es la
identity
función o lo que se conoce más popularmenteid
.En las primeras etapas de mi aprendizaje de Haskell, siempre me preguntaba las siguientes funciones:
ambos satisfacen la firma de tipo anterior, entonces ¿por qué la gente de Haskell afirma que es
id
solo el que satisface la firma de tipo?Esto se debe a que hay una implícita
forall
oculta en la firma de tipo. El tipo real es:Entonces, ahora volvamos a la declaración: las restricciones liberan, las libertades restringen
Traduciendo eso al sistema de tipos, esta declaración se convierte en:
Una restricción a nivel de tipo, se convierte en una libertad a nivel de término
y
Una libertad en el nivel de tipo, se convierte en una restricción en el nivel de término
Tratemos de probar la primera afirmación:
Una restricción en el nivel de tipo.
Poniendo una restricción a nuestra firma tipo
se convierte en una libertad en el nivel de término nos da la libertad o flexibilidad para escribir todo esto
Lo mismo puede observarse restringiendo
a
con cualquier otra clase de tipo, etc.Entonces, a qué se
foo :: (Num a) => a -> a
traduce este tipo de firma es:Esto se conoce como cuantificación existencial, que se traduce en que existen algunos casos
a
en los que una función cuando se alimenta algo de tipoa
devuelve algo del mismo tipo, y todas esas instancias pertenecen al conjunto de Números.Por lo tanto, podemos ver que agregar una restricción (que
a
debería pertenecer al conjunto de Números) libera el término nivel para tener múltiples implementaciones posibles.Ahora llegando a la segunda declaración y la que realmente lleva la explicación de
forall
:Una libertad en el nivel de tipo, se convierte en una restricción en el nivel de término
Así que ahora liberemos la función a nivel de tipo:
Ahora esto se traduce en:
lo que significa que la implementación de este tipo de firma debe ser tal que sea
a -> a
para todas las circunstancias.Así que ahora esto comienza a limitarnos a nivel de término. Ya no podemos escribir
porque esta implementación no satisfaría si ponemos
a
como aBool
.a
puede ser unChar
o un[Char]
o un tipo de datos personalizado. En todas las circunstancias, debería devolver algo del tipo similar. Esta libertad a nivel de tipo es lo que se conoce como cuantificación universal y la única función que puede satisfacer esto esque se conoce comúnmente como la
identity
funciónPor
forall
lo tanto, es unliberty
nivel de tipo, cuyo propósito real esconstrain
el nivel de término para una implementación particular.fuente
La razón por la que hay diferentes usos de esta palabra clave es que en realidad se usa en al menos dos extensiones de sistema de tipos diferentes: tipos de rango superior y existenciales.
Probablemente sea mejor leer y comprender esas dos cosas por separado, en lugar de tratar de obtener una explicación de por qué 'forall' es una sintaxis apropiada en ambos al mismo tiempo.
fuente
¿Cómo es existencial existencial?
Una explicación de por qué
forall
endata
las definiciones son isomorfo a(exists a. a)
(pseudo-Haskell) se puede encontrar en "Haskell / tipos Existencialmente cuantificados" de Wikilibros .El siguiente es un breve resumen literal:
Cuando coinciden / deconstruyen patrones
MkT x
, ¿de qué tipo sonx
?x
puede ser de cualquier tipo (como se indica en elforall
), por lo que su tipo es:Por lo tanto, los siguientes son isomórficos:
forall significa forall
Mi interpretación simple de todo esto es que "
forall
realmente significa 'para todos'". Una distinción importante es el impacto deforall
la aplicación de definición versus función .A
forall
significa que la definición del valor o función debe ser polimórfica.Si lo que se define es un valor polimórfico , significa que el valor debe ser válido para todos los adecuados
a
, lo cual es bastante restrictivo.Si lo que se está definiendo es una función polimórfica , significa que la función debe ser válida para todos los adecuados
a
, lo cual no es tan restrictivo porque el hecho de que la función sea polimórfica no significa que el parámetro que se aplique tenga que ser polimórfico. Es decir, si la función es válida para todosa
, a la inversa, se puede aplicar cualquier función adecuada a la función. Sin embargo, el tipo de parámetro solo se puede elegir una vez en la definición de la función.a
Si a
forall
está dentro del tipo de parámetro de función (es decir, aRank2Type
), significa que el parámetro aplicado debe ser verdaderamente polimórfico, para ser coherente con la idea de que la definición deforall
medios es polimórfica. En este caso, el tipo del parámetro se puede elegir más de una vez en la definición de la función ( "y se elige mediante la implementación de la función", como lo señala Norman )Por lo tanto, la razón por la cual las
data
definiciones existenciales permiten cualquieraa
es porque el constructor de datos es una función polimórfica :tipo de MkT ::
a -> *
Lo que significa que cualquiera
a
puede aplicarse a la función. A diferencia de, digamos, un valor polimórfico :tipo de valor T ::
a
Lo que significa que la definición de valueT debe ser polimórfica. En este caso,
valueT
se puede definir como una lista vacía[]
de todos los tipos.Las diferencias
Aunque el significado de
forall
es consistente enExistentialQuantification
yRankNType
, los existenciales tienen una diferencia ya que eldata
constructor se puede usar en la coincidencia de patrones. Como se documenta en la guía del usuario de ghc :fuente