【问题标题】:"takeWhile" within a list comprehension列表理解中的“takeWhile”
【发布时间】:2015-06-01 21:47:20
【问题描述】:

我有类似以下的内容:

[bla z|n<-[0..], let z = foo n, z < 42]

问题是,我希望列表理解在z &lt; 42 失败时立即结束,就好像它是一个 takeWhile。我知道我可以将其重构为一堆过滤器和映射,但使用列表理解会更加优雅。

将列表推导和 takeWhile 结合起来最优雅的方式是什么?

【问题讨论】:

  • AFAIK 没有用于列表理解的 takeWhile 的语法糖。但是,如果你使用maptakeWhile,它会显示为map bla $ takeWhile (&lt; 42) $ map foo [0..],我认为这不是特别不雅。
  • 你可以做一些像[bla z | z &lt;- takeWhile (&lt; 42) [foo n | n &lt;- [0..]] 这样的事情,这样可以让事情更容易理解。

标签: list haskell syntax list-comprehension infinite


【解决方案1】:

由于列表推导不允许这样做,我使用 monad 推导并定义了一个自定义 monad 进行了一些修改。结果是以下工作:

example :: [Int]
example = toList [1000 + n 
                 | n <- fromList [0..]
                 , _ <- nonStopGuard (n > 1)
                 , let z = 10*n
                 , _ <- stopGuard (z < 42) ]

-- Output: [1002,1003,1004]

上面是一个普通的列表推导式,但有两种不同的守卫。 nonStopGuard 用作常规守卫,除了需要奇怪的语法。 stopGuard 反而做了更多的事情:一旦它变为假,它就会停止考虑先前生成器中的进一步选择(例如&lt;-[0..])。

我写的小库如下图:

{-# LANGUAGE DeriveFunctor, MonadComprehensions #-}
import Control.Monad
import Control.Applicative

data F a = F [a] Bool
  deriving (Functor, Show)

上面的Bool 是一个stop位,表示我们必须stop考虑进一步的选择。

instance Applicative F where pure = return; (<*>) = ap
instance Monad F where
   return x = F [x] False
   F [] s      >>= _ = F [] s
   F (x:xs) sx >>= f = F (ys ++ zs) (sx || sy || sz)
      where 
      F ys sy = f x
      F zs sz = if sy then F [] False else F xs sx >>= f

f x 发出停止信号时,最后一个if 将丢弃xs 部分。

nonStopGuard :: Bool -> F ()
nonStopGuard True  = F [()] False
nonStopGuard False = F []   False

普通的守卫永远不会发出停止的信号。它只提供一个或零个选择。

stopGuard :: Bool -> F ()
stopGuard True  = F [()] False
stopGuard False = F [] True

一个停止守卫反而会在它变为假时发出停止信号。

fromList :: [a] -> F a
fromList xs = F xs False

toList :: F a -> [a]
toList (F xs _) = xs

最后一个警告:我不完全确定我的 monad 实例定义了一个实际的 monad,即它是否满足 monad 法则。


按照@icktoofay 的建议,我写了一些快速检查测试:

instance Arbitrary a => Arbitrary (F a) where
   arbitrary = F <$> arbitrary <*> arbitrary

instance Show (a -> b) where
   show _ = "function"

prop_monadRight :: F Int -> Bool
prop_monadRight m =
   (m >>= return) == m

prop_monadLeft :: Int -> (Int -> F Int) -> Bool
prop_monadLeft x f =
   (return x >>= f) == f x

prop_monadAssoc :: F Int -> (Int -> F Int) -> (Int -> F Int) -> Bool
prop_monadAssoc m f g =
   ((m >>= f) >>= g)
   ==
   (m >>= (\x -> f x >>= g))

运行 100000 次测试没有发现反例。所以,很可能上面的F 是一个真正的monad。

【讨论】:

  • 如果你为它实现Arbitrary,你也许可以使用QuickCheck来测试F是否是Monad
  • 这完全是疯了。 +1。
  • @icktoofay 谢谢,我刚刚尝试了你的建议。 100000 次测试后没有反例。
【解决方案2】:

这听起来像是停止使用列表推导并学习使用高阶列表函数(例如the Data.List module 中提供的函数)的好点。

从根本上说,列表推导式只是 concatMap :: (a -&gt; [b]) -&gt; [a] -&gt; [b] 嵌套使用的一种漂亮语法。所以你原来的理解:

[bla z|n<-[0..], let z = foo n, z < 42]

...真的和这个一样:

let step n = let z = foo n
             in if z < 42 then [] else [bla z] 
in concatMap step [0..]

但是直接编写类似理解的计算的惯用方式是使用mapfilterApplicative 类(在这个示例中不需要最后一个):

map bla (filter (<42) (map foo [0..]))

一旦你把它写成这样,很容易看出这就是你想要的:

map bla (takeWhile (<42) (map foo [0..]))

【讨论】:

    【解决方案3】:

    对此我没有很好的答案,所以我只是建议一个可以让您尽可能多地使用列表理解的组合:

    map snd . takeWhile fst $
        [(z < 42, bla z)|n<-[0..], let z = foo n]
    

    【讨论】:

      猜你喜欢
      • 2022-10-13
      • 2013-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-03
      • 2014-08-26
      • 1970-01-01
      相关资源
      最近更新 更多