How can I make this code more polymorphic? - haskell

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.

Related

Is it possible to define my own Show class and reuse instances of TextShow and Prelude.Show?

I'm trying to define my own Show class named MyShow, and want to 'steal' instances from TextShow and Prelude.Show, and prefer the former one if both exists.
I tried following (showbPrec things is simplified to be focused):
{-# LANGUAGE UndecidableInstances #-}
import Data.Text (Text, pack, unpack)
import qualified Prelude as P
import qualified TextShow as T
class MyShow a where
showText :: a -> Text
instance {-# OVERLAPPING #-} TextShow a => MyShow a where
showText x = T.showt x
instance {-# OVERLAPPABLE #-} P.Show a => MyShow a where
showText x = pack (P.show x)
But I'm telled that Duplicate instance declarations is not allowed, so is there any way to define my own Show and reuse instances from others?
This won't let you branch on which instance is available, that goes against the open-world assumption where adding a new instance should not change the behaviour of your program. There are ways of doing this kind of branching, one approach is the compiler plugin IfSat but this is not the normal way of doing it.
newtype AsShow a = AsShow a
instance P.Show a => MyShow (AsShow a) where
showText :: AsShow a -> Text
showText (AsShow a) = pack (P.show a)
newtype AsTextShow a = AsTextShow a
instance TextShow a => MyShow (AsTextShow a) where
showText :: AsTextShow a -> Text
showText (AsTextShow a) = showt a
With these newtypes you can then derive instances for your MyShow using DerivingVia
{-# Language DerivingVia #-}
{-# Language StandaloneDeriving #-}
-- standalone deriving for types defined elsewhere
deriving via AsShow Int instance MyShow Int
deriving via AsShow Integer instance MyShow Integer
data BOOL = FALSE | TRUE
deriving
stock P.Show
deriving MyShow
via AsShow BOOL

Using "deriving via" with a type family

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)

Error "Could not deduce instance"; two contexts?

I was reading https://www.reddit.com/r/haskell/comments/gb7f9l/design_trade_offs_for_different_application/fp5kmjv and trying something out. I ran into an error I don't understand.
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableSuperClasses #-}
import Control.Monad
import Control.Monad.Reader.Class
import Data.Kind
newtype Y = Y { unY :: Int }
data Services c = Services
{ service1 :: forall m. c m => m Int
, object :: forall m. c m => m Y
}
myServices :: c ~ (MonadReader (Services c)) => Services c
myServices = Services
{ service1 = unY <$> join (asks object)
, object = pure $ Y 2
}
The errors all go eg.
* Could not deduce (Functor m) arising from a use of `<$>'
from the context: c ~ MonadReader (Services c)
bound by the type signature for:
myServices :: forall (c :: (* -> *) -> Constraint).
(c ~ MonadReader (Services c)) =>
Services c
or from: c m
bound by a type expected by the context:
forall (m :: * -> *). c m => m Int
However, it seems to me that
(c m, c ~ (MonadReader (Services c))
should mean
MonadReader (Services c) m
and as
Monad m => MonadReader r m where ...
then
Monad m -- implies Applicative m implies Functor m
What have I misunderstood? Is the fact that the two contexts are presented separately relevant somehow - why doesn't it have access to both contexts?

Why is GHC emitting incorrect "redundant constraint" warning here?

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.

Typeable instance for Constraint tupling

I'm trying to derive a Typeable instance for tupled constraints. See the following code:
{-# LANGUAGE ConstraintKinds, GADTs #-}
{-# LANGUAGE DataKinds, PolyKinds, AutoDeriveTypeable #-}
{-# LANGUAGE StandaloneDeriving, DeriveDataTypeable #-}
import Data.Proxy
import Data.Typeable
data Foo (p :: (*, *))
data Dict ctx where
Dict :: ctx => Dict ctx
deriving (Typeable)
deriving instance Typeable '(,)
deriving instance Typeable Typeable
deriving instance Typeable Show
works :: IO ()
works = print (typeRep (Proxy :: Proxy (Foo '(Bool, Char))))
alsoWorks :: IO ()
alsoWorks = print (typeRep (Dict :: Dict (Show Bool)))
fails :: IO ()
fails = print (typeRep (Dict :: Dict (Show Bool, Typeable Bool)))
main :: IO ()
main = works >> alsoWorks >> fails
If you compile this with -fprint-explicit-kinds, the following error is given:
No instance for (Typeable
(Constraint -> Constraint -> Constraint) (,))
Is there a way to derive such an instance? Everything I try refuses to disambiguate from the ★ -> ★ -> ★ constructor.
GHC can not currently make a Typeable instance, or any other instance, for (,) :: Constraint -> Constraint -> Constraint. The type constructor (,) only has kind * -> * -> *. There is no type constructor for products of the kind Constraint -> Constraint -> Constraint. The constructor (,) is overloaded to construct both tuples and products of Constraints, but has no corresponding type constructor when used to make a product of Constraints.
If we did have a type constructor for products of Constraints we should be able to define an instance as follows. For this, we'll pretend (,) is also a type constructor with kind (,) :: Constraint -> Constraint -> Constraint. To define an instance for it, we'd use KindSignatures and import GHC.Exts.Constraint to be able to talk about the kind of constraints explicitly
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE KindSignatures #-}
import GHC.Exts (Constraint)
import Data.Typeable
deriving instance Typeable ((,) :: Constraint -> Constraint -> Constraint)
If we do this now, it results in the following error, due to the kind of the (,) type constructor.
The signature specified kind `Constraint
-> Constraint -> Constraint',
but `(,)' has kind `* -> * -> *'
In the stand-alone deriving instance for
`Typeable ((,) :: Constraint -> Constraint -> Constraint)'
The constraints package also works with products of constraints, and includes the following note.
due to the hack for the kind of (,) in the current version of GHC we can't actually make instances for (,) :: Constraint -> Constraint -> Constraint
I presume the hack Edward Kmett is referring to is the overloading of the (,) constructor for Constraints without a corresponding type constructor.
It seems that it is not currently possible. There's a revealing comment in the latest version of constraint:
due to the hack for the kind of (,) in the current version of GHC we can't actually make instances for (,) :: Constraint -> Constraint -> Constraint

Resources