递增值的函数如何工作?

时间:2019-02-27 01:50:28

标签: function haskell types currying partial-application

经过数年的OOP,我正在尝试学习haskell。我正在阅读Happy Haskell。它提供了以下代码:

plus :: Int -> Int -> Int
plus x y = x + y

plus' :: Int -> Int -> Int
plus' = \x -> \y -> x + y

increment :: Int -> Int
increment = plus 1

increment' :: Int -> Int
increment' = (\x -> \y -> x + y) 1

我了解plus和plus'的工作原理(它们是相同的,不同的语法)。 但是增加,我不明白。

increment :: Int -> Int

表示它需要一个int并返回一个int,对吗?但是在那之后,实际的功能是:

increment = plus 1

问题:

整数值增量在哪里?在符号x的右边是否应该有一个=或其他东西来表示该函数作为输入的整数值?像这样:

increment _ = plus 1 x

编辑:另外,增量的定义不应该是Int -> (Int -> Int),因为它需要一个int并将其传递给一个需要一个int并返回和{{1} }?

4 个答案:

答案 0 :(得分:5)

部分申请

在Haskell中,您可以具有函数的部分应用程序。看看Haskell Wiki: Partial Application

尤其是,如果您查看任何函数的类型签名,则其输入(参数)与输出之间没有真正的区别,这是因为实际上,您的函数plus :: Int -> Int -> Int是一个在给定给定值的情况下的函数一个Int,将返回另一个函数,该函数本身将使用其余参数并返回int:Int -> Int。这称为部分应用

这意味着,当您调用increment = plus 1时,您的意思是增量等于-记住部分应用程序-一个函数(由plus 1返回),该函数本身取一个整数并返回一个整数

因为Haskell是一种函数式编程语言,所以所有具有等号的对象都不是赋值,而是更像是定义,因此理解部分应用程序的一种简单方法实际上就是遵循等号:

increment = plus 1 = 
            plus 1 y = 1 + y

主要用途

如您所见,部分应用程序可用于定义更特定的功能,例如将1加到一个比加两个数字更具体的数字上。它还可以更多地使用无点样式,在这种情况下,您可以连接多个函数。

还请注意,使用像(+)这样的中缀函数,您可以部分应用到左侧或右侧,例如,这对于非交换函数很有用

divBy2 :: Float -> Float
divBy2 = (/2)

div2by :: Float -> Float
div2by = (2/)

Prelude> divBy2 3
1.5
Prelude> div2by 2
1.0

答案 1 :(得分:4)

应该是[HttpGet] public IActionResult DownloadLog() { var (path, bytes) = GetThePathAndTheNumberOfBytesIKnowHaveBeenFlushed(); var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); // this ensures that the file can be read while it's still being written return new PartialFileStreamResult(stream, "text/plain", bytes); } ,但通常increment x = plus 1 xfoo x = bar x是同一件事,因为如果foo = bar是一个在每次与任何调用时返回f的函数参数g(x),则xf的功能相同。因此g的效果也一样。

答案 2 :(得分:2)

这是因为Haskell中的所有函数都是隐式curried。因此,返回带有参数的函数的函数和带有两个返回值的参数的函数(两者的类型均为a -> a -> a )之间没有区别。因此,调用带有很少参数的plus(或任何其他函数)只会返回一个应用了已给定参数的新函数。在大多数语言中,这将是一个参数错误。另请参见point-free style

Haskell类型签名是右相关的,因此a -> a -> a -> a等效于a -> (a -> (a -> a))

答案 3 :(得分:2)

plusplus'的示例具有启发性。您会看到后者似乎没有参数,至少在等号的左侧:

plus' :: Int -> Int -> Int
plus' = \x -> \y -> x + y

让我们制作另一对版本的增量版本(我将数字“颠簸” 1之后再命名),将其转换为您给出的最终版本的一半:

bump :: Int -> Int
bump y = 1 + y

bump' :: Int -> Int
bump' = \y -> 1 + y

这两个定义之间的类比就像plusplus'之间的类比,因此它们应该是有意义的,包括后者,即使它的左侧没有形式上的论点。等号。

现在,您对bump'的理解与您对increment'的理解完全相同。实际上,我们将bump'定义为等于increment'所等于的东西。

即(稍后将看到)bump'定义的右侧,

\y -> 1 + y

等于

plus 1

这两种符号或表达式是定义“采用数字并返回多于数字的函数”的两种语法方式。

但是什么使它们相等?!嗯,(正如其他答复者所解释的),表达式plus 1部分应用的。从某种意义上说,编译器知道plus需要两个参数(毕竟是以这种方式声明的),因此,当它出现在此处仅应用于一个参数时,编译器知道它仍在等待另一个参数。它通过给您一个函数来表示“等待”,也就是说,如果再给一个参数,无论是现在还是以后,这样做都将使此功能完全适用,并且程序实际上将跳转到plus的函数体中(因此,为给定的两个参数计算x + y,即表达式1的文字plus 1和稍后给出的“一个”参数)

Haskell的乐趣和价值的关键部分是将功能本身视为事物,它们可以传递并非常灵活地从一个转换为另一个。局部应用就是将一件事(当您要确定附加值的时候,带有“太多参数”的函数)转换为“恰到好处”的函数的一种方式。您可以将部分应用的函数传递给需要特定数量参数的接口。或者,您可能只是想基于一个通用定义定义多个专用功能(因为我们可以定义通用plus以及更具体的功能,例如plus 1plus 7)。