Ваша функция check
более известна как метод класса Traversable
.
class (Functor t, Foldable t) => Traversable t where
-- The main method
traverse
:: Applicative f
=> (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
-- An alternative
sequenceA
:: Applicative f
=> t (f a) -> f (t a)
sequenceA = traverse id
-- (Mostly) legacy methods
mapM
:: Monad m
=> (a -> m b) -> t a -> m (t b)
mapM = traverse
sequence
:: Monad m
=> t (m a) -> m (t a)
sequence = sequenceA
В частности, check
- это sequence
для MyTree
.Таким образом, мы получим его, если напишем экземпляр Traversable MyTree
.Но давайте сначала сделаем шаг назад в двух направлениях.Traversable
является подклассом Functor
и Foldable
, и это не случайно.Можно реализовать как fmap
, так и foldMap
, используя traverse
.Но, что более важно, структуры fmap
, foldMap
и traverse
имеют тенденцию выглядеть почти одинаково!Итак, начнем с более простых.
instance Functor MyTree where
fmap f (MyTree a ts) = MyTree (f a) _
Что входит в этот пробел?У нас есть список поддеревьев, и нам нужно сгенерировать новое, поэтому хорошей ставкой будет
fmap f (MyTree a ts) = MyTree (f a) (fmap _ ts)
Теперь бланк имеет тип MyTree a -> MyTree b
, поэтому мы просто вызываем fmap
рекурсивно:
fmap f (MyTree a ts) = MyTree (f a) (fmap (fmap f) ts)
И мы закончили.Теперь давайте обратимся к Foldable
.
foldMap f (MyTree a ts) = _
Что ж, нам нужно применить f
к a
, чтобы получить значение в моноиде, затем сложить поддеревья и объединитьРезультаты.В итоге это выглядит как fmap
, как и было обещано.
foldMap f (MyTree a ts) = f a <> foldMap (foldMap f) ts
Так что теперь мы получаем Traversable
.Это будет очень похоже на fmap
, но нам нужно объединить результаты, используя Applicative
операции несколько , как мы объединяли foldMap
результаты, используя Monoid
операции.
instance Traversable MyTree where
traverse f (MyTree a ts) = _
У нас есть
a :: a
ts :: [MyTree a]
f :: a -> f b
Очевидно, мы хотим применить f
к a
.Следуя схеме для fmap
и foldMap
, мы собираемся вычислить traverse (traverse f) ts
.Итак, давайте посмотрим, куда это нас приведет:
traverse f (MyTree a ts) = _ (f a) (traverse (traverse f) ts)
Теперь GHC скажет нам, что
_ :: f b -> f [MyTree b] -> f (MyTree b)
Нам нужно взять b
результат первого действия и [MyTree b]
результат второго действия и примените конструктор MyTree
, чтобы собрать их вместе.Мы можем сделать это, используя liftA2
:
traverse f (MyTree a ts) = liftA2 MyTree (f a) (traverse (traverse f) ts)
После того, как вы освоите написание экземпляров Functor
, Foldable
и Traversable
, это будет довольно красиво.тупой.Так что у GHC есть расширение, которое позволяет компилятору написать их для вас.
{-# language DeriveTraversable #-}
module MyModule where
data MyTree a = MyTree a [MyTree a]
deriving (Functor, Foldable, Traversable)
И все готово.