最近,我正在和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
。我全神贯注地思考正确的方法,并且仍然在想办法。如果有人对此有所了解,将非常有帮助。
答案 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
具体地说,check
是sequence
的{{1}}。因此,如果我们编写一个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
,以获取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
。按照a
和fmap
的模式,我们将计算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)
,Functor
和Foldable
实例的知识,这样做往往会变得很乏味。因此,GHC具有扩展名,可让编译器为您编写它们。
Traversable
您完成了。