haskell polymorphism

Haskell rango dos polimorfismo error de compilación



polymorphism (2)

Dadas las siguientes definiciones:

import Control.Monad.ST import Data.STRef fourty_two = do x <- newSTRef (42::Int) readSTRef x

Lo siguiente compila bajo GHC:

main = (print . runST) fourty_two -- (1)

Pero esto no lo hace:

main = (print . runST) $ fourty_two -- (2)

Pero luego, como señala bdonlan en un comentario, esto compila:

main = ((print . runST) $) fourty_two -- (3)

Pero, esto no compila

main = (($) (print . runST)) fourty_two -- (4)

Lo que parece indicar que (3) solo compila debido al tratamiento especial de infix $ , sin embargo, todavía no explica por qué (1) compila.

Preguntas:

1) He leído las siguientes dos preguntas ( first , second ) y me han hecho creer que $ solo se puede instanciar con tipos monomórficos. Pero yo asumiría de manera similar . solo puede ser instanciado con tipos monomórficos, y como resultado fallaría de manera similar. ¿Por qué el primer código tiene éxito pero el segundo código no? (Por ejemplo, ¿hay una regla especial que tiene GHC para el primer caso que no se puede aplicar en el segundo?)

2) ¿Existe una extensión actual de GHC que compile el segundo código? (Quizás el ImpredicativePolymorphism hizo esto en algún momento, pero parece desaprobado, ¿algo lo ha reemplazado?)

3) ¿Hay alguna forma de definir digamos `my_dollar` usando las extensiones de GHC para hacer lo que $ hace, pero también es capaz de manejar tipos polimórficos, por lo que (print . runST) `my_dollar` fourty_two compila?

Editar: Respuesta propuesta:

Además, lo siguiente no se compila:

main = ((.) print runST) fourty_two -- (5)

Esto es lo mismo que (1), excepto que no se usa la versión de infijo de . .

Como resultado, parece que GHC tiene reglas especiales tanto para $ como para . , pero solo sus versiones infix.


  1. No estoy seguro de entender por qué el segundo no funciona. Podemos ver el tipo de print . runST print . runST y observa que es suficientemente polimórfico, por lo que la culpa no está en (.) . Sospecho que la regla especial que tiene GHC para infix ($) simplemente no es suficiente. SPJ y sus amigos podrían estar abiertos para volver a examinarlo si propone este fragmento como un error en su rastreador.

    En cuanto a por qué el tercer ejemplo funciona, bueno, eso es solo porque nuevamente el tipo de ((print . runST) $) es suficientemente polimórfico; de hecho, es igual al tipo de print . runST print . runST .

  2. Nada ha reemplazado al ImpredicativePolymorphism , porque la gente de GHC no ha visto ningún caso de uso en el que la conveniencia adicional del programador supere el potencial adicional de los errores del compilador. (Tampoco creo que vean esto como convincente, aunque, por supuesto, no soy la autoridad).
  3. Podemos definir un poco menos polimórfico ($$) :

    {-# LANGUAGE RankNTypes #-} infixl 0 $$ ($$) :: ((forall s. f s a) -> b) -> ((forall s. f s a) -> b) f $$ x = f x

    Entonces su ejemplo se comprueba correctamente con este nuevo operador:

    *Main> (print . runST) $$ fourty_two 42


No puedo decir con demasiada autoridad sobre este tema, pero esto es lo que creo que puede estar pasando:

Considere lo que tiene que hacer el typechecker en cada uno de estos casos. (print . runST) tiene el tipo Show b => (forall s. ST st) -> IO () . fourty_two tiene el tipo ST x Int .

El forall aquí es un calificador de tipo existencial - aquí significa que el argumento pasado debe ser universal en s . Es decir, debe pasar un tipo polimórfico que admita cualquier valor para s absoluto. Si no forall explícitamente forall , Haskell lo coloca en el nivel más externo de la definición de tipo. Esto significa que fourty_two :: forall x. ST x Int fourty_two :: forall x. ST x Int y (print . runST) :: forall t. Show t => (forall s. ST st) -> IO () (print . runST) :: forall t. Show t => (forall s. ST st) -> IO ()

Ahora, podemos emparejar para todo forall x. ST x Int forall x. ST x Int con forall s. ST st forall s. ST st dejando t = Int, x = s . Así que el caso de llamada directa funciona. ¿Qué pasa si usamos $ , sin embargo?

$ tiene el tipo ($) :: forall a b. (a -> b) -> a -> b ($) :: forall a b. (a -> b) -> a -> b . Cuando resolvemos a y b , dado que el tipo para $ no tiene un tipo de alcance explícito como este, el argumento x de fourty_two se eleva al ámbito más externo en el tipo para ($) - entonces ($) :: forall x t. (a = forall s. ST st -> b = IO ()) -> (a = ST xt) -> IO () ($) :: forall x t. (a = forall s. ST st -> b = IO ()) -> (a = ST xt) -> IO () . En este punto, intenta hacer coincidir a y b , y falla.

Si, por el contrario, escribe ((print . runST) $) fourty_two , el compilador primero resuelve el tipo de ((print . runST $) . Resuelve que el tipo para ($) es forall t. (a = forall s. ST st -> b = IO ()) -> a -> b ; tenga en cuenta que, dado que la segunda aparición de a no tiene restricciones, no tenemos esa variable molesta que se filtra hacia el alcance más externo. la función se aplica parcialmente, y el tipo general de la expresión es para todos forall t. (forall s. ST st) -> IO () , que está justo donde comenzamos, y por lo tanto tiene éxito.