这个函数如何等同于获取列表中的最后一项?

时间:2013-09-03 17:24:14

标签: haskell

99 questions exercises完成第一个问题(“查找列表的最后一个元素”)之后,我想看看我的解决方案与其他人的比较,我发现{{3 }}

myLast' = foldr1 (const id)

这个this solution似乎表明foldr1有两个参数,第一个是函数,第二个是列表。但是这个定义似乎只是作为一个参数。是否有像这样传递的参数的隐式定义?

myLast' xs = foldr1 (const id) xs

我已查找了foldr1constid的定义,但我很难理解这三者如何协同工作以返回最后一项列表。

4 个答案:

答案 0 :(得分:6)

你说得对。在Haskell中,一个带有两个参数的函数实际上可以被视为一个函数,它接受一个参数并返回另一个带参数的函数;这被称为currying。请注意foldr1的函数签名是:

(a -> a -> a) -> [a] -> a

虽然我们经常将其视为“将函数和列表作为参数并返回值的函数”,但它实际上是“将函数作为参数并返回一个获取列表并返回的函数的函数”值“。

答案 1 :(得分:3)

正如mipadi解释的那样,这个功能正在进行中。这解释了list参数是如何实现的,但也许并不能解释实际折叠是如何工作的。

const id位有点三连胜。 foldr1期望获得类型为a -> a -> a的内容。这些功能的定义是

const :: x -> y -> x
const x y = x

id :: x -> x
id x = x

所以,将所有这些整合在一起,我们已经

const id =
\ y -> id =
\ y -> \ x -> x =
\ y x -> x

在单词中,const idflip const的作用相同;它是一个2参数函数,抛出第一个参数,然后返回第二个参数。事实并非如此明显;恕我直言,flip const会更清楚。

foldr1将使用旧值作为第一个参数调用此函数,将下一个列表元素作为第二个参数调用。此函数始终返回下一个列表元素。 foldr的最终输出是函数的最后一个输出,它将是整个列表的最后一个元素。

答案 2 :(得分:0)

你是对的,你的两个例子可以互相替换。这可以被认为是代数消除,就像在代数类中一样。

f x y = g x y

我可以取消每一方的y

f x = g x

现在我可以取消每一方的x:

f = g

查看wiki page for foldr vs foldl and foldl'以了解有关foldr的更多信息。

了解myLast'我们可以做一些代数性的操作。

myLast' [1, 2, 3]

== foldr1 (const id) [1, 2, 3] 

现在我们应该特别使用definition of foldr1foldr1 f (x:xs) = f x (foldr1 f xs)

== (const id) 1 (foldr1 (const id) [2, 3])

const id结果是类型sig b -> a -> aflip const相同,结果它的行为相同,这是一个忽略其第一个参数的函数返回第二个。例如:(const id) 1 3 == 3 *请参阅下面的内容*

== foldr1 (const id) [2, 3]

== foldr1 (const id) [3]

== 3

最后一步可能不是您的预期,但如果您检查foldr1的定义,则包含:

foldr1 _ [x] =  x

当它们只是列表中的一个元素并返回时,哪个模式匹配。

* const id如何运作?

const返回其第一个参数并忽略它,所以

const id 3   == id

const id 3 4 == id 4 == 4

答案 3 :(得分:0)

是的,你对不需要所有参数的功能的直觉就是现实。当一个函数将另一个函数作为参数返回另一个函数时,它被称为“currying”。请参阅:http://en.wikipedia.org/wiki/Currying

(作为旁注,这实际上是由 Haskell Curry 发现(或重新发现),这就是我们的Haskell得名的原因。)

如果currying的想法仍然需要时间来吸收,这可能会有所帮助: 实际上在Prelude中定义了两个函数curryuncurry。它们具有以下类型:

Prelude> :t curry
curry :: ((a, b) -> c) -> a -> b -> c
Prelude> :t uncurry
uncurry :: (a -> b -> c) -> (a, b) -> c

uncurry采用2参数 curried 函数(或者一个函数,它接受一个参数的函数,返回一个参数的函数),并生成一个 uncurrried function,或者一次性获取所有参数的函数(作为元组。)

curry,正如你可能暗示的名称及其类型一样,是另一种方式,因此它需要一个 curried 函数(一个函数可以立即获取所有参数)并生成一个函数,该函数接受一个参数并返回一个接受另一个参数的函数。

大多数编程语言默认以不受欢迎的方式工作,您提供所有参数并获得结果,而Haskell默认为curried。

现在举一个uncurry的示例,我们可以使用一个简单的函数(+)

Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> (+) 1 2
3
Prelude> :t (+)
(+) :: Num a => a -> a -> a
Prelude> :t uncurry (+)
uncurry (+) :: Num c => (c, c) -> c
Prelude> uncurry (+) (1,2)
3

而且,如果我们想:{/ p>,我们也可以使用const执行此操作

Prelude> :t const
const :: a -> b -> a
Prelude> const 1 2
1
Prelude> :t uncurry const
uncurry const :: (c, b) -> c
Prelude> uncurry const (1,2)
1

但是,const的未经验证的版本不是很有用,因为如果你必须预先指定所有的参数,那么拥有一个带有两个参数的函数并且总是返回第一个参数是没有意义的。

const非常有用,因为它是有条件的,可以在需要函数的地方给出,该函数需要两个参数并简单地返回第一个参数。

foldr1类似,例如:

Prelude> :t foldr1
foldr1 :: (a -> a -> a) -> [a] -> a

第一个参数是两个参数的curried函数。 由于函数的返回值可以是函数,因此const

也可以
Prelude> :t const id
const id :: b -> a -> a

const id只接受任何类型b的参数并返回id函数。

因此,如果我们可以逐步应用const id

Prelude> :t const id 1
const id 1 :: a -> a

const id 1const id anyOtherValueHere只返回id函数。 可以使用哪个喜欢这样:

Prelude> :t const id "Giraffe" 100
const id "Giraffe" 100 :: Num a => a
Prelude> const id "Giraffe" 100
100
Prelude> :t const id (\a b -> undefined) 100
const id (\a b -> undefined) 100 :: Num a => a
Prelude> const id (\a b -> undefined) 100
100

所以,const真的忽略了它的第二个论点。在上面,就像将id应用于100。

因此,foldr1 (const id)只需一个列表并继续将id应用于每组两个元素并保留第二个元素(因为const id会返回id的值传递的第二个参数)直到我们有最后一个元素。