我正在尝试并且未能从traverse
中查看Data.Traversable
函数。我无法理解其观点。由于我来自一个势在必行的背景,有人可以根据命令性循环向我解释一下吗?伪代码将非常感激。感谢。
答案 0 :(得分:104)
traverse
与fmap
相同,只是它还允许您在重建数据结构时运行效果。
查看Data.Traversable
文档中的示例。
data Tree a = Empty | Leaf a | Node (Tree a) a (Tree a)
Functor
的{{1}}实例将是:
Tree
重建整个树,将instance Functor Tree where
fmap f Empty = Empty
fmap f (Leaf x) = Leaf (f x)
fmap f (Node l k r) = Node (fmap f l) (f k) (fmap f r)
应用于每个值。
f
instance Traversable Tree where
traverse f Empty = pure Empty
traverse f (Leaf x) = Leaf <$> f x
traverse f (Node l k r) = Node <$> traverse f l <*> f k <*> traverse f r
实例几乎相同,只是构造函数以applicative样式调用。这意味着我们可以在重建树时获得(侧面)效果。应用与monad几乎相同,只是效果不能取决于之前的结果。在此示例中,这意味着您无法对节点的右侧分支执行不同的操作,具体取决于重建左侧分支的结果。
由于历史原因,Traversable
类还包含名为Traversable
的{{1}}的monadic版本。对于所有意图和目的traverse
与mapM
相同 - 它作为单独的方法存在,因为mapM
后来才成为traverse
的超类。
如果您使用不纯的语言实现此功能,Applicative
将与Monad
相同,因为无法防止副作用。您不能将其实现为循环,因为您必须递归遍历数据结构。这是一个小例子,我将如何在Javascript中执行此操作:
fmap
像这样实现它会限制您使用语言允许的效果。如果你f.e.想要非确定性(应用模型的列表实例)和你的语言没有内置它,你运气不好。
答案 1 :(得分:51)
traverse
将Traversable
内的内容转换为Traversable
内的Applicative
内容,给出了使Applicative
成为Maybe
的功能
我们将Applicative
用作Traversable
并列为half x = if even x then Just (x `div` 2) else Nothing
。首先,我们需要转换函数:
Just
因此,如果数字是偶数,我们得到一半(在Nothing
内),否则我们得到traverse half [2,4..10]
--Just [1,2,3,4,5]
。如果一切顺利,它看起来像这样:
traverse half [1..10]
-- Nothing
但是......
<*>
原因是Nothing
函数用于构建结果,当其中一个参数为Nothing
时,我们返回rep x = replicate x x
。
另一个例子:
x
此函数生成一个长度为x
且内容为rep 3
的列表,例如[3,3,3]
= traverse rep [1..3]
。 [1]
的结果是什么?
我们使用[2,2]
得到[3,3,3]
,rep
和Applicatives
的部分结果。现在,作为(+) <$> [10,20] <*> [3,4]
的列表的语义是“采用所有组合”,例如[13,14,23,24]
是[1]
。
[2,2]
和[1,2]
的“所有组合”是[1,2]
的两倍。 [3,3,3]
和[1,2,3]
两次的所有组合是traverse rep [1..3]
--[[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3]]
的六倍。所以我们有:
{{1}}
答案 2 :(得分:41)
我认为最简单的理解是sequenceA
,因为traverse
可以定义为
如下:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
traverse f = sequenceA . fmap f
sequenceA
将结构的元素从左到右排列在一起,返回一个包含结果的相同形状的结构。
sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)
sequenceA = traverse id
您还可以将sequenceA
视为颠倒两个仿函数的顺序,例如:从一系列动作转变为一个返回结果列表的动作。
所以traverse
需要一些结构,并应用f
将结构中的每个元素转换为一些应用程序,然后从左到右排序这些应用程序的效果,返回一个结构包含结果的相同形状。
您还可以将其与Foldable
进行比较,traverse_
定义相关函数traverse_ :: (Foldable t, Applicative f) => (a -> f b) -> t a -> f ()
。
Foldable
所以你可以看到Traversable
和IO
之间的关键区别在于后者允许你保留结构的形状,而前者需要你将结果折叠成其他的值。
使用它的一个简单示例是使用列表作为可遍历的结构,并使用λ> import Data.Traversable
λ> let qs = ["name", "quest", "favorite color"]
λ> traverse (\thing -> putStrLn ("What is your " ++ thing ++ "?") *> getLine) qs
What is your name?
Sir Lancelot
What is your quest?
to seek the holy grail
What is your favorite color?
blue
["Sir Lancelot","to seek the holy grail","blue"]
作为应用程序:
traverse
虽然这个例子相当令人兴奋,但当{{1}}用于其他类型的容器或使用其他应用程序时,事情变得更有趣。
答案 3 :(得分:16)
它有点像fmap
,除了你可以在mapper函数中运行效果,这也会改变结果类型。
想象一下表示数据库中用户ID的整数列表:[1, 2, 3]
。如果您想要fmap
这些用户ID到用户名,则不能使用传统的fmap
,因为在函数内部需要访问数据库来读取用户名(这需要一个效果 - 在这种情况下,使用IO
monad)。
traverse
的签名是:
traverse :: (Traversable t, Applicative f) => (a -> f b) -> t a -> f (t b)
使用traverse
,您可以执行效果,因此,将用户ID映射到用户名的代码如下所示:
mapUserIDsToUsernames :: (Num -> IO String) -> [Num] -> IO [String]
mapUserIDsToUsernames fn ids = traverse fn ids
还有一个名为mapM
的函数:
mapM :: (Traversable t, Monad m) => (a -> m b) -> t a -> m (t b)
mapM
的任何使用都可以替换为traverse
,但不能替代mapM
。 traverse
仅适用于monad,而traverse_
更通用。
如果你只想获得一个效果并且不返回任何有用的值,那么这些函数有mapM_
和if( $('#input_1').hasClass('valid') )
{
$('#trigger').attr("onclick", null);
}
else // disable the button
{
$('#trigger').on('click', function(e) { e.preventDefault(); } );
}
个版本,它们都忽略了函数的返回值而且速度稍快
答案 4 :(得分:7)
traverse
是循环。它的实现取决于要遍历的数据结构。这可能是一个列表,树,Maybe
,Seq
(uence),或者具有通过类似for循环或递归函数遍历的通用方式的任何东西。数组将具有for循环,列表具有while循环,树可以是递归的,或者是堆栈与while循环的组合;但是在函数式语言中,您不需要这些繁琐的循环命令:您可以以更直接的方式将循环的内部部分(以函数的形式)与数据结构相结合,并且更简洁。
使用Traversable
类型类,您可以编写更独立,更通用的算法。但我的经验表明,Traversable
通常仅用于将算法简化为现有数据结构。很高兴不需要为不同的数据类型编写类似的函数。