在99 questions exercises完成第一个问题(“查找列表的最后一个元素”)之后,我想看看我的解决方案与其他人的比较,我发现{{3 }}
myLast' = foldr1 (const id)
这个this solution似乎表明foldr1
有两个参数,第一个是函数,第二个是列表。但是这个定义似乎只是作为一个参数。是否有像这样传递的参数的隐式定义?
myLast' xs = foldr1 (const id) xs
我已查找了foldr1
,const
和id
的定义,但我很难理解这三者如何协同工作以返回最后一项列表。
答案 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 id
与flip 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 foldr1
:foldr1 f (x:xs) = f x (foldr1 f xs)
== (const id) 1 (foldr1 (const id) [2, 3])
const id
结果是类型sig b -> a -> a
与flip 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
中定义了两个函数curry
和uncurry
。它们具有以下类型:
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 1
或const 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
的值传递的第二个参数)直到我们有最后一个元素。