我最近一直在教自己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 _ [] = []
谢谢
答案 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 []
我们在这里利用无点风格;由于xs
是filter4
和foldr 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 == Nothing
和filter5 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 x
。 foldMap
函数需要一个函数,它将所有内容转换为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的定义相同。