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

如何使元组成为Haskell中此类的实例?,haskell,types,typeclass,type-kinds,Haskell,Types,Typeclass,Type Kinds,我一直在读“”这本书,我停止了这个例子: 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 我的问题是:如何创建该类的实例?其思想是将函数调用为: λ bimap

我一直在读“”这本书,我停止了这个例子:

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
我的问题是:如何创建该类的实例?其思想是将函数调用为:

λ bimap  (+1) (+2) (8, 9) -- (9, 11)
λ first  (*4) (10, 8) -- (40, 8)
λ second (*2) (3, 5) -- (3, 10)
我最接近做到这一点的是:

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)
但它不起作用,它引发了一个错误:

• 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)’
(x,y)
已经是一种具体的元组类型,包含两种具体的(尽管未知)类型
x
y
。同时,函子或双函子应该是参数化的,也就是说,在元组实例中,您希望所包含的类型作为参数保持打开状态,然后在使用方法时用各种不同的具体类型填充参数

也就是说,您基本上需要类型级别的lambda

Haskell没有类型级别的lambda,但它在类型级别上有部分应用程序——在本例中,甚至不是部分应用程序,您根本不想将元组构造函数应用于任何类型,只想让它们保持打开状态。这是这样写的:

instance Bifunctor (,) where
如果您只想将其应用于一个参数,您可以编写

instance Functor ((,) a) where
如果将其解析为
函子(a,)
,我会觉得更容易理解,但这在Haskell中实际上并不合法。

好问题

该类应用于函子类型本身,在本例中,函子类型为(,)。要了解这种感觉,请注意这里的区别

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

:t (True,False)
(True,False) :: (Bool, Bool)
如果您使用了以下配对类型,可能会更直观:

data Pair a b = Pair a b
因为阅读类定义会使“p”的类型应用程序更加明显

正如Haskell使用类型表示值一样,如上所示,它使用类型表示类型(也用于编译时逻辑),这些类型称为种类

最后一行说明了Bifunctor类是为种类
(*->*->*->*)
的类型而设计的,而不是(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)

编辑:@leftroundound所建议的种类图解

我要注意的是,当在这些值级实体上使用
:t
时,您实际上看到的是与实例头中使用的类型级实体不同的东西。对于元组,类型-结束值构造函数的行为几乎完全相同,但对于大多数其他类型,情况并非如此。一般来说,您需要
:k
[ind]而不是
:t
[ype]。这在技术上是正确的,但是我认为在本例中,我选择的值构造函数示例充分说明了这一差异,同时读者仍然可以轻松访问。但是你的观点也是正确的,建议阅读关于类参数化和数据类型的内容,以便更好地理解错误消息。这个答案是经过验证的,因此我将对其进行编辑以反映你的观点。从语法上讲,
(a,b)
(,)a b
的值和类型相同。
:k Pair
Pair :: * -> * -> *

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

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

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

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)