我有以下内容:
[bla z|n<-[0..], let z = foo n, z < 42]
问题是,我希望列表理解在z < 42
失败后立即结束,就好像它是一个takeWhile。我知道我可以将它重构成一堆过滤器和地图,但是列表理解会更加优雅。
将列表推导和takeWhile结合起来的最优雅方式是什么?
答案 0 :(得分:6)
由于列表推导不允许这样做,我使用 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
代替更多内容:只要它变为false,就会停止在之前的生成器(例如<-[0..]
)中进一步选择。
我写的小图书馆如下所示:
{-# LANGUAGE DeriveFunctor, MonadComprehensions #-}
import Control.Monad
import Control.Applicative
data F a = F [a] Bool
deriving (Functor, Show)
上面的Bool
是 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
当if
信号停止时,最后xs
会丢弃f x
部分。
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
可能是一个真正的单子。
答案 1 :(得分:3)
我没有对此有一个好的答案,所以我只是建议一个让你尽可能多地使用列表理解的kludge:
map snd . takeWhile fst $
[(z < 42, bla z)|n<-[0..], let z = foo n]
答案 2 :(得分:1)
这听起来像是停止使用列表推导而不是学习使用更高阶列表函数的好点,例如the Data.List
module中提供的函数。
从根本上说,列表推导只是concatMap :: (a -> [b]) -> [a] -> [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..]
但直接编写类似理解的计算的惯用方法是map
,filter
和Applicative
类(在这个例子中不需要最后一个):< / p>
map bla (filter (<42) (map foo [0..]))
一旦你这样写了,就很容易看出这就是你想要的:
map bla (takeWhile (<42) (map foo [0..]))