As per the title I'm curious as to why GHC is emitting a warning about a redundant constraint when its removal makes the code no longer compile.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
module Lib where
import Protolude hiding (from, try)
import Control.Exception.Safe
import Database.Esqueleto
import Database.Persist.TH
newtype PingId =
PingId Int
deriving (Enum, Eq, Integral, Num, Ord, Real, Show)
data Ping = Ping
{
} deriving (Show)
share [mkPersist sqlSettings] [persistLowerCase|
DbPing sql=pings
|]
pingToDbPing :: Ping -> DbPing
pingToDbPing _ = undefined
dbPingToPing :: DbPing -> Either Text Ping
dbPingToPing _ = undefined
class (PersistEntity a, ToBackendKey SqlBackend a) =>
FromPersistEntity a b | a -> b where
fromPersistEntity :: a -> Either Text b
instance FromPersistEntity DbPing Ping where
fromPersistEntity = dbPingToPing
type family ToKey a :: * where
ToKey PingId = DbPingId
findById
:: forall m key record val.
( Integral key
, Key record ~ ToKey key
, FromPersistEntity record val
, MonadCatch m
, MonadIO m
, MonadReader DbConfig m
)
=> key -> m (Either Text (Maybe val))
findById key = do
maybeRetOrErr <-
try
(liftIO . evaluate =<<
runDB
(select $
from $ \table -> do
where_
(table ^. persistIdField ==. val (toSqlKey . fromIntegral $ key))
return table))
case maybeRetOrErr of
Left (e :: SomeException) -> return . Left . toS . displayException $ e
Right [] -> return . Right $ Nothing
Right [ret :: Entity record] ->
return . fmap Just . fromPersistEntity . entityVal $ ret
Right _ -> return . Left $ "impossible happened, more than one result"
data DbConfig = DbConfig
{ dbConnectionPool :: ConnectionPool
}
runDB
:: (MonadIO m, MonadReader DbConfig m)
=> SqlPersistT IO b -> m b
runDB q = do
pool <- asks dbConnectionPool
liftIO $ runSqlPool q pool
test :: IO ()
test = do
let dbConfig = DbConfig undefined
flip runReaderT dbConfig $ do
pingOrErr <- findById (PingId 1)
print pingOrErr
and produces the following warning:
/home/ppb/Code/haskell/test/src/Lib.hs:49:1: warning: [-Wredundant-constraints]
• Redundant constraint: Key record ~ ToKey key
• In the type signature for:
findById :: (Integral key, Key record ~ ToKey key,
FromPersistEntity record val, MonadCatch m, MonadIO m,
MonadReader DbConfig m) =>
key -> m (Either Text (Maybe val))
and removing the constraint results in the following error:
/home/ppb/Code/haskell/test/src/Lib.hs:50:6: error:
• Could not deduce (FromPersistEntity record0 val)
from the context: (Integral key,
FromPersistEntity record val,
MonadCatch m,
MonadIO m,
MonadReader DbConfig m)
bound by the type signature for:
findById :: (Integral key, FromPersistEntity record val,
MonadCatch m, MonadIO m, MonadReader DbConfig m) =>
key -> m (Either Text (Maybe val))
at src/Lib.hs:(50,6)-(57,39)
The type variable ‘record0’ is ambiguous
• In the ambiguity check for ‘findById’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
In the type signature:
findById :: forall m key record val.
(Integral key,
FromPersistEntity record val,
MonadCatch m,
MonadIO m,
MonadReader DbConfig m) =>
key -> m (Either Text (Maybe val))
I'm using GHC 8.0.1 and compiling with -Wall.
Is there any way I could restructure the code to avoid the warning? Or if that's not possible is there a way to silence the warning on per-function basis, rather than across the whole module with OPTIONS_GHC?
EDIT: compiling with GHC 8.0.2 no longer produces a warning.
Related
I have a typeclass with a default implementation, and would like to provide a simple way to derive the typeclass if a user wants to use their custom monad.
Here's a solution someone else provided me:
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}
import Control.Monad.Cont (MonadIO, MonadTrans (lift))
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT))
----------------- My module's definitions -----------------
class Monad m => MonadFoo m where
foo :: m ()
instance MonadFoo IO where
foo = putStrLn "Hello world!"
instance MonadFoo m => MonadFoo (ReaderT r m) where
foo = lift foo
------------------------------------------------------------
------ The user's custom monad + instance definitions ------
data AppEnv = AppEnv
newtype AppM a = AppM
{ runAppM :: ReaderT AppEnv IO a
}
deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)
deriving via (ReaderT AppEnv IO) instance MonadFoo AppM
------------------------------------------------------------
-- Example usage
program :: IO ()
program = runReaderT (runAppM foo) AppEnv
> program
"Hello world!"
If my typeclass uses a type family, I'm unable to use deriving via. For example:
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Cont (MonadIO, MonadTrans (lift))
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT))
----------------- My module's definitions -----------------
class Monad m => MonadFoo ctx m where
type FooCtx ctx
foo :: m (FooCtx ctx)
data DummyCtx = DummyCtx
instance MonadFoo DummyCtx IO where
type FooCtx DummyCtx = ()
foo :: IO ()
foo = putStrLn "hello"
instance MonadFoo DummyCtx m => MonadFoo DummyCtx (ReaderT r m) where
type FooCtx DummyCtx = ()
foo :: ReaderT r m ()
foo = lift $ foo #DummyCtx
------------------------------------------------------------
------ The user's custom monad + instance definitions ------
data AppEnv = AppEnv
newtype AppM a = AppM
{ runAppM :: ReaderT AppEnv IO a
}
deriving (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)
deriving via (ReaderT AppEnv IO) instance MonadFoo DummyCtx AppM
The last line doesn't compile:
[typecheck] [E] • Can't make a derived instance of
~ ‘MonadFoo DummyCtx AppM’ with the via strategy:
~ the associated type ‘FooCtx’ is not parameterized over the last type
~ variable
~ of the class ‘MonadFoo’
~ • In the stand-alone deriving instance for ‘MonadFoo DummyCtx AppM’
How do I get the deriving via clause to compile when the typeclass has a type family?
As the error message says, your associated type FooCtx depends only on ctx, but not on m, thus creating a potential for ambiguity like this:
instance MonadFoo X A where
type FooCtx X = Int
...
instance MonadFoo X B where
type FooCtx X = String
...
Now it's ambiguous whether FooCtx X evaluates to Int or to String.
To fix that, just add m to parameters of FooCtx:
class Monad m => MonadFoo ctx m where
type FooCtx ctx m
...
instance MonadFoo DummyCtx IO where
type FooCtx DummyCtx IO = ()
...
instance MonadFoo DummyCtx m => MonadFoo DummyCtx (ReaderT r m) where
type FooCtx DummyCtx (ReaderT r m) = ()
...
(I figured I'd add this as an answer since it turned out to be that simple after all)
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)
I'm working with the following code:
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Lib where
import Protolude hiding (from, try)
import Control.Exception.Safe
import Database.Esqueleto
import Database.Persist.TH
newtype PingId =
PingId Int
deriving (Enum, Eq, Integral, Num, Ord, Real, Show)
data Ping = Ping
{
} deriving (Show)
share [mkPersist sqlSettings] [persistLowerCase|
DbPing sql=pings
|]
pingToDbPing :: Ping -> DbPing
pingToDbPing _ = undefined
class (PersistEntity b, ToBackendKey SqlBackend b) =>
ToPersistEntity a b | a -> b where
toPersistEntity :: a -> b
type family FromKey a :: * where
FromKey DbPingId = PingId
instance ToPersistEntity Ping DbPing where
toPersistEntity = pingToDbPing
data DbConfig = DbConfig
{ dbConnectionPool :: ConnectionPool
}
runDB
:: (MonadIO m, MonadReader DbConfig m)
=> SqlPersistT IO b -> m b
runDB q = do
pool <- asks dbConnectionPool
liftIO $ runSqlPool q pool
saveDB
:: ( Exception e
, ToPersistEntity value record
, Num (FromKey (Key record))
, MonadCatch m
, MonadIO m
, MonadReader DbConfig m
, FromKey (Key record) ~ key
)
=> value -> m (Either e key)
saveDB x =
fmap (fromIntegral . fromSqlKey) <$>
try (liftIO . evaluate =<< runDB (insert (toPersistEntity x)))
In short I have domain objects (e.g Ping), ToPersistEntity type class to convert those into their database representation, FromKey type family to map between database / domain object id's, and saveDB function that works for anything satisfying those criteria.
As the next step I wish to create a type class to abstract persisting the objects so that my other functions can be polymorphic in MonadStore and I can have different instances for use in my application and for testing. For use in my app I would like to reuse the generic saveDB function. Ideally I would like to have something along the lines of:
class (Monad m) =>
MonadStore m where
save
:: (Exception e)
=> value -> m (Either e key)
with save type class method delegating to saveDB when used in my application. That works fine if I make type class methods specialised to specific types such as Ping and PingId.
class (Monad m) =>
MonadStore m where
savePing
:: (Exception e)
=> Ping -> m (Either e PingId)
instance (MonadCatch m, MonadIO m, Monad m, MonadReader DbConfig m) =>
MonadStore m where
savePing = saveDB
but has the drawback of having to define a new method for each of my objects.
Trying to define my desired instance:
instance (MonadCatch m, MonadIO m, Monad m, MonadReader DbConfig m) =>
MonadStore m where
save = saveDB
produces a type error:
/home/ppb/Code/haskell/test/src/Lib.hs:86:10: error:
• Couldn't match type ‘key’ with ‘FromKey (Key record0)’
arising from a use of ‘saveDB’
‘key’ is a rigid type variable bound by
the type signature for:
save :: forall e value key.
Exception e =>
value -> m (Either e key)
at src/Lib.hs:86:3
• In the expression: saveDB
In an equation for ‘save’: save = saveDB
In the instance declaration for ‘MonadStore m’
• Relevant bindings include
save :: value -> m (Either e key) (bound at src/Lib.hs:86:3)
and no matter what I try I can't figure out how to make a valid instance where save delegates to saveDB. Is there any way I could make this work? I'm also interested to better understand the error the compiler produces.
Given the code below which looks up type-specific information in Data.HashMap for a type, is it possible to define a new function getMapVal2 as documented in the comments, to build the TypeKey argument given the type?
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DataKinds #-}
import Data.Monoid ((<>))
import Data.Proxy (Proxy(Proxy))
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import qualified Data.HashMap.Strict as Map (HashMap, empty, insert, lookup)
import Data.Dynamic
import GHC.Generics
import Data.Maybe (fromJust, isNothing, maybe)
type family TypeKey (a :: *) :: Symbol where
TypeKey Int = "int"
TypeKey T = "trec"
data T = T { aInt :: Int} deriving (Show, Generic, Typeable)
extract ::(s ~ TypeKey a, Typeable a, KnownSymbol s) => Maybe Dynamic -> Maybe a
extract dyn = if (isNothing dyn) then Nothing else fromDynamic . fromJust $ dyn
getMapVal :: (s ~ TypeKey a, Typeable a, KnownSymbol s) => Map.HashMap String Dynamic -> String -> Maybe a
getMapVal m k = extract $ Map.lookup k m
{-- How do we get the TypeKey lookup for type a?
getMapVal2 :: (s ~ TypeKey a, Typeable a, KnownSymbol s) => Map.HashMap String Dynamic -> a -> Maybe a
getMapVal2 m ty = extract $ Map.lookup (symbolVal (Proxy :: Proxy (TypeKey ???))) m
--}
main = do
let map = Map.insert (symbolVal (Proxy :: Proxy (TypeKey T))) (toDyn $ T {aInt=5}) Map.empty -- we insert some value in hashmap for type T - it is of same type
val = getMapVal map (symbolVal (Proxy :: Proxy (TypeKey T))) :: Maybe T -- now let us retrieve the value in map for Type T. We pass the SymbolVal ourselves
--val = getMapVal2 map (T {aInt = 2}) -- now we want to lookup map value given something of a type T. Need getMapVal2 to build symbolval given the input type
print $ maybe "" show val -- prints value stored in Hashmap for type T which is: T {aInt=5}
This is just a toy code to test passing type specific configuration at run-time via Data.HashMap to a polymorphic function that acts on types of a typeclass.
Use the ScopedTypeVariables extension. This allows you to refer to forall-bound type variables in the body of the definition in which they are bound.
{-# LANGUAGE ScopedTypeVariables #-}
getMapVal2 :: forall a s. (s ~ TypeKey a, Typeable a, KnownSymbol s) => Map.HashMap String Dynamic -> a -> Maybe a
getMapVal2 m ty = extract $ Map.lookup (symbolVal (Proxy :: Proxy (TypeKey a))) m
I want to use string literal as Traversal, but I am a bit lost in types.
Is it possible to create this instance?
import Control.Lens
import Data.Aeson
import Data.Aeson.Lens
import Data.String
import Data.Default
{- Having:
key' :: AsValue t => Text -> Traversal' t (Maybe Value)
_JSON :: (ToJSON a, FromJSON a) => Traversal' t a
-}
instance (AsValue t, FromJSON v, ToJSON v, Default v) => IsString (Traversal' t v) where
fromString k = key' (fromString k) . non (toJSON def) . _JSON
To achieve something like this inside State monad:
"some-key" .= (3 :: Int)
Problem with universally quantified type instances. Thanks!
I couldn't get your code to compile, but that shouldn't matter. I assume that you have a function of type
fromStringTraversal :: (AsValue t, FromJSON v, ToJSON v, Default v)
=> String -> Traversal' t v
fromStringTraversal = undefined
Then to write your instance, simply inline the definition of Traversal' into the instance head. This works because any type variables in an instance are universally quantified over implicitly anyways.
{-# LANGUAGE RankNTypes, FlexibleInstances, GADTs #-}
instance (a ~ a', b ~ b', AsValue b, Default a, FromJSON a, ToJSON a, Applicative f)
=> IsString ((a -> f a') -> b -> f b') where
fromString = fromStringTraversal
The a ~ a', b ~ b' constraints could be moved from the context to the instance head, but this way gives better type inference. Then
{-# LANGUAGE OverloadedStrings, NoMonomorphismRestriction #-}
-- Infered type:
-- test :: (AsValue s, MonadState s m) => m ()
test = "some-key" .= (3 :: Int)