【问题标题】:Can a typeclass constraint be used in a newtype definition?可以在新类型定义中使用类型类约束吗?
【发布时间】:2017-04-17 17:18:28
【问题描述】:

假设我们有以下newtype 定义:

newtype A = A { _run :: Monad m => A -> [Int] -> m Int }

这不能用 GHC 8.0.2 编译:

error: Not in scope: type variable ‘m’

如我所料,将m 替换为IO[] 之类的具体类型类确实可以编译。既然这样没问题,为什么GHC不允许上面的签名呢?在 newtype 中添加类型类约束有什么问题?

【问题讨论】:

  • 以下声明有效:newtype A = A { _run :: A -> [Int] -> (forall m. Monad m => m Int) }(需要{-# LANGUAGE RankNTypes #-} 扩展)。

标签: haskell typeclass newtype


【解决方案1】:

这取决于您尝试在 A 中存储的内容。

如果您尝试存储任何类似的函数,只要 mMonad,将其用作类型参数,并在您的函数中指定此约束:

newtype A m = A { _run :: A m -> [Int] -> m Int }

myFunction :: Monad m => A m -> A m

然后,您可以在构造函数中包含 A [] -> [Int] -> [Int]A Maybe -> [Int] -> Maybe Int 之类的内容。

f :: A Maybe -> [Int] -> Maybe Int
f _ (x:_) = Just x
f _ [] = Nothing

g :: Monad m => A m -> [Int] -> m Int
g _ xs = return $ head xs

myA :: A Maybe
myA = A f  -- this works

myOtherA :: Monad m => A m
myOtherA = A g  -- this works too

另一方面,如果你想强制你存储的数据是多态的,你可以使用GHC扩展RankNTypes

{-# LANGUAGE RankNTypes #-}
newtype A = A { _run :: forall m. Monad m => A -> [Int] -> m Int }

myFunction :: A -> A

你不能在构造函数中包含 A -> [Int] -> [Int]A -> [Int] -> Maybe Int 之类的东西,因为 forall 强制它们比 any Monad m 通用,所以它必须是类型为Monad m => A -> [Int] -> Maybe Int

f :: A -> [Int] -> Maybe Int
f _ (x:_) = Just x
f _ [] = Nothing

g :: Monad m => A -> [Int] -> m Int
g _ xs = return $ head xs

myA :: A
myA = A f  -- this does not work ; it wants forall m. Monad m => m, not []

myOtherA :: A
myOtherA = A g  -- this does work

仅当您打算为 A 值使用不同的特定 Monad 实例时,这才会真正有用。例如,镜头就是这样工作的,它使用不同的函子对镜头做不同的事情。

【讨论】:

  • 感谢您阐明如何编写构造函数并建立与 Lenses 的连接,这让我很满意。
【解决方案2】:

这是可能的:

{-# LANGUAGE RankNTypes #-}
newtype A = A { _run :: forall m. Monad m => A -> [Int] -> m Int }

很难说你想做什么,但这不是很有用。 A 类型的任何值都需要适用于所有 monad(您无法选择)。

这也是可能的,但有相同的限制:

{-# LANGUAGE GADTs #-}
data A where A :: Monad m => (A -> [Int] -> m Int) -> A

但也许你的意思更像

newtype A m = A { _run :: A m -> [Int] -> m Int }

这允许使用不同 monad 的不同类型 A 的值。

【讨论】:

  • 谢谢,这对我来说更有意义了。 RankNTypes 方法和 GADT 方法之间有什么有意义的区别吗?
  • @BillM 除了一个错字(已修复)并且没有像 _run 这样的名字?没有。
  • @ephemient 我在 GHCi 中尝试过,在下面我对f 的定义中,GADTs 方式有效,但 RankNTypes 方式出现错误。
  • 我不确定如何编辑其他人的答案,但我在这方面进行了编辑。
  • datanewtype 版本之间存在巨大差异。它们的含义完全不同。 data 版本创建了一个存在主义。无论何时应用构造函数,都会丢失类型m,并且永远无法恢复。 newtype 版本是通用的。构造函数持有的值必须在所有m 中具有Monad 实例的多态性。当你提取它时,你可以将它用于任何m
【解决方案3】:

当您创建A 类型的数据时,GHC 如何知道要使用Monad 的哪个实例?

或者,换句话说,类型变量m 不在类型定义左侧的范围内。这意味着它不知道m 应该是什么,也无法解决。这是隐含的。

我相信您可以通过某种方式使用扩展来做您想做的事情,可能使用显式的 forall。 (RankNTypes 扩展),但是我们需要了解更多信息。

【讨论】:

    猜你喜欢
    • 2013-11-05
    • 2011-12-11
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-04
    • 1970-01-01
    相关资源
    最近更新 更多