类型 class 中的多个类型参数?

Multiple type parameters in type class?

假设我有一个类型 class Stack 和一个实例 List:

class Stack a where
    push :: a -> Integer -> a
    pop :: a -> a
    last :: a -> Integer

data List = Empty | Element Integer List
instance Stack List where
    push list value = Element value list
    pop Empty = error "No elements"
    pop (Element _ list) = list
    last Empty = error "No elements"
    last (Element value _) = value

如何定义 Stack 才能使 List 不限于 Integer 值?

-- class Stack (?) where ...
data List a = Empty | Element a (List a)
-- instance Show (List a) where ...

在那种情况下你可以做一个多参数class:

class Stack a b where
    push :: a -> b -> a
    pop :: a -> a
    last :: a -> b

并定义为:

instance Stack (List b) b where --You don't need to use `b`, but this make it easier to understand
    push list value = Element value list
    pop Empty = error "No elements"
    pop (Element _ list) = list
    last Empty = error "No elements"
    last (Element value _) = value

请注意,这不是默认(标准化)Haskell 功能,您需要将其打开。通过将 -XMultiParamTypeClasses-XFlexibleInstances 传递给编译器。

或者你可以这样写:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}

在源文件的 header 中。


请注意,您可以为一个 a 定义多个 b 实例(反之亦然)。这会使使用这样的 classes 变得困难。比如说你写了一个 Dummy type:

data Dummy = Dummy

你可以定义:

instance Stack Dummy b where
    push x = const x
    pop = id
    last = const $ error "Dummy object"

现在这意味着每个可能的 b 都有 Stack 个实例,这样您就可以 pushpop 各种东西到 Dummy objects.

考虑使用更高级的 class 变量。因此:

class Stack s where
    push :: s a -> a -> s a
    pop  :: s a -> s a
    last :: s a -> a

data List a = Empty | Element a (List a)

该实例与您编写的完全相同(尽管 List 现在具有类型 * -> * 而不是 *):

instance Stack List where
    push list value = Element value list
    pop Empty = error "No elements"
    pop (Element _ list) = list
    last Empty = error "No elements"
    last (Element value _) = value

此方法是纯粹的 Haskell 2010 -- 它不需要扩展。

另外,考虑让你的失败变得可观察;例如,将 poplast 的类型分别更改为 return Maybe (s a)Maybe a