Haskell 中的类型签名与函数方程

发布于 2024-10-13 02:06:12 字数 1535 浏览 3 评论 0原文

我是 Haskell 和函数式编程的新手。我正在通读 Real World Haskell,我意识到我对一些例子感到困惑。

具体来说,这是在第 9 章的“谓词的领域特定语言”部分中,具有 wxyz 参数的示例。

我把它归结为:

为什么这段代码可以编译?

f :: Int -> (Int -> Int)
f x y = x+y

main = do
  let q = f 4 5
  putStr  (show (q))

根据类型签名,f 显然接受 1 个参数并返回一个函数。 但是,似乎我可以编写函数方程,因此它将接受两个参数并返回一个 int 。 为什么这可能?这是否意味着类型签名被忽略?

这是柯里化吗?这是某种关闭吗? 如果我正确理解这个http://www.haskell.org/haskellwiki/Currying,那么这似乎与那里定义的柯里化相反 - 我的 f 函数接受多个参数而不是单个参数!

另外,任何人都可以回答,请提供某种 Haskell 文档的链接,其中说明了这种能力(如果可能的话)。

编辑:

思考了一段时间后,你们两个似乎暗示的是:

1)这个语法是语法糖,f将始终有一个参数,无论等式中写了多少个参数

2)在应用时对于 f,函数体将(总是?)转换为存根(实际上是返回的函数),其中 x 固定为给定的参数 (4),y 是一个参数。

3) 然后将这个新函数应用于 5,替换 y,然后计算 + 函数。

我真正感兴趣的是,它到底在哪里说了类似“在函数方程中,如果你写了多个参数,它实际上是语法糖,并且实际上会发生以下情况......”,正如我上面所写的。 或者说这对除了我之外的每个人来说都是如此明显?

编辑二:

真正令人大开眼界的答案在下面的@luqui评论中,不幸的是我不认为我可以将评论标记为答案。

事实是 fxy = ... 实际上是语法糖: f = \x ->; \y-> ...

对我来说,下面每个人所说的其他一切都源于此。

我在 Haskell 的温和介绍中找到了一些来源,在这里: http:// /haskell.cs.yale.edu/tutorial/functions.html 在第 3.1 节中,称为 Lambda 抽象。

事实上,方程式:

inc x = x+1 添加 xy = x+y

实际上是:

的简写

inc = \x ->; x+1 添加 = \xy -> x+y

虽然它不使用短语“语法糖”,但它使用更多,呃,数学导向的词“速记”,但作为一名程序员,我将其读为“糖”:-)

I'm new to Haskell and to functional programming. I'm reading through Real World Haskell, and I've realized I'm confused by a few examples.

Specifically this is in Chapter 9, in the section "A Domain specific language for predicates", the examples which have the w x y z parameters.

I've boiled it down to this:

Why does this code compile?

f :: Int -> (Int -> Int)
f x y = x+y

main = do
  let q = f 4 5
  putStr  (show (q))

According to the type signature, f is clearly accepting 1 parameter and returning a function.
However, it seems I can write the function equation so it will accept two parameters and return an int.
Why is this possible? Does this mean the type signature is ignored?

Is this currying? Is this some kind of closure?
If I understand this http://www.haskell.org/haskellwiki/Currying correctly, then it seems to be somewhat in reverse to currying as defined there - my f function is taking multiple arguments instead of a single one!

Also, can anyone answering please provide a link to some sort of Haskell documentation where this ability is stated (if possible at all).

EDIT:

After thinking about this for some time, what you two seem to be implying is that:

1) This syntax is syntactic sugar, f will always have a single parameter, no matter how many parameters are written in the equation

2) Upon application of f, the function body will (always?) be transformed into a stub (in effect, the returned function), where x is fixed to the parameter given (4), and y is a parameter.

3) Then this new function is applied to 5 which replaces y, and then the + function is evaluated.

What I was really interested in was, where exactly does it say something like "in the function equation, if you write more than one parameter, it's really syntactic sugar, and the following actually happens..." as I wrote above.
Or is that so obvious to everyone except me?

Edit II:

The real eye-opener answer was in @luqui comment below, unfortunately I don't think I can mark a comment as an answer.

It is the fact that
f x y = ...
is actually syntactic sugar for:
f = \x -> \y -> ...

And for me, everything else everyone below said follows from this.

I found a sort of source for this in the Gentle Introduction to Haskell, here: http://haskell.cs.yale.edu/tutorial/functions.html in section 3.1, called Lambda Abstractions.

In fact, the equations:

inc x = x+1
add x y = x+y

are really shorthand for:

inc = \x -> x+1
add = \x y -> x+y

While it doesn't use the phrase "syntactic sugar", it uses the more, uh, mathematically oriented word "shorthand", but as a programmer I read this as "sugar" :-)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

叫嚣ゝ 2024-10-20 02:06:13

经过更多思考后,我认为完整的解释应该是这样的:

Haskell 函数只能接受一个参数并返回一个参数。
Haskell 允许我们假装传递了多个参数,但这种形式被视为一系列嵌套的 lambda 函数。

f x y = x + y

被视为

(1) f = \x -> \y -> x + y

这种处理也适用于 lambda 函数
\xy-> x + y
被视为
\x-> \y-> x + y

这允许我们将类型声明视为左关联,即:
f::Int->整数-> INT
实际上是
f :: (Int -> (Int -> Int))
这完全符合上面的 (1):f 没有参数,但返回一个接受 Int 的函数。该函数又返回一个接受另一个 Int 的函数,并返回一个 Int。

这意味着如果我们想从函数返回一个函数,我们不需要做任何特殊的事情,因为这是 Haskell 的“默认”模式。

这也意味着给定类型声明
f::Int->整数-> INT
我们可以用 0、1 或 2 个参数编写 f 的实现(“方程”)。如果指定了一个或两个参数,编译器将生成必要的 lambda 表达式以符合以下形式
f :: (Int -> (Int -> Int))

f = \x -> \y -> x + y

f x = \y -> x + y  -- \x -> is generated

f x y = x + y -- \x -> \y is generated

但在每种情况下,看似接受两个参数的函数应用程序都将成功编译,因为它总是会被转换为第一种形式,例如,

f 4 5 --> (\x -> (\y -> x + y) 5 ) 4

其中内部函数应用程序将返回柯里化形式 (x + 5)

这使得部分函数应用成为可能,我们可以只给 f 一个参数并返回一个部分函数。

另外,更改函数类型中的优先级:

f'' :: (Int -> Int) -> Int

更改含义 - 我们接受一个获取 Int 并返回一个作为单个参数的函数,然后返回一个 Int。
假设我们已经在某处定义了这个函数,那么使用整数参数调用这个函数,例如

f'' 4 5

将无法编译。

编辑:

此外,即使最后一个参数位于括号中,或者是类型声明,这也成立。

例如,在下面的内容中,最后一对括号是可选的,因为如果它们不存在,编译器无论如何都会将它们放入“lambda'd”形式。

t4 :: (Int -> Int) -> (Int -> Int)
t4 f i = f i + i

可以这样应用:

t4 (\x -> x*10) 5

另外,给定:

type MyIntInt = Int -> Int

然后:

t5 :: MyIntInt -> MyIntInt

相当于 t4,但令人困惑,因为 MyIntInt 的含义在两个地方都不同。
第一个是第一个参数的类型。
第二个被“扩展”为 Int -> Int(可能是因为运算符的右结合性),这意味着 t5 可以在函数方程和函数应用程序中接受 0 到 2 个参数(而实际上总是接受 0 并返回嵌入的 lambda,正如我上面详细介绍的) )。

例如,我们可以像 t4 一样写 t5:

t5 f i = f i + i

After thinking some more about this, I think the full explanation should be something along the lines of:

An Haskell function can only accept a single argument and return one parameter.
Haskell allows us to pretend that several arguments are passed, but this form is treated as a series of nested lambda functions.

f x y = x + y

is treated as

(1) f = \x -> \y -> x + y

This treatment is true for lambda functions as well
\x y -> x + y
is treated as
\x -> \y -> x + y

This allows us to treat type declaration as left-associative, that is:
f :: Int -> Int -> Int
is actually
f :: (Int -> (Int -> Int))
which fits exactly to (1) above: f has no arguments, but is returning a function which accepts Int. This function in turn returns a function which accepts another Int, and returns an Int.

This means that if we want to return a function from a function, we don't have to do anything special, since that's Haskell's "default" mode.

This also means that given the type declaration
f :: Int -> Int -> Int
We can write f's implementatin ("equation") with 0, 1 or 2 parameters. If one or two parameter are specified, the compiler will generate the necessary lambdas to comply with the form
f :: (Int -> (Int -> Int))

f = \x -> \y -> x + y

f x = \y -> x + y  -- \x -> is generated

f x y = x + y -- \x -> \y is generated

But in each of these cases, the function application appearing to accept two parameters will compile successfully, since it will always be translated to the first form, e.g.

f 4 5 --> (\x -> (\y -> x + y) 5 ) 4

Where the inner function application will return the curried form (x + 5)

This enables partial function application, where we can give f just one parameter and get back a partial function.

Also, changing the precedence in the function type:

f'' :: (Int -> Int) -> Int

changes the meaning - we accept a function getting an Int and returning one as the single parameter, and return an Int.
Assuming we've defined this function somewhere, then calling this function with Integer parameters, e.g.

f'' 4 5

will not compile.

Edit:

Also, even if the last arguments are in parenthesis, or are a type declaration, this holds.

E.g., in the following, the last pair of parenthesis is optional, since if they weren't there the compiler would put them anyway to get to the "lambda'd" form.

t4 :: (Int -> Int) -> (Int -> Int)
t4 f i = f i + i

Can be applied thus:

t4 (\x -> x*10) 5

Also, given:

type MyIntInt = Int -> Int

Then:

t5 :: MyIntInt -> MyIntInt

Is equivalent to t4, but confusing, since the meaning of MyIntInt is different in both places.
The first one is the type of the first parameter.
The second one is "expanded" into Int -> Int (probably because of the right-associativity of the operator), which means that t5 can appear to accept 0 to 2 parameters in the function equation and function application (while actually always accepting 0 and returning embedded lambdas, as I've detailed above).

E.g. we can write t5 just like t4:

t5 f i = f i + i
看轻我的陪伴 2024-10-20 02:06:12

这是柯里化。正如类型签名所述,f 仅采用一个参数。那将是 4。然后它返回一个函数,该函数立即应用于 5。事实上,这两个类型签名:

Int ->整数-> Int

Int -> (Int -> Int)

在 Haskell 中是等价的。

编辑:关于部分申请的链接,我在页面内找到了它你提供了,解释了这一点。

编辑2:您询问 Haskell 的柯里化行为是在哪里定义的。我不知道这是否是您要找的:Haskell 98 修订报告,位于3.3 柯里化应用程序和 Lambda 抽象 部分说:

函数应用写作e1 e2。应用程序关联到左侧,因此 (fx) y 中的括号可以省略。

It is currying. As the type signature says, f takes only one parameter. That would be 4. It then returns a function, which is immediately applied to 5. In fact, these two type signatures:

Int -> Int -> Int

and

Int -> (Int -> Int)

are equivalent in Haskell.

EDIT: this link about Partial Application, which I found inside the page you provided, explains this.

EDIT 2: You asked where the currying behavior of Haskell is defined. I don't know if this is what you're looking for: the Haskell 98 Revised Report, in section 3.3 Curried Applications and Lambda Abstractions, says:

Function application is written e1 e2. Application associates to the left, so the parentheses may be omitted in (f x) y.

如日中天 2024-10-20 02:06:12

-> 运算符是右关联的,即 t -> 。 t-> tt -> 相同(t -> t)

重新编写,

f x y = x+y

如果您以等效形式

f x = \y -> x + y

这种联系应该会变得显而易见。

The -> operator is right-associative, i.e. t -> t -> t is the same as t -> (t -> t).

If you re-write

f x y = x+y

in the equivalent form

f x = \y -> x + y

this conncetion should become obvious.

土豪我们做朋友吧 2024-10-20 02:06:12

这绝对是有点柯里化了。虽然我无法立即找到文档中明确说明此行为的位置,但我们所要做的就是检查一下有关柯里化的数学知识。

我们知道,具有签名 Int ->(Int -> Int) 的函数接受一个 Int,并返回一个 函数接受一个 Int 并返回一个 Int。我们可以只提供获得最终 int 所需的两个 Int,就像在您的示例中一样:

f :: Int -> (Int -> Int)
f x y = x+y

如果我们只提供第一个参数,我们会返回一个需要另一个参数的函数。咖喱面包和黄油。

简而言之,柯里化是右关联。换句话说,Int -> (Int -> Int)Int->Int->Int 相同,只是我们添加了括号以使其更加明显:

f 3

没有缺少参数,但实际上返回一个Int->Int类型的函数。

这有点像数学中学习加法的关联属性

3 + 2 + 1 = (3 + 2) + 1 = 3 + (2 + 1)

无论我们如何放置括号,结果都是相同的。

This is definitely a bit of currying. While I can't immediately find where in the documentation it explicitly states this behavior, all we have to do is check out our math a little about currying.

As we know, a function with the signature Int ->(Int -> Int) takes an Int, and returns a function that takes an Int and returns an Int. And we could just provide both of the Ints it needs to get that final int, like in your example:

f :: Int -> (Int -> Int)
f x y = x+y

And if we provide just the first argument, we get back a function that needs another argument. The bread and butter of currying.

Simply put, currying is right-associative. In other words, Int -> (Int -> Int) is the same as Int->Int->Int, just we added parenthesis to make it more obvious that:

f 3

Isn't missing an argument, but actually returns a function of type Int->Int.

It's kind of like back in math when you learn the associative property of addition.

3 + 2 + 1 = (3 + 2) + 1 = 3 + (2 + 1)

The result is the same, regardless of how we put our parentheses.

飘然心甜 2024-10-20 02:06:12

它并不是真正的语法糖,它只是 Haskell 中函数应用程序的工作方式。

考虑一下:

f :: Int -> Int -> Int -> Int 
f x y z = x + y + z

g = f 4
h = g 4 5

f 4 4 5 -- 13
g 4 5   -- 13
g 6     -- 13

你可以在 ghci 中使用这个来确认。 g 是函数 f 的部分应用 - 它的类型是 g :: Int ->整数->整数。

您还可以这样写:

((f 4) 4) 5  -- 13 

在这种情况下,(f 4) 返回一个带有两个附加参数的部分应用函数,((f 4) 4) 返回一个部分应用函数,该函数采用一个参数,整个表达式减少到 13。

It's not really syntax sugar, it's just how function application works in Haskell.

Consider:

f :: Int -> Int -> Int -> Int 
f x y z = x + y + z

g = f 4
h = g 4 5

f 4 4 5 -- 13
g 4 5   -- 13
g 6     -- 13

You can play with this in ghci to confirm. g is a partial application of function f - its type is g :: Int -> Int -> Int.

You can also write:

((f 4) 4) 5  -- 13 

In this case (f 4) returns a partially applied function that takes two additional arguments, ((f 4) 4) returns a partially applied function that takes one argument, and the whole expression reduces to 13.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文