努力实现Monad功能

时间:2018-11-21 23:23:06

标签: haskell functional-programming monads

最近,我正在和Haskell monad玩耍,并试图了解这个概念。

假设有一个声明的树数据类型可以包含多个子树。

data MyTree a = MyTree a [MyTree a]

并且我正在尝试实现一个函数,如果树在树中包含任何“ Nothing”值,则该函数返回“ Nothing”。否则,提取所有m值并返回包装的树。

因此,函数类型签名具有以下特征。

check :: Monad m => MyTree (m a) -> m (MyTree a)

这是我当前的实现方式。

check (MyTree v []) = v >>= (\v' -> return (MyTree v' []))
check (MyTree v (x:xs)) =
  v >>= (\v' -> check x >>= (\t' -> return (MyTree v' [t'])))

我在v上使用了绑定运算符,因此我可以得到它的纯值。然后,我使用列表中的head值递归调用“ check”函数。最后,我包装了最终结果。

我对一些样品进行了测试,得出以下结果。

> test1 = MyTree (Just 1) [MyTree (Just 2) [MyTree (Just 3) []]]
> check test1
Just (MyTree 1 [MyTree 2 [MyTree 3 []]])

> test2 = MyTree (Just 1) [MyTree (Just 2) [], MyTree (Just 3) []]
> check test2
-- expected: Just (MyTree 1 [MyTree 2 [], MyTree 3 []]
-- actual:   Just (MyTree 1 [MyTree 2 []])

因此,当输入树具有多个子树时,当前实现存在问题。而且我已经意识到问题是我仅使用x而不是xs。我全神贯注地思考正确的方法,并且仍然在想办法。如果有人对此有所了解,将非常有帮助。

1 个答案:

答案 0 :(得分:6)

您的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

具体地说,checksequence的{​​{1}}。因此,如果我们编写一个MyTree实例,我们将得到它。但是,让我们首先向两个方向退后一步。 Traversable MyTreeTraversableFunctor的子类,这不是巧合。可以使用Foldable来实现fmapfoldMap。但更重要的是,traversefmapfoldMap的结构看起来几乎完全相同!因此,让我们从更简单的内容开始。

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,以获取monoid中的值,然后折叠子树并合并结果。正如所承诺的,最终看起来很像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。按照afmap的模式,我们将计算foldMap。因此,让我们看看能带给我们什么:

traverse (traverse f) ts

现在GHC会告诉我们

traverse f (MyTree a ts) = _ (f a) (traverse (traverse f) ts)

我们需要从第一个动作中提取_ :: 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) FunctorFoldable实例的知识,这样做往往会变得很乏味。因此,GHC具有扩展名,可让编译器为您编写它们。

Traversable

您完成了。