una trabajo son sociedad que programacion por las instancias hacer genericas generica funciones empresa ejemplos dividir contador composicion como clases ciclos casos haskell polymorphism scotty

haskell - trabajo - programacion generica ejemplos



¿Cuándo una función genérica no es genérica? (3)

Parece que GHC está diciendo que, de hecho, el tipo de runDB se especializa de alguna manera.

Tu conjetura es correcta. Su tipo original era app :: (MonadIO m) => (SqlPersistT IO a -> ma) -> ScottyM () . Esto significa que su argumento runDB de tipo SqlPersistT IO a -> ma se puede utilizar en cualquier tipo a . Sin embargo, el cuerpo de la app quiere usar el argumento runDB en dos tipos diferentes ( Person y Food ), por lo que necesitamos pasar un argumento que pueda funcionar para cualquier número de tipos diferentes en el cuerpo. Así, la app necesita el tipo

app :: MonadIO m => (forall a. SqlPersistT IO a -> m a) -> ScottyM ()

(Yo sugeriría mantener la restricción de MonadIO fuera de la general, pero también puede MonadIO ).

EDITAR:

Lo que está pasando detrás de escena es lo siguiente:

(F a -> G a) -> X significa para todos forall a. (F a -> G a) -> X forall a. (F a -> G a) -> X , que significa //a -> (F a -> G a) -> X // es la lambda a nivel de tipo. Es decir, la persona que llama puede pasar en un solo tipo a y una función de tipo F a -> G a para esa elección particular de a .

(forall a. F a -> G a) -> X significa (//a -> F a -> G a) -> X y la persona que llama debe pasar en una función en la cual el usuario puede especializarse en muchas opciones de .

Estoy trabajando en un servidor Haskell usando scotty y persistent . Muchos manejadores necesitan acceso al grupo de conexión de la base de datos, por lo que he decidido pasar el grupo por toda la aplicación, de esta manera:

main = do runNoLoggingT $ withSqlitePool ":memory:" 10 $ /pool -> liftIO $ scotty 7000 (app pool) app pool = do get "/people" $ do people <- liftIO $ runSqlPool getPeople pool renderPeople people get "/foods" $ do food <- liftIO $ runSqlPool getFoods pool renderFoods food

donde getPeople y getFoods son acciones de base de datos persistent apropiadas que devuelven [Person] y [Food] respectivamente.

El patrón de llamar a liftIO y runSqlPool en un pool se vuelve agotador después de un tiempo, ¿no sería genial si pudiera refactorizarlos en una sola función, como runDB de Yesod, que simplemente tomaría la consulta y devolvería el tipo apropiado? Mi intento de escribir algo como esto es:

runDB'' :: (MonadIO m) => ConnectionPool -> SqlPersistT IO a -> m a runDB'' pool q = liftIO $ runSqlPool q pool

Ahora, puedo escribir esto:

main = do runNoLoggingT $ withSqlitePool ":memory:" 10 $ /pool -> liftIO $ scotty 7000 $ app (runDB'' pool) app runDB = do get "/people" $ do people <- runDB getPeople renderPeople people get "/foods" $ do food <- runDB getFoods renderFoods food

Excepto que GHC se queja:

Couldn''t match type `Food'' with `Person'' Expected type: persistent-2.1.1.4:Database.Persist.Sql.Types.SqlPersistT IO [persistent-2.1.1.4:Database.Persist.Class.PersistEntity.Entity Person] Actual type: persistent-2.1.1.4:Database.Persist.Sql.Types.SqlPersistT IO [persistent-2.1.1.4:Database.Persist.Class.PersistEntity.Entity Food] In the first argument of `runDB'', namely `getFoods''

Parece que GHC está diciendo que, de hecho, el tipo de runDB se especializa de alguna manera. Pero entonces, ¿cómo se definen las funciones como runSqlPool ? Su tipo de firma es similar al mío:

runSqlPool :: MonadBaseControl IO m => SqlPersistT m a -> Pool Connection -> m a

pero se puede usar con consultas de base de datos que devuelven muchos tipos diferentes, como lo estaba haciendo originalmente. Creo que hay algo fundamental que no entiendo bien sobre los tipos aquí, ¡pero no tengo idea de cómo descubrirlo! Cualquier ayuda sería muy apreciada.

EDITAR:

A sugerencia de Yuras, he añadido esto:

type DBRunner m a = (MonadIO m) => SqlPersistT IO a -> m a runDB'' :: ConnectionPool -> DBRunner m a app :: forall a. DBRunner ActionM a -> ScottyM ()

que requería -XRankNTypes para el typedef. Sin embargo, el error del compilador sigue siendo idéntico.

EDITAR:

Victoria a los comentaristas. Esto permite que el código se compile:

app :: (forall a. DBRunner ActionM a) -> ScottyM ()

Por lo cual estoy agradecido, pero todavía desconcertado!

El código actualmente se ve así y this .


Juguemos el juego:

Prelude> let f str = (read str, read str) Prelude> f "1" :: (Int, Float) (1,1.0)

Funciona como se espera.

Prelude> let f str = (read1 str, read1 str) where read1 = read Prelude> f "1" :: (Int, Float) (1,1.0)

Trabaja tambien

Prelude> let f read1 str = (read1 str, read1 str) Prelude> f read "1" :: (Int, Float) <interactive>:21:1: Couldn''t match type ‘Int’ with ‘Float’ Expected type: (Int, Float) Actual type: (Int, Int) In the expression: f read "1" :: (Int, Float) In an equation for ‘it’: it = f read "1" :: (Int, Float)

Pero esto no lo hace. ¿Cuál es la diferencia?

La última f tiene el siguiente tipo:

Prelude> :t f f :: (t1 -> t) -> t1 -> (t, t)

Por lo tanto, no funciona por una razón clara, ambos elementos de la tupla deben tener el mismo tipo.

La solución es así:

Prelude> :set -XRankNTypes Prelude> let f read1 str = (read1 str, read1 str); f :: (Read a1, Read a2) => (forall a . Read a => str -> a) -> str -> (a1, a2) Prelude> f read "1" :: (Int, Float) (1,1.0)

Es poco probable que pueda venir con una buena explicación de RankNTypes , así que ni siquiera lo intentaría. Hay suficientes recursos en la web.


Para responder realmente la pregunta del título que aparentemente continúa desconcertándote: Haskell siempre elige el tipo de rango 1 más genérico para una función, cuando no proporcionas una firma explícita. Así que para la app en la app (runDB'' pool) expresión app (runDB'' pool) , GHC intentaría tener el tipo

app :: DBRunner ActionM a -> ScottyM ()

que es de hecho taquigrafía para

app :: forall a. ( DBRunner ActionM a -> ScottyM () )

Esto es polimórfico de rango 1, porque todas las variables de tipo se introducen fuera de la firma (no hay ninguna cuantificación en curso en la propia firma; el argumento DBRunner ActionM a es de hecho monomorfo, ya que a se fija en ese punto). En realidad, es el tipo más genérico posible: puede funcionar con un argumento polimórfico como (runDB'' pool) , pero también estaría bien con los argumentos monomórficos.

Pero resulta que la implementación de la app no puede ofrecer esa generalidad: necesita una acción polimórfica, de lo contrario no puede alimentar dos tipos diferentes de valores a esa acción. Por lo tanto necesitas solicitar manualmente el tipo más específico.

app :: (forall a. DBRunner ActionM a) -> ScottyM ()

que es rango 2, porque tiene una firma que contiene un argumento polimórfico de rango 1. GHC no puede realmente saber que este es el tipo que desea, no existe el "tipo de clasificación-n más general posible" bien definido para una expresión, ya que siempre puede introducir cuantificadores adicionales. Por lo que debe especificar manualmente el tipo de rango 2.