改进我的Haskell Filter实现

时间:2010-06-10 02:42:46

标签: haskell filter

我最近一直在教自己Haskell,我的一个练习就是重新实现filter功能。然而,在我演过的所有练习中,我对这个练习的答案在我看来是最丑陋和最长的。我怎么能改进它?有什么Haskell技巧我还不知道吗?

myfilter :: (a -> Bool) -> [a] -> [a]
myfilter f (x:xs) = if f x
    then x : myfilter f xs
    else myfilter f xs
myfilter _ [] = []

谢谢

5 个答案:

答案 0 :(得分:16)

最简单的方法是使用guards。您可以写pattern = value而不是pattern | boolean = value;这只会在boolean为真时匹配。因此,我们可以得到

filter1 :: (a -> Bool) -> [a] -> [a]
filter1 p (x:xs) | p x       = x : filter1 p xs
                 | otherwise = filter1 p xs
filter1 _ []                 = []

(请注意,otherwise只是True的同义词。)现在,我们在两个地方都有filter p xs,因此我们可以将其移到where子句中;这些共享的东西都有共同的模式,即使它有不同的后卫:

filter2 :: (a -> Bool) -> [a] -> [a]
filter2 p (x:xs) | p x       = x : xs'
                 | otherwise = xs'
  where xs' = filter2 p xs
filter2 _ []                 = []

(此实现是used by GHCs Prelude。)

现在,这些都不是尾递归的。这可能是不利的,但它确实使函数变得懒惰。如果我们想要一个尾递归版本,我们可以编写

filter3 :: (a -> Bool) -> [a] -> [a]
filter3 p xs = let filter3' p (x:xs) ys | p x       = next $! x:ys
                                        | otherwise = next $! ys
                     where next = filter3' p xs
                   filter3' _ []     ys             = reverse ys
               in filter3' p xs []

但请注意,这会在无限列表上失败(尽管所有其他实现都会起作用),感谢reverse,因此我们严格遵守$!。 (我想我做得对 - 我本来可以强制使用错误的变量。不过我认为我的这个变量是正确的。)

这些实现看起来都像你的。当然还有其他人。一个基于foldr

filter4 :: (a -> Bool) -> [a] -> [a]
filter4 p = let check x | p x       = (x :)
                        | otherwise = id
            in foldr check []

我们在这里利用无点风格;由于xsfilter4foldr check []的最后一个参数,我们可以忽略它,与check的最后一个参数类似。

您还可以利用列表monad:

import Control.Monad
filter5 :: MonadPlus m => (a -> Bool) -> m a -> m a
filter5 p xs = do x <- xs
                  guard $ p x
                  return x

列表monad代表不确定性。您从x中选择一个元素xs,确保它满足p,然后返回它,如果确实如此。然后将所有这些结果收集在一起。但请注意,现在这种情况更为普遍;这适用于任何MonadPlus(monad也是一个monoid;也就是说,它具有关联二进制操作mplus - ++用于列表 - 和一个标识元素mzero - []列表),例如[]Maybe。例如,filter5 even $ Just 1 == Nothingfilter5 even $ Just 2 == Just 2

我们还可以调整基于foldr的版本以获得不同的通用类型签名:

import Control.Monad
import qualified Data.Foldable as F
import qualified Data.Monoid   as M
filter6 :: (F.Foldable f, MonadPlus m, M.Monoid (m a))
        => (a -> Bool) -> f a -> m a
filter6 p = let check x | p x       = return x
                        | otherwise = mzero
            in F.foldMap check

Data.Foldable module提供了Foldable类型类,它表示可以像列表一样fold的任何结构(将结果放在通用Monoid中。)我们的filter要求对结果有MonadPlus约束,以便我们可以编写return xfoldMap函数需要一个函数,它将所有内容转换为Monoid的元素,然后将它们连接在一起。左侧f a与右侧m a之间的不匹配意味着您可以filter6一个Maybe并返回列表。

我确信filter还有(很多!)其他实现,但这些是我能想到的相对较快的6。现在,我最喜欢哪一个呢?这是直截了当的filter2和基于foldr的{​​{1}}之间的折腾。而且filter4对于它的泛型类型签名很好。 (我不认为我曾经需要像filter5那样的类型签名。)GHC使用filter6的事实是一个加分,但GHC也使用了一些时髦的重写规则,所以对我而言,如果没有这些,那就更好了。就个人而言,如果我需要通用性,我可能filter2(或filter4一起使用),但filter5绝对没问题。

答案 1 :(得分:7)

列表理解怎么样?

myfilter f xs = [x | x <- xs, f x]

答案 2 :(得分:3)

通过拉出常见的myfilter f xs代码,你至少可以把它干掉一点:

myfilter :: (a -> Bool) -> [a] -> [a]
myfilter f (x:xs) = if f x
    then x : rest
    else rest
        where rest = myfilter f xs
myfilter _ [] = []

答案 3 :(得分:2)

为了比较,这是维基百科的实施:

myfilter :: (a -> Bool) -> [a] -> [a]
myfilter _ []                 = []
myfilter f (x:xs) | f x       = x : myfilter f xs
                  | otherwise = myfilter f xs

答案 4 :(得分:1)

在Haskell中,大多数时候你可以(而且应该)使用guards而不是if-then-else:

myfilter :: (a -> Bool) -> [a] -> [a]
myfilter f (x:xs)
   | f x       = x : myfilter f xs
   | otherwise = myfilter f xs
myfilter _ [] = []

这最终基本上与使用in the standard library的定义相同。