I\'ve been using a free monad to build a DSL. As part of the language, there is an input command, the goal is to reflect what types are expected by the input primit
EDIT: I have posted a more general alternative answer. I leave this answer here for now since it may be an useful example for constructing the target monad by hand.
My solution does what OP asked for (though it involves manual monad instance writing, so there's room for refinement certainly).
The effect-monad package (which OP mentioned) already contains an effect that handles reading from a HList. It's called ReadOnceReader. However, we also need a Writer effect for Output, and it seems to me that the library doesn't let us combine these two.
We can still take the idea of ReadOnceReader and manually write an AST for the desired language. The AST should be an indexed monad, of course. It would be neat if we could also do this through an indexed free monad or operational monad. I haven't had success with free monads thus far. I might update my answer after I looked at operational monads.
Preliminaries:
{-# LANGUAGE
RebindableSyntax, DataKinds, ScopedTypeVariables,
GADTs, TypeFamilies, TypeOperators,
PolyKinds, StandaloneDeriving, DeriveFunctor #-}
import Prelude hiding (Monad(..))
data HList (xs :: [*]) where
Nil :: HList '[]
(:>) :: x -> HList xs -> HList (x ': xs)
infixr 5 :>
type family (++) (xs :: [*]) (ys :: [*]) where
'[] ++ ys = ys
(x ': xs) ++ ys = x ': (xs ++ ys)
Indexed monads must provide a way to combine (Plus) indices, with identity (Unit). In short, indices should be monoids.
class IxMonad (m :: k -> * -> *) where
type Unit m :: k
type Plus m (i :: k) (j :: k) :: k
return :: a -> m (Unit m) a
(>>=) :: m i a -> (a -> m j b) -> m (Plus m i j) b
fail :: m i a
The type of Input is of interest here: we prepend the input type to the resulting index of the next computation:
data Action i a where
Return :: a -> Action '[] a
Input :: (x -> Action xs a) -> Action (x ': xs) a
Output :: String -> Action i a -> Action i a
deriving instance Functor (Action i)
The IxMonad instance and the smart constructors are fully standard, and the eval function is also implemented straightforwardly.
instance IxMonad Action where
type Unit Action = '[]
type Plus Action i j = i ++ j
return = Return
Return a >>= f = f a
Input k >>= f = Input ((>>= f) . k)
Output s nxt >>= f = Output s (nxt >>= f)
fail = undefined
input :: Action '[a] a
input = Input Return
output :: String -> Action '[] ()
output s = Output s (Return ())
eval :: Action xs a -> HList xs -> [String]
eval (Return a) xs = []
eval (Input k) (x :> xs) = eval (k x) xs
eval (Output s nxt) xs = s : eval nxt xs
Now everything works as desired:
concat' :: Action '[String, String] ()
concat' = do
(x :: String) <- input
(y :: String) <- input
output $ x ++ " " ++ y
main = print $ eval concat' ("a" :> "b" :> Nil)
-- prints ["a b"]