常用函数
join
join 函数通常用于处理嵌套的 Monad 结构,例如 Maybe (Maybe a) 或 IO (IO a)。它将一个嵌套的 Monad “拉平” 成一个单层 Monad,类型签名如下:
1 | join :: Monad m => m (m a) -> m a |
在 Maybe 中使用 join
1 | import Control.Monad (join) |
在 List 中使用 join
1 | example3 :: [Int] |
在 IO 中使用 join
1 | example4 :: IO () |
这里 getLine 的类型为 IO String,putStrLn <$> getLine 的类型为 IO (IO ()),使用 join 可以得到 IO () 类型,从而直接执行打印操作。
traverse
traverse 函数的作用是将一个函数应用于数据结构中的每个元素,并返回一个包含应用结果的新的数据结构。该函数的签名如下:
1 | traverse :: Traversable t => (a -> f b) -> t a -> f (t b) |
以下是列表的Traversable的实现
1 | instance Traversable [] where |
(:) <$> f x <*> ys的解释如下
- f参数:
(a -> f b) - f x:x是
[a]的一个元素,所以f x生成f b - ys:是
f [b] - (:) <$> f x:表示(:)应用于f内部的x
- (:) <$> f x <*> ys:的意思就是(f (x :))里面的
x :应用于ys的内部也就是[b]
容易误解的地方有两个
- 一个是函数声明中的f和函数体的参数f不是同一个f,容易弄混了,函数声明中的f是一个Applicative,而函数体的参数的f是一个(a -> f b)的函数。
- 另外一个是
(:) <$> f x <*> ys容易误解为f x : ys也可以,后面的写法是将f x直接放入ys中,而第一种写法是将f x的x放到ys等同于f [b]中的[b]。其实当你看到<$>和<*>同时使用的时候就可以想到lift提升了,(:)的签名是a -> [a] -> [a],提升后的签名是f a -> f [a] -> f [a]。
例子 1: 把偶数转换为字符串
1 | import Data.Traversable (traverse) |
例子 2: 和 IO 一起使用
1 | import System.IO (readFile) |
总结
traverse 的应用场景通常是:
你有一个数据结构(比如列表、树、Maybe 等)。
你想对每个元素执行一个带有副作用的操作,比如返回 Maybe、IO 或者其他 Applicative 类型。
traverse 帮助你保持原数据结构的形状,同时把每个操作的结果组合成一个整体的结果。