【问题标题】:How do I write takeWhile using foldl?如何使用 foldl 编写 takeWhile?
【发布时间】:2017-07-09 05:43:11
【问题描述】:

所以我正在从现实世界的 haskell 书中做练习,并使用 foldl 为 takeWhile 函数编写了以下代码。

myTakeWhile' :: (a->Bool) -> [a] -> [a]
myTakeWhile' f xs = foldl step [] xs
    where step x xs | f x       = x:(myTakeWhile' f xs)
                    | otherwise = []

这会产生以下错误,我无法理解。

Couldn't match type ‘a’ with ‘[a]’
      ‘a’ is a rigid type variable bound by
          the type signature for myTakeWhile' :: (a -> Bool) -> [a] -> [a]
          at p1.hs:17:17
    Expected type: [a] -> [a] -> [a]
      Actual type: a -> [a] -> [a]
    Relevant bindings include
      step :: a -> [a] -> [a] (bound at p1.hs:19:11)
      xs :: [a] (bound at p1.hs:18:16)
      f :: a -> Bool (bound at p1.hs:18:14)
      myTakeWhile' :: (a -> Bool) -> [a] -> [a] (bound at p1.hs:18:1)
    In the first argument of ‘foldl’, namely ‘step’
    In the expression: foldl step [] xs

【问题讨论】:

  • 你确定这个练习没有要求你使用foldr吗?使用foldl 实现takeWhile 毫无意义。用foldr 实现比较简单。
  • 练习没有提到使用哪个折叠。

标签: list haskell fold


【解决方案1】:

编辑

1)您的主要错误是您不了解fold 为您执行递归,您不必自行引用函数myTakeWhile',而且折叠还有另一种类型:

foldl: (b -> a -> b) -> b -> [a] -> b

你在 where 子句的 step 函数中的类型错误是因为它。

2)你的第二个错误,因为foldl的定义,你的takeWhile会从头到尾创建,所以,你应该把列表倒过来(这一切只是因为你正在询问如何使用 foldl)

您可以通过使用简单的lambdaif else 来做到这一点,这样更具可读性:

myTakeWhile' f xs = foldl (\rs x -> if f x then x:rs else []) [] (reverse xs)

示例:

myTakeWhile' (<10) [1..20]

[1,2,3,4,5,6,7,8,9]

但是,有了这个,你就不能遍历无限列表,就像这样:

myTakeWhile' (<10) [1..]

尝试创建所有未计算的表达式时会挂起。

无论如何,如果你想使用guardswhere,你应该这样做:

myTakeWhile f xs = foldl step [] (reverse xs)
  where step rs x | f x = x : rs
                  | otherwise = []

3) 最重要的是:

正如@amalloy 所说,如果您使用foldr 来实现takeWhile,它会更合适:)。了解两者之间的差异在某些情况下至关重要,使用一种或另一种可能会导致您的性能问题,为此,您可以阅读以下问题:

foldl versus foldr behavior with infinite lists

4) 最后,使用foldr的正确替代方案:

myTakeWhile'' f xs = foldr (\x rs -> if f x then x:rs else []) [] xs

有了这个,你可以使用无限列表:

myTakeWhile' (<10) [1..]

您可以像这样看到折叠方式的不同:

Prelude> foldr (\x y -> concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(1+(2+(3+(4+(5+(6+(7+(8+(9+(10+(11+(12+(13+0)))))))))))))"

Prelude> foldl (\x y -> concat ["(",x,"+",y,")"]) "0" (map show [1..13])
"(((((((((((((0+1)+2)+3)+4)+5)+6)+7)+8)+9)+10)+11)+12)+13)"

本文来自https://wiki.haskell.org/Fold

【讨论】:

  • 这实现了filter,而不是takeWhile
  • myTakeWhile' f xs = foldr (\x rs -&gt; if f x then x:rs else []) [] xs 有效,虽然我想知道我写的 where 步骤表达式有什么问题
  • 您还应该提到foldl 是错误的折叠用于takeWhilefoldr 更合适。
  • @amalloy 这也是真的,我会提一下,再次非常感谢!
  • 我的意思是,当然,它会起作用,但它没有充分的理由变得更糟。
猜你喜欢
  • 2011-09-04
  • 2018-02-07
  • 2014-04-17
  • 1970-01-01
  • 2019-05-20
  • 2021-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多