【发布时间】:2019-03-28 15:52:22
【问题描述】:
列表推导与map 和filter 有重叠的功能。 filterM 迎合了谓词返回 Bool 包裹在 monad 中的情况(准确地说,是 Applicative)。 mapM 对结果封装在 Applicative 中的映射函数做了类似的事情。
如果您想通过列表理解来重现 mapM 的行为,sequence 可以提供帮助。但是filterM 怎么能被列表理解替换呢?换句话说,在上下文中返回 Bool 的谓词如何在列表推导中用作保护?
以下是探索 2x2x2 空间的简单示例(filter/map, function/c omprehension, plain/monadic) 以上的观察,只是我不知道怎么写fcm。如何修复fcm 使其与ffm 具有相同的值?
import Control.Monad (filterM)
predicate = (>3)
convert = (*10)
predicateM = Just . predicate -- Just represents an arbitrary monad
convertM = Just . convert
data_ = [1..5]
ffp = filter predicate data_
ffm = filterM predicateM data_
fcp = [a | a <- data_, predicate a]
fcm = [a | a <- data_, predicateM a]
mfp = map convert data_
mfm = mapM convertM data_
mcp = [ convert a | a <- data_ ]
mcm = sequence [ convertM a | a <- data_ ]
编辑:
需要注意的是,名称以m 结尾的版本必须使用convertM 或predicateM:在这些情况下,普通的convert 和predicate 不可用;这就是问题的重点。
背景资料
动机源于拥有一堆函数(这里是一个简化的集合,希望具有代表性)
convert :: a -> r -> b
predicate :: a -> r -> Bool
big :: [a] -> r -> [b]
big as r = [ convert a r | a <- as, predicate a r ]
请求根据 Reader 进行重构...其中之一 (big) 使用其他之一 (predicate) 作为 listcomp 保护中的谓词。
只要将 listcomp 替换为 mapM 和 filterM 的组合,重构就可以正常工作:
convertR :: a -> Reader r b
predicateR :: a -> Reader r Bool
bigR :: [a] -> Reader r [b]
bigR as = mapM convertR =<< filterM predicateR as
这样做的问题是,在现实生活中,listcomp 复杂得多,而对mapM 和filterM 的转换则远没有那么干净。
因此,即使谓词变成单子,也希望保留 listcomp。
编辑 2
真正的 listcomp 更复杂,因为它结合了多个列表中的元素。我试图将问题的本质提取到以下示例中,这与编辑1不同
-
big中的 listcomp 从多个列表中获取数据。 -
predicate接受多个值作为输入。 -
convert已重命名为combine并接受多个值作为输入。 -
Reader版本的类似更改。
.
combine :: r -> (a,a) -> b
predicate :: r -> (a,a) -> Bool
big, big' :: r -> [a] -> [b]
big r as = [ combine r (a,b) | a <- as, b <- as, predicate r (a,b) ]
big' r as = map (combine r) $ filter (predicate r) $ [ (a,b) | a <- as, b <- as ]
combineR :: (a,a) -> Reader r b
predicateR :: (a,a) -> Reader r Bool
bigR, bigR' :: [a] -> Reader r [b]
bigR = undefined
bigR' as = mapM combineR =<< filterM predicateR =<< return [ (a,b) | a <- as, b <- as ]
big' 是对big 的重写,其中combine 和predicate 是从listcomp 中提取的。这在阅读器版本中有一个直接等效项:bigR'。所以,问题是,你怎么写bigR应该是
- 没有明显比
bigR'丑 - 尽可能直接翻译
big。
在这个阶段,我很想得出结论,bigR' 已经达到了预期效果。这意味着我的问题的答案是:
- 保留用于构造笛卡尔积的 listcomp
- 将
predicate和combine从表达式中分别移到filter和map中 - 在单子版本中,将
filter和map替换为filterM和mapM(以及将$替换为=<<)。 - 取消对谓词和组合函数的柯里化:listcomp 与柯里化和非柯里化版本同样适用,但映射过滤器组合需要对它们进行柯里化。在这个阶段,这可能是失去使用 listcomp 能力的最大代价。
【问题讨论】:
-
不就是
fcm = Just [a | a <- data_, predicate a]吗? (它不会产生Nothing,因为(Just . predicate)永远不会。) -
@RobinZigmond
fcm中的m表示谓词返回m Bool。通过将(Just . predicate)更改为predicate,您已从立方体的fcm角移动到fcp角。关键是,无论出于何种原因,谓词都会返回一个单子,而我没有选择更改它的选项,我只需要处理它。 (在现实生活中,将一堆函数重构为 Reader monad 时出现了这种情况,突然我的谓词变成了 monadic,我的 lisctomp 坏了。) -
但是根据定义,谓词不能返回
m Bool。它必须返回一个Bool。您正在考虑filterM的函数参数是a -> m Bool类型的事实,但您不能在列表推导中使用它,因为它实际上不是谓词。我认为fcm的目标是让它产生与ffm相同的结果,但使用列表组合 - 我在上面提到的是我看到的唯一方法。 -
@RobinZigmond 正如我所说,这是为了重构 Reader。假设你有
fcm :: [a] -> r -> [b]和p :: a -> r -> Bool。fcm a r = [ xxx a r | a <- as , p a r]. Then you try to refactor tofcm :: [a] -> Reader r [b]` 和p :: a -> Reader r Bool,这使得p在 listcomp 中不可用。将xxx从a -> r ->b更改为a -> Reader r b可以通过切换到mapM或将sequence与listcomp 一起使用来处理。我可以通过将 listcomp 替换为filterM来了解如何处理p中的更改,但是可以在不摆脱 listcomp 的情况下完成吗? -
@RobinZigmond 是的,
fcm应该使用 listcomp 产生与ffm相同的结果...... 同时尊重您必须提供的唯一功能filterM类型为a -> m Bool。(Just . predicate)代表我必须使用的功能。我没有删除Just .的奢侈
标签: haskell