也许典型的例子是由向量给出的。
data Nat = Z | S Nat deriving (Show, Eq, Ord)
data Vec :: Nat -> * -> * where
V0 :: Vec Z x
(:>) :: x -> Vec n x -> Vec (S n) x
我们可以通过一些努力使它们具有应用性,首先定义单例,然后将它们包装在一个类中。
data Natty :: Nat -> * where
Zy :: Natty Z
Sy :: Natty n -> Natty (S n)
class NATTY (n :: Nat) where
natty :: Natty n
instance NATTY Z where
natty = Zy
instance NATTY n => NATTY (S n) where
natty = Sy natty
现在我们可以开发Applicative
结构
instance NATTY n => Applicative (Vec n) where
pure = vcopies natty
(<*>) = vapp
vcopies :: forall n x. Natty n -> x -> Vec n x
vcopies Zy x = V0
vcopies (Sy n) x = x :> vcopies n x
vapp :: forall n s t. Vec n (s -> t) -> Vec n s -> Vec n t
vapp V0 V0 = V0
vapp (f :> fs) (s :> ss) = f s :> vapp fs ss
我省略了Functor
实例(应该fmapDefault
从Traversable
实例中提取)。
现在,有一个Monad
对应于 this 的实例Applicative
,但它是什么?对角思维!这就是所需要的!向量可以看作是来自有限域的函数的列表,因此Applicative
它只是 K 和 S 组合子的列表,并且Monad
具有类似Reader
的行为。
vtail :: forall n x. Vec (S n) x -> Vec n x
vtail (x :> xs) = xs
vjoin :: forall n x. Natty n -> Vec n (Vec n x) -> Vec n x
vjoin Zy _ = V0
vjoin (Sy n) ((x :> _) :> xxss) = x :> vjoin n (fmap vtail xxss)
instance NATTY n => Monad (Vec n) where
return = vcopies natty
xs >>= f = vjoin natty (fmap f xs)
您可以通过更直接的定义来节省一点>>=
,但是无论您如何削减它,单子行为都会为非对角线计算创建无用的 thunk。懒惰可能会使我们免于因世界末日因素而放慢速度,但 zip 的压缩行为<*>
肯定比采用矩阵的对角线至少要便宜一些。