【问题标题】:List comprehension guard equivalent of filterM列表理解保护等效于 filterM
【发布时间】:2019-03-28 15:52:22
【问题描述】:

列表推导与mapfilter 有重叠的功能。 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 结尾的版本必须使用convertMpredicateM:在这些情况下,普通的convertpredicate 不可用;这就是问题的重点。

背景资料

动机源于拥有一堆函数(这里是一个简化的集合,希望具有代表性)

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 替换为 mapMfilterM 的组合,重构就可以正常工作:

convertR   ::  a  -> Reader r b
predicateR ::  a  -> Reader r Bool
bigR       :: [a] -> Reader r [b]

bigR as = mapM convertR =<< filterM predicateR as

这样做的问题是,在现实生活中,listcomp 复杂得多,而对mapMfilterM 的转换则远没有那么干净。

因此,即使谓词变成单子,也希望保留 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 的重写,其中combinepredicate 是从listcomp 中提取的。这在阅读器版本中有一个直接等效项:bigR'。所以,问题是,你怎么写bigR应该是

  1. 没有明显比bigR'
  2. 尽可能直接翻译big

在这个阶段,我很想得出结论,bigR' 已经达到了预期效果。这意味着我的问题的答案是:

  • 保留用于构造笛卡尔积的 listcomp
  • predicatecombine 从表达式中分别移到 filtermap
  • 在单子版本中,将filtermap 替换为filterMmapM(以及将$ 替换为=&lt;&lt;)。
  • 取消对谓词和组合函数的柯里化:listcomp 与柯里化和非柯里化版本同样适用,但映射过滤器组合需要对它们进行柯里化。在这个阶段,这可能是失去使用 listcomp 能力的最大代价。

【问题讨论】:

  • 不就是fcm = Just [a | a &lt;- 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 -&gt; m Bool 类型的事实,但您不能在列表推导中使用它,因为它实际上不是谓词。我认为fcm 的目标是让它产生与ffm 相同的结果,但使用列表组合 - 我在上面提到的是我看到的唯一方法。
  • @RobinZigmond 正如我所说,这是为了重构 Reader。假设你有fcm :: [a] -&gt; r -&gt; [b]p :: a -&gt; r -&gt; Boolfcm a r = [ xxx a r | a &lt;- as , p a r]. Then you try to refactor to fcm :: [a] -> Reader r [b]` 和 p :: a -&gt; Reader r Bool,这使得 p 在 listcomp 中不可用。将xxxa -&gt; r -&gt;b 更改为a -&gt; Reader r b 可以通过切换到mapM 或将sequence 与listcomp 一起使用来处理。我可以通过将 listcomp 替换为 filterM 来了解如何处理 p 中的更改,但是可以在不摆脱 listcomp 的情况下完成吗?
  • @RobinZigmond 是的,fcm 应该使用 listcomp 产生与 ffm 相同的结果...... 同时尊重您必须提供的唯一功能 filterM类型为a -&gt; m Bool(Just . predicate) 代表我必须使用的功能。我没有删除Just .的奢侈

标签: haskell


【解决方案1】:

就我个人而言,我想不出用只是一个列表组合的方法,但这可能会让你更接近? (我列出了我经历的一些中间步骤,以便您可以根据需要采取不同的方向)。

{-# LANGUAGE MonadComprehensions #-}

predicateM = return . (>3)
[[a | True <- predicateM a] | a <- [1,2,3,4,5]] 
  :: (Num b, Ord b, Control.Monad.Fail.MonadFail m) => [m b]
[[if b then Just a else Nothing | b <- predicateM a] | a <- [1,2,3,4,5]]
  :: (Num a, Monad m, Ord a) => [m (Maybe a)]
[[bool Nothing (Just a) b | b <- predicateM a] | a <- [1,2,3,4,5]]
  :: (Num a, Monad m, Ord a) => [m (Maybe a)]
catMaybes <$> sequence [[bool Nothing (Just a) b | b <- predicateM a] | a <- [1,2,3,4,5]]
  :: (Monad f, Num a, Ord a) => f [a]

filterM' p xs = catMaybes <$> sequence [[bool Nothing (Just a) b | b <- p a] | a <- xs]

ListT 可能会更简洁?

编辑:另一种方法,尽管它会发出一些关于ListT 提供非法实例的警告..

unListT (ListT x) = x
filterM'' p xs = unListT [a | (a,True) <- ListT $ mapM (\x -> return . (x,) =<< p x) xs]

EDIT2:好的,我把它归结为一个单一的理解!

filterM''' :: (Traversable m1, Monad m2, Monad m1, Alternative m1) => (b -> m2 Bool) -> m1 b -> m2 (m1 b)
filterM''' p xs = [[a | (a,b) <- mlist, b] | mlist <- mapM (\x -> return . (x,) =<< p x) xs]

EDIT3:另一种方法,因为我不确定您需要访问什么。

filterM' p xs = [x | x <- filterM p xs]

然后,嵌套理解以获得对“内部”元素类型的访问,例如。过滤和映射,

filterMap f p xs = [[f x | x <- mlist] | mlist <- filterM p xs]

【讨论】:

  • 我添加了另一个编辑,以强调其背后的务实动机。关键不是为了它而将其强制为 listcomp(尽管这可能很有趣且有趣!)在我的编辑中,您可以看到 listcomp 使用多个输入集,并且映射的翻译非常干净+filter,它对 monadic 版本的翻译非常干净,但它的代价是必须取消谓词和组合器。所以大局的问题是:有没有办法把它写成一个比 uncurry-filterM-mapM 方法更整洁的 listcomp?
【解决方案2】:
import Control.Monad.Trans.Reader
import Control.Monad (filterM)

这个问题的动机是希望获得表单的某些功能

f ::  x  ->  y  ->  z ->   a                 ; f  = undefined
p ::  x  ->  y  ->  z -> Bool                ; p  = undefined
u :: [x] -> [y] -> [z] -> [a]

并在某些上下文中将它们替换为类似的(在此示例中为Reader):

fR ::  x  ->  y  ->  z  -> Reader r  a        ; fR = undefined
pR ::  x  ->  y  ->  z  -> Reader r Bool      ; pR = undefined
uR :: [x] -> [y] -> [z] -> Reader r [a]

问题的核心是,其中一个原始函数具有多输入列表推导式,它使用其他函数进行组合和过滤:

u xs ys zs = [ f x y z | x <- xs, y <- ys, z <- zs, p x y z ]

该问题解决了实现等效的上下文uR 函数的困难;特别是明显丧失使用列表推导的能力及其便利性。

u 相当简洁地翻译成结合了列表理解的实现,filtermap。这在下面显示为u'u' 有直接翻译成上下文版本,uR

u' xs ys zs = map  ucf  $   filter  ucp  $           cartesian xs ys zs
uR xs ys zs = mapM ucfR =<< filterM ucpR =<< return (cartesian xs ys zs)

在上面,列表推导被打包在cartesian

cartesian xs ys zs = [ (x,y,z) | x <- xs, y <- ys, z <- zs ]

并且该解决方案要求原始函数(pfpRfR)未被curried:

ucp = uncurry3 p     ;    ucpR = uncurry3 pR
ucf = uncurry3 f     ;    ucfR = uncurry3 fR

uncurry3 :: (a -> b -> c -> r) -> (a, b, c) -> r
uncurry3 f = \(a,b,c) -> f a b c

也许这个解决方案可以加上单子理解,但我现在没有时间考虑这个。

【讨论】:

  • 对不起,我写几个不同版本的原因是我自己做实验,也因为我不确定你的要求。您有几个选择,uR xs ys zs = sequence [ join [ fR x y z | True &lt;- pR x y z ] | x &lt;- xs, y &lt;- ys, z &lt;- zs ] 是更清晰的选择之一。您可以通过使内部理解复杂化来删除连接,和/或您可以通过直接在 Bool 上匹配来将 MonadFail 约束更改为 Alternative ..[ r | r &lt;- fR x y z, p &lt;- pR x y z, p](此处显示了两个更改)。不过,您可能仍然觉得非理解版本更清晰!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-31
  • 2013-06-25
相关资源
最近更新 更多