一种获得样本效果的方法
我首先想描述一种方法来实现您的目标。让我们再次看一下您的上一个代码示例:
-- Note, Frob is an instance of class Frobbable
getFrobbable :: (Frobbable a) => Frob -> a
getFrobbable x = x
这本质上是一个铸造操作。它需要一个 Frob 并且只是忘记了它是什么,只保留您拥有 Frobbable 实例的知识。
在 Haskell 中有一个成语可以做到这一点。它需要 GHC 中的 ExistentialQuantification 扩展。下面是一些示例代码:
{-# LANGUAGE ExistentialQuantification #-}
module Foo where
class Frobbable a where
getInt :: a -> Int
data Frob = Frob Int
instance Frobbable Frob where
getInt (Frob i) = i
data FrobbableWrapper = forall a . Frobbable a => FW a
instance Frobbable FrobbableWrapper where
getInt (FW i) = getInt i
关键部分是FrobbableWrapper 数据结构。有了它,您可以编写以下版本的getFrobbable 转换函数:
getFrobbable :: Frobbable a => a -> FrobbableWrapper
getFrobbable x = FW x
如果您想要一个异构列表,其元素共享一个公共类型类,即使它们可能不共享一个公共类型,这个习语很有用。例如,虽然Frobbable a => [a] 不允许您混合Frobbable 的不同实例,但[FrobbableWrapper] 列表肯定可以。
为什么您发布的代码不允许
现在,为什么不能按原样编写铸造操作?如果您原来的 getFrobbable 函数被允许进行类型检查,这一切都是关于可以完成的。
方程getFrobbable x = x 真的应该被认为是一个方程。 x 没有被以任何方式修改;因此,它的类型也不是。这样做的原因如下:
让我们将getFrobbable 与另一个对象进行比较。考虑
anonymousFrobbable :: Frobbable a => a
anonymousFrobbable = undefined
(当你想真正推动你的直觉时,涉及undefined 的代码是尴尬行为的重要来源。)
现在假设有人过来介绍了一个数据定义和一个类似的函数
data Frob2 = Frob2 Int Int
instance Frobbable Frob2 where
getInt (Frob2 x y) = y
useFrobbable :: Frob2 -> [Int]
useFrobbable fb2 = []
如果我们跳入 ghci,我们可以执行以下操作:
*Foo> useFrobbable anonymousFrobbable
[]
没问题:anonymousFrobbable 的签名意味着“你选择一个 Frobbable 实例,我就假装我属于那种类型。”
现在,如果我们尝试使用您的 getFrobbable 版本,调用类似
useFrobbable (getFrobbable someFrob)
会导致以下情况:
-
someFrob 必须是 Frob 类型,因为它被分配给 getFrobbable。
-
(getFrobbable someFrob) 必须是 Frob2 类型,因为它被提供给 useFrobbable
- 但是通过等式
getFrobbable someFrob = someFrob,我们知道getFrobbable someFrob 和someFrob 具有相同的类型。
因此,系统得出结论,Frob 和 Frob2 是同一类型,即使它们不是。因此,这种推理是不合理的,这最终是为什么您发布的getFrobbable 版本没有进行类型检查的原因。