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
If you're willing to sacrifice the implicit ordering and use explicit accessors instead, your Action '[Int, Int] could be implemented using ReaderT (HList '[Int, Int]). If you use an existing library like vinyl that provides lenses, you could write something like this:
-- Implemented with pseudo-vinyl
-- X and Y are Int fields, with accessors xField and yField
addTwo :: ReaderT (PlainRec '[X, Y]) Output ()
addTwo = do
x <- view (rGet xField)
y <- view (rGet yField)
lift . output $ show (x + y) -- output :: String -> Output ()
Type safety is enforced by constraint propagation: rGet xField introduces a requirement that X be a member of the record.
For a simpler illustration without the type-level machinery, compare:
addTwo :: ReaderT (Int, Int) IO ()
addTwo = do
x <- view _1
y <- view _2
lift . putStrLn $ show (x + y)
We lose the ordering property, which is significant loss, particularly if the ordering is meaningful, e.g. represents the order of user interaction.
Furthermore, we now have to use runReaderT (~ eval). We can't, say, interleave user input with output.