【问题标题】:Implementing filter using HoF in Haskell在 Haskell 中使用 HoF 实现过滤器
【发布时间】:2010-09-03 04:10:27
【问题描述】:

我正在尝试编写一个函数,该函数接受一个谓词 f 和一个列表,并返回一个列表,该列表包含满足 f 且具有保留顺序的所有项目。诀窍是只使用高阶函数 (HoF),没有递归,没有推导,当然也没有过滤器。

【问题讨论】:

    标签: list haskell


    【解决方案1】:

    你可以用foldr表达filter

    filter p = foldr (\x xs-> if p x then x:xs else xs) []
    

    【讨论】:

    • 谢谢,这正是我想要的!
    【解决方案2】:

    我认为你可以这样使用map

    filter' :: (a -> Bool) -> [a] -> [a]
    filter' p xs = concat (map (\x -> if (p x) then [x] else []) xs)
    

    你看到了吗?将列表转换为列表列表,如果你想要的元素没有通过p,它会变成一个空列表

    filter' (> 1) [1 , 2, 3 ] 将是:concat [ [], [2], [3]] = [2,3]

    prelude 中有 concatMap 使代码更简单:P

    代码应如下所示:

    filter' :: (a -> Bool) -> [a] -> [a]
    filter' p xs = concatMap (\x -> if (p x) then [x] else []) xs
    

    按照 sclv 的建议,使用 foldr 可以通过以下方式完成:

    filter'' :: (a -> Bool) -> [a] -> [a]
    filter'' p xs = foldr (\x y -> if p x then (x:y) else y) [] xs
    

    【讨论】:

    • 还有其他方法可以做到,我对haskell有点菜鸟:P
    • 与其使用concat,不如使用catMaybes :: [Maybe a] -> [a]
    【解决方案3】:

    你这样做显然是为了学习,所以让我向你展示一些很酷的东西。首先,让我们重新思考一下,过滤器的类型是:

    filter :: (a -> Bool) -> [a] -> [a]
    

    有趣的是最后一位[a] -> [a]。它分解一个列表并建立一个新列表。

    递归模式在 Haskell(和其他函数式语言)中非常普遍,以至于人们已经为其中一些模式想出了名称。最简单的是变形,它是双重变形。最后我会告诉你这与你的直接问题有什么关系。

    固定点

    必备知识FTW!

    Nothing 的类型是什么?启动 GHCI,它说Nothing :: Maybe a,我不会不同意。 Just Nothing 呢?再次使用 GHCI,它会显示 Just Nothing :: Maybe (Maybe a),这也是完全有效的,但是这个 Nothing 嵌入在任意数量甚至无限数量的 Justs 中的值呢?即,这个值的类型是什么:

    foo = Just foo
    

    Haskell 实际上不允许这样的定义,但稍作调整我们就可以做出这样的类型:

    data Fix a = In { out :: a (Fix a) }
    
    just :: Fix Maybe -> Fix Maybe
    just = In . Just
    
    nothing :: Fix Maybe
    nothing = In Nothing
    
    foo :: Fix Maybe
    foo = just foo
    

    哇,够近了!使用相同的类型,我们可以创建任意嵌套的nothings:

    bar :: Fix Maybe
    bar = just (just (just (just nothing)))
    

    旁白:Peano 算术有人吗?

    fromInt :: Int -> Fix Maybe
    fromInt 0 = nothing
    fromInt n = just $ fromInt (n - 1)
    
    toInt :: Fix Maybe -> Int
    toInt (In Nothing) = 0
    toInt (In (Just x)) = 1 + toInt x
    

    这个Fix Maybe 类型有点无聊。这是一个定点为列表的类型:

    data L a r = Nil | Cons a r
    type List a = Fix (L a)
    

    这种数据类型将有助于演示一些递归模式。

    有用的事实:Cons a r 中的 r 称为递归站点

    变质

    变质是一种破坏结构的操作。列表的 catamorphism 更为人所知的是折叠。现在变质的类型可以这样表示:

    cata :: (T a -> a) -> Fix T -> a
    

    可以等价写成:

    cata :: (T a -> a) -> (Fix T -> a)
    

    或者用英文写成:

    您给我一个将数据类型化简为值的函数,我将给您一个将其定点化简为值的函数。

    其实我撒谎了,类型真的是:

    cata :: Functor T => (T a -> a) -> Fix T -> a
    

    但是原理是一样的。请注意,T 仅根据递归站点的类型进行参数化,因此 Functor 部分实际上是在说“给我一种操作所有递归站点的方法”。

    那么cata可以定义为:

    cata f = f . fmap (cata f) . out
    

    这是相当密集的,让我详细说明。这是一个三步过程:

    • 首先,给我们一个Fix t,这是一个很难玩的类型,我们可以通过应用out(来自Fix的定义)给我们一个t (Fix t)来使它更容易。
    • 接下来我们要将t (Fix t) 转换为t a,我们可以通过一厢情愿的方式使用fmap (cata f);我们假设我们将能够构造cata
    • 最后,我们有一个t a,我们想要一个a,所以我们只使用f

    之前我说过列表的变态称为折叠,但cata 目前看起来不像折叠。让我们根据cata 定义一个折叠函数。

    回顾一下,列表类型是:

    data L a r = Nil | Cons a r
    type List a = Fix (L a)
    

    这需要是一个有用的函子,这是直截了当的:

    instance Functor (L a) where
      fmap _ Nil = Nil
      fmap f (Cons a r) = Cons a (f r)
    

    所以专门cata 我们得到:

    cata :: (L x a -> a) -> List x -> a
    

    我们几乎就在那里:

    construct :: (a -> b -> b) -> b -> L a b -> b
    construct _ x (In Nil) = x
    construct f _ (In (Cons e n)) = f e n
    
    fold :: (a -> b -> b) -> b -> List a -> b
    fold f m = cata (construct f m)
    

    好的,变质一次将数据结构分解为一层。

    变形

    列表上的变形是展开。展开比折叠对偶少为人知,它们的类型如下:

    unfoldr :: (b -> Maybe (a, b)) -> b -> [a]
    

    正如您所见,变形构建了数据结构。这是更通用的类型:

    ana :: Functor a => (a -> t a) -> a -> Fix t
    

    这应该立即看起来很熟悉。这个定义也让人联想到变质。

    ana f = In . fmap (ana f) . f
    

    这只是同样的事情颠倒过来。从ana 构造unfold 甚至比从cata 构造fold 还要简单。注意Maybe (a, b)L a b 之间的结构相似性。

    convert :: Maybe (a, b) -> L a b
    convert Nothing = Nil
    convert (Just (a, b)) = Cons a b
    
    unfold :: (b -> Maybe (a, b)) -> b -> List a
    unfold f = ana (convert . f)
    

    理论付诸实践

    filter 是一个有趣的函数,因为它可以从变形或变形构造。这个问题的其他答案(迄今为止)也使用了变态,但我会用两种方式定义它:

    filter p = foldr (\x xs -> if p x then x:xs else xs) []
    
    filter p =
      unfoldr (f p)
     where
      f _ [] =
        Nothing
      f p (x:xs) =
        if p x then
          Just (x, xs)
        else
          f p xs
    

    是的,是的,我知道我在展开版本中使用了递归定义,但请原谅我,我教了你很多理论,而且无论如何 filter 不是递归的。

    【讨论】:

      【解决方案4】:

      我建议你看看foldr

      【讨论】:

        【解决方案5】:

        那么,是否允许 if 和空列表?

        filter = (\f -> (>>= (\x -> if (f x) then return x else [])))
        

        【讨论】:

        • 这与我的回答完全相同,只是它使用 list monad.. 还是有其他区别?此外,它非常不清楚且难以阅读..
        • 好吧,如果你用 mzero 改变 [] 并导入 Control.Monad,你会得到一个在任何 monad 中工作的过滤器,而不仅仅是在 List 中。然而,它不是 filterM。
        【解决方案6】:

        对于整数列表

        filter2::(Int->Bool)->[Int]->[Int]
        filter2 f []=[]
        filter2 f (hd:tl) = if f hd then hd:filter2 f tl
                        else filter2 f tl
        

        【讨论】:

        • 这个想法是使用递归。
        【解决方案7】:

        我忍不住用另一种方式回答这个问题,这次完全没有递归。

        -- This is a type hack to allow the y combinator to be represented
        newtype Mu a = Roll { unroll :: Mu a -> a }
        -- This is the y combinator
        fix f = (\x -> f ((unroll x) x))(Roll (\x -> f ((unroll x) x)))
        
        filter :: (a -> Bool) -> [a] -> [a]
        filter =
          fix filter'
         where
          -- This is essentially a recursive definition of filter
          -- except instead of calling itself, it calls f, a function that's passed in
          filter' _ _ [] = []
          filter' f p (x:xs) =
            if p x then
              (x:f p xs)
            else
              f p xs
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-02-06
          • 1970-01-01
          • 2016-06-04
          • 2014-11-22
          相关资源
          最近更新 更多