在专门化未使用的类型变量时不可触摸的类型

时间:2014-10-30 12:58:30

标签: haskell compiler-errors type-families

我为我的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(或者代码的其他部分)它会接受所有程序而不管它们的结果类型如何?

1 个答案:

答案 0 :(得分:4)

这与Program的同义词类型中的约束有关。将printProg的类型签名替换为 real 类型:

printProg :: WriterT [AnyStatementWithResult PrettyPrintResult] IO a -> IO () 

它将编译。必须决定约束m ~ ResultMonad(给定m的给定ResultMonadresult?),但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。从本质上讲,您的resultresult类型已完全不相关。

简单的解决方法是使用函数依赖而不是类型族:

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