Возможно, канонический пример - это векторы.
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
, соответствующий этому 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)
Вы можете немного сэкономить, определив >>=
более прямо, но как бы вы это ни сокращали, монадическое поведение создает бесполезные переходники для недиагональных вычислений. Лень может спасти нас от замедления из-за фактора армагеддона, но режим «застегивания» <*>
обязательно будет по крайней мере немного дешевле, чем получение диагонали матрицы.
person
pigworker
schedule
03.09.2013