I have a function type declaration
f :: MonadHandler m => SqlPersistT m ()
Which I want to convert to
f :: MonadHandlerDB m => m ()
I try everything I can think of to define constraint MonadHandlerDB, but cannot get either it or function type declaration to compile, e.g.:
class (forall a . (MonadHandler m, m ~ SqlPersistT a)) => MonadHandlerDB m
class MonadHandlerDB m
instance MonadHandler a => MonadHandlerDB (SqlPersistT a)
type MonadHandlerDB m = forall a . (MonadHandler a, m ~ SqlPersistT a)
type MonadHandlerDB = forall a . (MonadHandler a => m ~ SqlPersistT a)
type MonadHandlerDB m = forall a . (MonadHandler a => m ~ SqlPersistT a)
One of the errors:
Couldn't match type `m' with `ReaderT backend0 m0
`m' is a rigid type variable bound by
the type signature for:
f:: forall (m :: * -> *).
MonadHandlerDB m =>
m ()
SqlPersistT is defined as
type SqlPersistT = ReaderT SqlBackend
How do I express this constraint?
I think this achieves what you want:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeSynonymInstances #-}
import Control.Monad.Trans.Reader (ReaderT (ReaderT))
import Database.Persist.Sql (SqlPersistT)
import Yesod.Core (MonadHandler)
f :: MonadHandlerDB m => m ()
f = undefined
class (MonadHandler (Sub m), m ~ SqlPersistT (Sub m)) => MonadHandlerDB m where
type Sub m :: * -> *
instance MonadHandler m => MonadHandlerDB (SqlPersistT m) where
type Sub (SqlPersistT m) = m
But note that I think this is really not very good to use in practice. It makes it seem as if the m is completely polymorphic, but, in fact, it can only ever be some monad inside SqlPersistT.
Constraints are powerful, but I think a constraint like this has a high potential to confuse its users.
Related
I'm trying to implement a Pascal-style write procedure in Haskell as a polyvariadic function. Here is a simplified version with monomorphic result type (IO in that case) that works fine:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import System.IO
class WriteParams a where
writeParams :: IO () -> a
instance (a ~ ()) => WriteParams (IO a) where
writeParams = id
instance (Show a, WriteParams r) => WriteParams (a -> r) where
writeParams m a = writeParams (m >> putStr (show a ++ " "))
write :: WriteParams params => params
write = writeParams (return ())
test :: IO ()
test = do
write 123
write ('a', 'z') True
When changing the result type to a polymorphic type, however, to use that function in different monads that have a MonadIO instance, I'm running into overlapping or undecidable instances. Specifically, that a ~ () trick from the previous version doesn't work anymore. The best approach is the following which requires a lot of type annotations, though:
class WriteParams' m a where
writeParams' :: m () -> a
instance (MonadIO m, m ~ m') => WriteParams' m (m' ()) where
writeParams' m = m
instance (MonadIO m, Show a, WriteParams' m r) => WriteParams' m (a -> r) where
writeParams' m a = writeParams' (m >> liftIO (putStr $ show a ++ " "))
write' :: forall m params . (MonadIO m, WriteParams' m params) => params
write' = writeParams' (return () :: m ())
test' :: IO ()
test' = do
write' 123 () :: IO ()
flip runReaderT () $ do
write' 45 ('a', 'z') :: ReaderT () IO ()
write' True
Is there anyway to make this example work without having to add type annotations here and there and still keep the result type polymorphic?
The two instances overlap, because their indices unify: m' () ~ (a -> r) with m' ~ (->) a and () ~ r.
To pick the first instance whenever m' is not a function type, you can add an OVERLAPPING pragma. (Read more about it in the GHC user guide)
-- We must put the equality (a ~ ()) to the left to make this
-- strictly less specific than (a -> r)
instance (MonadIO m, a ~ ()) => WriteParams (m a) where
writeParams = liftIO
instance {-# OVERLAPPING #-} (Show a, WriteParams r) => WriteParams (a -> r) where
writeParams m a = writeParams (m >> putStr (show a ++ " "))
However, the overlapping instance makes it inconvenient to use write in a context where the monad is a parameter m (try generalizing the signature of test).
There is a way to avoid overlapping instances by using closed type families, to define a type-level boolean that's true if and only if a given type is a function type, so that instances can match on it. See below.
It arguably just looks like more code and more complexity, but, on top of the added expressiveness (we can have a generalized test with a MonadIO constraint), I think this style makes the logic of the instances clearer in the end by isolating the pattern-matching on types.
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UndecidableInstances #-}
module Main where
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import System.IO
class WriteParams a where
writeParams :: IO () -> a
instance WriteParamsIf a (IsFun a) => WriteParams a where
writeParams = writeParamsIf
type family IsFun a :: Bool where
IsFun (m c) = IsFun1 m
IsFun a = 'False
type family IsFun1 (f :: * -> *) :: Bool where
IsFun1 ((->) b) = 'True
IsFun1 f = 'False
class (isFun ~ IsFun a) => WriteParamsIf a isFun where
writeParamsIf :: IO () -> a
instance (Show a, WriteParams r) => WriteParamsIf (a -> r) 'True where
writeParamsIf m a = writeParams (m >> putStr (show a ++ " "))
instance ('False ~ IsFun (m a), MonadIO m, a ~ ()) => WriteParamsIf (m a) 'False where
writeParamsIf = liftIO
write :: WriteParams params => params
write = writeParams (return ())
test :: (MonadIO m, IsFun1 m ~ 'False) => m ()
test = do
write 123
write ('a', 'z') True
main = test -- for ghc to compile it
Some words on UndecidableInstances
Undecidable instances are an orthogonal feature to overlapping instances, and in fact I think they're much less controversial. Whereas badly using OVERLAPPING may cause incoherence (constraints being solved differently in different contexts), badly using UndecidableInstances may at worst send the compiler in a loop (in practice GHC terminates with an error message once some threshold is reached), which is still bad but when it does manage to resolve instances it is still guaranteed that the solution is unique.
UndecidableInstances lifts limitations that made sense a long time ago, but are now too restrictive to work with the modern extensions to type classes.
In practice, most common type classes and instances defined with UndecidableInstances, including the one above, still guarantee that their resolution will terminate. In fact, there is an active proposal for a new instance termination checker. (I don't know yet whether it handles this case here.)
Here I flesh out my comment into an answer. We will keep the idea of your original class, and even the existing instances, only adding instances. Simply add one instance for each existing MonadIO instance; I'll do just one to illustrate the pattern.
instance (MonadIO m, a ~ ()) => WriteParams (ReaderT r m a) where
writeParams = liftIO
Everything works fine:
main = do
write 45
flip runReaderT () $ do
write 45 ('a', 'z')
write "hi"
This prints 45 45 ('a','z') "hi" when executed.
If you would like to reduce the writeParams = liftIO boilerplate a little bit, you can turn on DefaultSignatures and add:
class WriteParams a where
writeParams :: IO () -> a
default writeParams :: (MonadIO m, a ~ m ()) => IO () -> a
writeParams = liftIO
Then the IO and ReaderT instances are just:
instance a ~ () => WriteParams (IO a)
instance (MonadIO m, a ~ ()) => WriteParams (ReaderT r m a)
Given the following code
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE PolyKinds #-}
type family Tagged (m :: * -> *) :: k
class Example (t :: k) (a :: *) where
type Return t a
a :: (Monad m, Tagged m ~ t) => a -> m (Return t a)
data A
data A' a
data B = B
instance Example A B where
type Return A B = ()
a B = return ()
-- This is why I want a PolyKinded 't'
instance Example A' B where
type Return A' B = ()
a B = return ()
I get the type error (pointing to the line a :: (Monad m ...)
• Could not deduce: Return (Tagged m) a ~ Return t a
from the context: (Example t a, Monad m, Tagged m ~ t)
bound by the type signature for:
a :: (Example t a, Monad m, Tagged m ~ t) =>
a -> m (Return t a)
...
Expected type: a -> m (Return t a)
Actual type: a -> m (Return (Tagged m) a)
NB: ‘Return’ is a type function, and may not be injective
The type variable ‘k0’ is ambiguous
• In the ambiguity check for ‘a’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
a :: forall k (t :: k) a.
Example t a =>
forall (m :: * -> *).
(Monad m, Tagged m ~ t) =>
a -> m (Return t a)
In the class declaration for ‘Example’
I can introduce an argument to a with Proxy t and this will work provided I give the signature at the call site: test = a (Proxy :: Proxy A) B but this is what I'm looking to avoid. What I'd like is
newtype Test t m a = Test
{ runTest :: m a
} deriving (Functor, Applicative, Monad)
type instance Tagged (Test t m) = t
test :: Monad m => Test A m ()
test = a B
I want t to be found from the context Test A m () using the type instance. It seems that it should be possible given the module will compile after removing the kind annotations, PolyKinds, and the instance for A'. Where is k0 coming from?
I suppose the workaround would be to drop PolyKinds and use extra data types like data ATag; data A'Tag; data BTag etc.
This is a partial answer, only.
I tried to make the kind explicit.
type family Tagged k (m :: * -> *) :: k
class Example k (t :: k) (a :: *) where
type Return k (t :: k) (a :: *)
a :: forall m . (Monad m, Tagged k m ~ t) => a -> m (Return k t a)
And, after enabling many extensions, observed this:
> :t a
a :: (Example k (Tagged k m) a, Monad m) =>
a -> m (Return k (Tagged k m) a)
Hence, the compiler is complaining because the instance Example k (Tagged k m) a can not be determined by a,m alone. That is, we do not know how to choose k.
I guess that, technically, we might have different Example k (Tagged k m) a instances, e.g. one for k=* and another for k=(*->*).
Intuitively, knowing t should allow us to find k, but Return being non injective prevents us to find t.
I want use InputT (ReaderT Int IO) a with MonadReader Int.
I write below code to make instance MonadReader on InputT
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module HaskelineMonadReader where
import Control.Monad.Reader
import System.Console.Haskeline
instance MonadReader r m => MonadReader r (InputT m) where
ask = lift ask
local = mapInputT . local
main :: IO ()
main = putStrLn "HI"
But I get this type error.
HaskelineMonadReader.hs:11:13:
Couldn't match type ‘m0 a0 -> m0 a0’ with ‘forall b. m b -> m b’
Expected type: (m0 a0 -> m0 a0) -> InputT m a -> InputT m a
Actual type: (forall b. m b -> m b) -> InputT m a -> InputT m a
Relevant bindings include
local :: (r -> r) -> InputT m a -> InputT m a
(bound at HaskelineMonadReader.hs:11:5)
In the first argument of ‘(.)’, namely ‘mapInputT’
In the expression: mapInputT . local
How can I fix this error.
Full source code is here
This compiles:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE UndecidableInstances #-}
module HaskelineMonadReader where
import Control.Monad.Reader
import System.Console.Haskeline
instance MonadReader r m => MonadReader r (InputT m) where
ask = lift ask
local f = mapInputT (local f)
main :: IO ()
main = putStrLn "HI"
Update
Here is the error message:
Couldn't match type ‘m0 a0 -> m0 a0’
with ‘forall b. m b -> m b’
Expected type: (m0 a0 -> m0 a0) -> InputT m a -> InputT m a
Actual type: (forall b. m b -> m b) -> InputT m a -> InputT m a
So it appears that the expected type is more general because the m0 does not have to be the same as m. In the actual type the m in m b must be the same as the m in m a.
tl;dr How do I get Class to have kind (* -> *) -> Constraint?
I have the following code:
{-# LANGUAGE ConstraintKinds, TypeFamilies, RankNTypes, MultiParamTypeClasses #-}
class MonadTrans t where
type Class t
lift :: (Monad m) => m b -> t m b
generalize :: (Monad m) => t m a -> (forall m'. (Class t m') => m a)
class (Monad m) => MaybeC m where
fart :: m a
data MaybeT m a = NothingT | JustT (m a)
instance (Monad m) => Monad (MaybeT m) where
return = JustT . return
(JustT a) >>= f = a >>= f
NothingT >>= f = NothingT
instance (Monad m) => MaybeC (MaybeT m) where
fart = NothingT
instance MonadTrans MaybeT where
type Class Maybe = MaybeC --Error Is HERE
lift = JustT
generalize NothingT = fart
generalize JustT a = return a
The compiler says: "Expected kind '* -> Constraint', butMaybeC' has kind (* -> *) -> Constraint'</code>" but Class is (*->*)->Constraint.
So then I tried:
{-# LANGUAGE ConstraintKinds, TypeFamilies, RankNTypes, MultiParamTypeClasses #-}
class MonadTrans t where
type Class t :: (*->*)->Constraint
lift :: (Monad m) => m b -> t m b
generalize :: (Monad m) => t m a -> (forall m'. (Class t m') => m a)
class (Monad m) => MaybeC m where
fart :: m a
data MaybeT m a = NothingT | JustT (m a)
instance (Monad m) => Monad (MaybeT m) where
return = JustT . return
(JustT a) >>= f = a >>= f
NothingT >>= f = NothingT
instance (Monad m) => MaybeC (MaybeT m) where
fart = NothingT
instance MonadTrans MaybeT where
type Class Maybe = MaybeC
lift = JustT
generalize NothingT = fart
generalize JustT a = return a
But the compiler says "pad1.hs:4:37: Not in scope: type constructor or class 'Constraint'", so ConstraintKinds is doing nothing for me.
I want to set the type of the state parameter, of a state monad tranformer, to an associated type of that monad transformer. However, this results in constructing an infinite type,
s = AssocTyp (StateT s m) a
The intuition as to why this isn't really a problem is that,
AssocTyp (StateT s m) a = AssocTyp (StateT s' m) a
for all s and s'. However, the compiler isn't smart enough to figure this out. I've read that in some cases, a newtype can be used to avoid infinite types; how do I do it?
Here's minimized code to reproduce the question,
{-# LANGUAGE KindSignatures, TypeFamilies #-}
import Control.Monad.Trans.State
class MyMonad (m :: * -> *) where
data AssocTyp m :: * -> *
instance MyMonad m => MyMonad (StateT s m) where
data AssocTyp (StateT s m) a = StateTA (AssocTyp m a)
isAssocTyp :: Monad m => (AssocTyp m a) -> m ()
isAssocTyp x = return ()
x = do
v <- get
isAssocTyp (v)
I'm not sure what you're trying to achieve. However, the equality you state,
AssocTyp (StateT s m) a = AssocTyp (StateT s' m) a
is not true, because you are using a data family rather than a type family. The following code compiles:
{-# LANGUAGE KindSignatures, TypeFamilies #-}
import Control.Monad.Trans.State
class MyMonad (m :: * -> *) where
type AssocTyp m :: * -> *
instance MyMonad m => MyMonad (StateT s m) where
type AssocTyp (StateT s m) = AssocTyp m
isAssocTyp :: Monad m => (AssocTyp m a) -> m ()
isAssocTyp x = return ()
x :: Monad m => StateT (AssocTyp m a) m ()
x = do
v <- get
isAssocTyp v
The difference between type families and data families is that data families are injective, which means the following implication holds if DF is data family:
DF a b c = DF a' b' c' =====> a = a', b = b', c = c'
This is not what you want in your case.
Just in case a data family is what you want, I have a variant that does type check:
isAssocType' :: (Monad m1, Monad m0) => (AssocTyp m1 a) -> m0 ()
isAssocType' _ = return ()