如何在 Haskell 中使元组成为此类的实例?

NrB*_*ex 13 haskell types typeclass type-kinds

我一直在阅读“我希望在学习 Haskell 时知道什么本书,然后我停在了这个例子上:

class Bifunctor p where
    bimap  :: (a -> b) -> (c -> d) -> p a c -> p b d
    first  :: (a -> b) -> p a c -> p b c
    second :: (b -> c) -> p a b -> p a c
Run Code Online (Sandbox Code Playgroud)

我的问题是:如何创建该类的实例?这个想法是将函数调用为:

? bimap  (+1) (+2) (8, 9) -- (9, 11)
? first  (*4) (10, 8) -- (40, 8)
? second (*2) (3, 5) -- (3, 10)
Run Code Online (Sandbox Code Playgroud)

我最接近做到这一点的是:

instance Bifunctor (x, y) where
    bimap func func' (x, y) = (func x, func' y)
    first func (x, y) = (func x, y)
    second func (x, y) = (x, func y)
Run Code Online (Sandbox Code Playgroud)

但它不起作用,它引发了一个错误:

• Expecting two fewer arguments to ‘(x, y)’
  Expected kind ‘* -> * -> *’, but ‘(x, y)’ has kind ‘*’
• In the first argument of ‘Bifunctor’, namely ‘(x, y)’
  In the instance declaration for ‘Bifunctor (x, y)’
Run Code Online (Sandbox Code Playgroud)

Pau*_*l R 13

好问题。

该类适用于函子类型本身,在您的情况下,函子类型是 (,)。要了解它,请注意此处的差异。

:t (,)
(,) :: a -> b -> (a, b)

:t (True,False)
(True,False) :: (Bool, Bool)
Run Code Online (Sandbox Code Playgroud)

如果您使用像这样的 Pair 类型,它可能会更直观:

data Pair a b = Pair a b
Run Code Online (Sandbox Code Playgroud)

因为阅读类定义会使'p'的类型应用更加明显。

就像 Haskell 使用类型作为值一样,如上所示,它使用类型作为类型(也用于编译时逻辑),称为Kinds

:k Pair
Pair :: * -> * -> *

:k (,)
(,) :: * -> * -> *

:k (Bool,Bool)
(Bool,Bool) :: *

:k Bifunctor 
Bifunctor :: (* -> * -> *) -> Constraint

Run Code Online (Sandbox Code Playgroud)

最后一行说明 Bifunctor 类是为 kind 类型设计的(* -> * -> *),而不是(*)为 (a,b)类型设计的,因此您从 GHC 获得了错误消息。

你的定义几乎是正确的,这是正确的:

instance Bifunctor (,) where
  bimap func func' (x, y) = (func x, func' y)
  first func (x, y) = (func x, y)
  second func (x, y) = (x, func y)
Run Code Online (Sandbox Code Playgroud)

编辑:插图由@leftroundabout建议

  • 我注意到,当在这些_值级别_实体上使用 `:t` 时,您实际上看到的是与实例头中使用的类型级别实体不同的东西。碰巧的是,对于元组,类型结束值构造函数的行为几乎完全相同,但对于大多数其他类型而言并非如此。一般来说,您需要 `:k`[ind] 而不是 `:t`[ype]。 (2认同)

lef*_*out 6

(x,y)已经是一个具体的元组类型,包含两个具体的(尽管未知)类型xy. 同时,函子或双函子应该是参数化的,即在元组实例的情况下,您希望包含的类型作为参数保持打开状态,然后在使用方法时用各种不同的具体类型填充。

即,您基本上想要一个类型级别的 lambda

instance Bifunctor (\x y -> (x, y)) where
Run Code Online (Sandbox Code Playgroud)

好吧,Haskell 没有类型级别的 lambdas,但它确实在类型级别有部分应用——在这种情况下甚至不是partial,你根本不想将元组构造函数应用于任何类型而只是让它们保持打开状态. 是这样写的:

instance Bifunctor (,) where
Run Code Online (Sandbox Code Playgroud)

如果你只想将它应用于一个参数,你可以写

instance Functor ((,) a) where
Run Code Online (Sandbox Code Playgroud)

如果将其解析为,我发现更容易理解Functor (a,)- 但这在 Haskell 中实际上并不合法。