【问题标题】:Existential types and monad transformers存在类型和单子变换器
【发布时间】:2011-12-22 18:49:08
【问题描述】:

上下文:我正在尝试生成一个错误 monad,它还跟踪警告列表,如下所示:

data Dangerous a = forall e w. (Error e, Show e, Show w) =>
    Dangerous (ErrorT e (State [w]) a)

Dangerous a 是导致 (Either e a, [w]) 的操作,其中 e 是可显示的错误,w 是可显示的。

问题是,我似乎无法真正运行它,主要是因为我不太了解存在类型。观察:

runDangerous :: forall a e w. (Error e, Show e, Show w) =>
    Dangerous a -> (Either e a, [w])
runDangerous (Dangerous f) = runState (runErrorT f) []

这不会编译,因为:

Could not deduce (w1 ~ w)
from the context (Error e, Show e, Show w)
...
`w1' is a rigidtype variable bound by
    a pattern with constructor
    Dangerous :: forall a e w.
                 (Error e, Show e, Show w) =>
                 ErrorT e (State [w]) a -> Dangerous a
...
`w' is a rigid type variable bound by
    the type signature for
    runDangerous :: (Error e, Show e, Show w) =>
                    Dangerous a -> (Either e a, [w])

我迷路了。 w1是什么?为什么我们不能推断它是~ w

【问题讨论】:

    标签: haskell monads monad-transformers existential-type


    【解决方案1】:

    存在主义可能不是你想要的;没有办法“观察”绑定到 Dangerous a 值中的 ew 的实际类型,因此您完全受限于 ErrorShow 提供给您的操作。

    换句话说,关于w,你唯一知道的就是你可以把它变成String,所以它也可能只是一个String(忽略优先级以简化事情),并且唯一你对e的了解是,你可以把它变成String,你可以把Strings变成它,并且你有它的独特价值(noMsg)。无法断言或检查这些类型是否与其他类型相同,因此一旦将它们放入 Dangerous,就无法恢复这些类型可能具有的任何特殊结构。

    错误消息的意思是,本质上,您的runDangerous 类型声称您可以将Dangerous 转换为(Either e a, [w]) 用于任何 ew有相关实例。这显然不是真的:您只能将Dangerous 转换为那种类型,以便one 选择ew:创建它的那个类型。 w1 只是因为您的 Dangerous 类型是使用类型变量 w 定义的,runDangerous 也是如此,因此 GHC 重命名其中一个以避免名称冲突。

    你需要给runDangerous的类型如下所示:

    runDangerous
      :: (forall e w. (Error e, Show e, Show w) => (Either e a, [w]) -> r)
      -> Dangerous a -> r
    

    其中,给定一个函数,该函数将接受ew任何 选择类型的值w,只要它们具有给定的实例,以及Dangerous a , 产生该函数的结果。这很难理解!

    实现很简单

    runDangerous f (Dangerous m) = f $ runState (runErrorT m) []
    

    这是对您的版本的一个微不足道的更改。如果这对您有用,那就太好了;但我怀疑存在主义是实现你想做的任何事情的正确方法。

    请注意,您需要{-# LANGUAGE RankNTypes #-} 来表示runDangerous 的类型。或者,您可以为您的结果类型定义另一个存在:

    data DangerousResult a = forall e w. (Error e, Show e, Show w) =>
       DangerousResult (Either e a, [w])
    
    runDangerous :: Dangerous a -> DangerousResult a
    runDangerous (Dangerous m) = DangerousResult $ runState (runErrorT m) []
    

    并使用case 提取结果,但你必须小心,否则GHC 会开始抱怨你让ew 逃逸——这相当于试图通过不充分的runDangerous 的另一种形式的多态函数;即需要更多限制 ew 超出 runDangerous 保证的类型。

    【讨论】:

    • 有没有更好(或更惯用的)方法来做到这一点?我真的只是想要一个带有一些警告的错误单子。
    • 为什么不直接将类型定义为Dangerous e w a?如果我了解您想要实现的目标(我很可能不会),这里就不需要存在主义了。
    • 我有几个模块都抛出自己的错误和警告,并且它们在顶层处理。顶层只需要打印它们,但是在选项模块中说Dangerous OptError OptWarning [Option] 和在模板文件中说Dangerous TemplateError TemplateWarning Template 很烦人,因为它们都只是shown。我正在尝试删除很多样板并学习一些东西,这当然不是必需的。
    • @So8res 是的,停止尝试使用存在主义。要么在类型构造函数中包含类型变量,要么选择具体类型。或者也许每个。看起来您确实希望 e 成为类型构造函数中的类型变量,并且您确实希望 w 只是 String。
    • @So8res 功能应该在它们更好地表达您的实际意思时使用。如果你希望一个类型是多态的,可以让你恢复使用的类型,你需要给它一个类型变量。据我所知,这就是 e 的含义。如果你想要字符串,就说字符串。在任何可用的意义上,这就是 w 的全部内容。填入无法阐明您的意思的随机特征是一种不好的方法,这一点不应该引起争议。
    【解决方案2】:

    好吧,我想我知道我在挣扎什么了:

    data Failure = forall e. (Error e, Show e) => Failure e
    
    data Warning = forall w. (Show w) => Warning w
    
    class (Monad m) => Errorable m where
        warn :: (Show w) => w -> m ()
        throw :: (Error e, Show e) => e -> m ()
    
    instance Errorable Dangerous where
        warn w = Dangerous (Right (), [Warning w])
        throw e = Dangerous (Left $ Failure e, [])
    

    instance Monad Dangerousdata DangerousT 也有帮助。)

    这允许您拥有以下代码:

    foo :: Dangerous Int
    foo = do
        when (badThings) (warn $ BadThings with some context)
        when (worseThings) (throw $ BarError with other context)
    
    data FooWarning = BadThings FilePath Int String
    instance Show FooWarning where
    ...
    

    然后在您的主模块中,您可以定义 Show FailureError FailureShow Warning 的自定义实例,并有一个集中的方式来格式化您的错误消息,例如

    instance Show Warning where show (Warning s) = "WARNING: " ++ show s
    instance Show Failure where ...
    
    let (result, warnings) = runDangerous function
    in ...
    

    在我看来,这是一种处理错误和警告的非常酷的方法。我有一个类似这样的工作模块,现在我要去完善它,也许把它放在hackage上。建议表示赞赏。

    【讨论】:

    • 对不起,data Warning = forall w. (Show w) => Warning w 等价于data Warning = Warning String;您不妨在警告值上调用warn 函数show。存在主义真的不是你想要的
    • 感谢您尝试应用一个很酷的类型系统功能,但它在这里产生的唯一效果是在不添加任何功能的情况下使代码更加复杂。
    • 这里还有一些材料可以帮助解释:12 — 我已经看到了一个很好的解释,说明为什么不使用类似 data Foo = forall a. (Show a) => Foo a 的东西,但不幸的是不能立即找到链接。
    • 谢谢。我开始意识到为什么存在主义不是我想要的。 (我可能有点固执。)(有意的)要点是允许自定义instance Show Warnings,允许您附加“警告:”或“警告:”或任何内容,无需担心稳定性。这当然会导致孤立实例警告,这导致我阅读了一堆关于孤立实例的内容,我开始明白这一点。 (我很固执,不是愚蠢的\笑脸)。
    • 是的,这个解决方案对我来说似乎不错——尽管如果我不得不挑一个小窍门,那就是Show 实例应该产生有效的 Haskell 代码,所以你技术上想要你自己的类型类:)
    猜你喜欢
    • 2014-08-03
    • 1970-01-01
    • 2015-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多