我为我的haskell程序创建了一个eDSL,它允许定义一组存储数据的指令。这些指令可能取决于彼此的结果,甚至可以序列化为文件以进一步恢复。这是我想出的东西(相当冗长,但它是我能提取的最少的代码量来重现我的问题):
{-# LANGUAGE TypeFamilies, RankNTypes, ExistentialQuantification, FlexibleContexts #-}
module Untouchable where
import Control.Applicative
import Control.Monad.Writer
import System.Random
class ResultClass e where
type ResultMonad :: * -> *
statementAResult :: ResultMonad (e Int)
literalResult :: a -> ResultMonad (e a)
data Statement result = StatementA | StatementB (result Int)
data StatementWithResult result t = StatementWithResult (Statement result, result t)
data AnyStatementWithResult result = forall t. AnyStatementWithResult (StatementWithResult result t)
type Program result a = (ResultClass result, ResultMonad ~ m) => WriterT [AnyStatementWithResult result] m a
doA :: Program result (result Int)
doA = do
r <- lift statementAResult
tell [AnyStatementWithResult $ StatementWithResult (StatementA, r)]
return r
doB :: result Int -> Program result ()
doB arg = do
r <- lift $ literalResult ()
tell [AnyStatementWithResult $ StatementWithResult (StatementB arg, r)]
prog :: Program result ()
prog = do
x <- doA
doB x
data PrettyPrintResult x = PrettyPrintResult Int
deriving Show
instance ResultClass PrettyPrintResult where
type ResultMonad = IO
statementAResult = PrettyPrintResult <$> randomIO
literalResult _ = PrettyPrintResult <$> randomIO
printProg :: Program PrettyPrintResult a -> IO ()
printProg p = do
stmts <- execWriterT p
forM_ stmts $ \(AnyStatementWithResult (StatementWithResult (stmt, r))) -> do
putStrLn $ "Statement: " ++ case stmt of
StatementA -> "A"
StatementB arg -> "B with arg " ++ show arg
putStrLn $ "Result: " ++ show r
test :: IO ()
test = printProg prog
问题本身在于printProg
函数,该函数预计可以完美地打印eDSL块。我希望它能够为所有程序工作,而不依赖于它们的返回类型。但GHC抱怨道:
untouchable.hs: line 52, column 18:
Couldn't match type `a0' with `()'
`a0' is untouchable
inside the constraints (ResultClass PrettyPrintResult,
ResultMonad ~ m)
bound by a type expected by the context:
(ResultClass PrettyPrintResult, ResultMonad ~ m) =>
WriterT [AnyStatementWithResult PrettyPrintResult] m a0
at untouchable.hs:52:8-21
Expected type: WriterT
[AnyStatementWithResult PrettyPrintResult] m a0
Actual type: WriterT
[AnyStatementWithResult PrettyPrintResult] m ()
In the first argument of `printProg', namely `prog'
In the expression: printProg prog
如果我将printProg
的签名替换为Program PrettyPrintResult () -> IO ()
,那么所有内容都会构建,甚至可以按预期工作。
所以问题是为什么GHC无法匹配一个类型变量,实际上它被代码忽略了?我怎么能重写printProg
(或者代码的其他部分)它会接受所有程序而不管它们的结果类型如何?
答案 0 :(得分:4)
这与Program
的同义词类型中的约束有关。将printProg
的类型签名替换为 real 类型:
printProg :: WriterT [AnyStatementWithResult PrettyPrintResult] IO a -> IO ()
它将编译。必须决定约束m ~ ResultMonad
(给定m
的给定ResultMonad
和result
?),但m
是存在的,不存在其他信息帮助决定这一点。为什么错误说a
是不可触及的,我不知道。如果你想要好的类型错误,不要在类型同义词中加入约束!以下更改还可以解决您的问题:
type Program result a =
(ResultClass result) => WriterT [AnyStatementWithResult result] ResultMonad a
最后,这些问题是更大问题的症状。请注意以下事项:
*Untouchable> :t lift statementAResult
lift statementAResult
:: (ResultClass e, MonadTrans t) => t IO (e Int)
ResultMonad
立即成为IO
!这当然是错的。发生这种情况的原因是lift
有一个Monad
约束,并且无法获得Monad ResultMonad
- 因为ResultMonad
取决于类型result
,但{ {1}}中任何地方都没有ResultMonad
。从本质上讲,您的result
和result
类型已完全不相关。
简单的解决方法是使用函数依赖而不是类型族:
ResultMonad
您不需要class Monad m => ResultClass e m | e -> m where
statementAResult :: m (e Int)
literalResult :: a -> m (e a)
约束,但可能您的结果monad必须始终是monad。然后,简单地编写Monad m
类型,没有任何限制:
Program
并将所有约束放在它们出现的函数类型中:
type Program result m a = WriterT [AnyStatementWithResult result] m a
现在不再使用doA :: ResultClass result m => Program result m (result Int)
doB :: ResultClass result m => result Int -> Program result m ()
prog :: ResultClass result m => Program result m ()
-- etc ...
而不是&#34;忘记&#34;你的结果monad类型:
lift