澄清Haskell函数类型参数

时间:2017-05-27 21:15:30

标签: haskell functional-programming

在最近的工作表中,我被要求解释为什么f中的函数f g x = g (g x) x没有类型。

我对Haskell很陌生,我很不清楚如何在不知道任何有关函数的细节的情况下计算出左右表达式的关联顺序。似乎g应定义为:

g :: a -> b,假设x的类型为a - 但这似乎会立即导致RHS问题,g (g x) x似乎意味着{{1}需要2个参数,一个类型为g,另一个类型为b。此外,我还坚持如何阅读LHS,即a接受2个参数:函数f和变量g还是简单地接受1个参数,x

我想知道是否有人可以告诉我应该如何阅读这些表达方式?

3 个答案:

答案 0 :(得分:7)

为了能够回答这样的问题,你需要像Haskell编译器一样思考。我们有一个功能。

f g x = g (g x) x

为了使其输入良好,我们需要找到fgx的类型。现在,f有两个参数,所以

f :: a -> b -> c
g :: a
x :: b

我们也知道g x必须有意义作为表达式(我们会说g x是"格式良好"),所以g必须是一个函数可以应用x

也是如此
g :: b -> t0

目前,t0是一个类型变量。我们不知道g x的结果;我们只知道它有意义并且#34;。现在,外部g (g x) x也必须有意义。因此,g必须是我们可以应用的类型g x(其类型为t0,如前所述)和x(其类型为b )能够获得类型c的结果(函数的返回类型)。所以

g :: t0 -> b -> c

现在,问题发生了。我们看到g有三个声明:ab -> t0t0 -> b -> c。为了让g拥有所有这三种类型,他们必须统一。也就是说,通过插入某些变量的值,它们必须能够变得相同。 a没有任何问题,因为它是一个自由变量并且不依赖于任何其他变量,所以我们可以设置"它到我们想要的任何东西。因此b -> t0t0 -> b -> c必须相同。在Haskell中,我们将其写为

(b -> t0) ~ (t0 -> b -> c)

圆括号(在类型中)是右关联的,因此这相当于

(b -> t0) ~ (t0 -> (b -> c))

为了使两个函数类型相同,它们的参数必须相同,所以

b ~ t0
t0 ~ (b -> c)

通过传递属性,这意味着

b ~ (b -> c)

所以b是一种将自己作为参数的类型。这就是Haskell所说的无限类型,并且当前标准不允许这样做。因此,为了使您编写的函数可以接受,b必须是Haskell中不存在的类型。因此,f不是有效的Haskell函数。

答案 1 :(得分:6)

相关规则是:

  1. 应用程序被写为并列,即JsPath.readf x应用于f

  2. 括号用于分隔表达式,而不是用于函数应用程序,即xf (g x)应用于f。 (g x本身就是g xg的应用。)

  3. 应用程序关联到左侧,即x

  4. 将这些放在一起,我们可以看到f x y = (f x) y,即g (g x) x = (g (g x)) xg (g x)的应用,其中x本身就是g (g x)的应用到g

    我还应该提到Haskell中的所有函数都只有一个参数。在一个函数看起来有多个参数的地方,确实存在一些问题:

    g x

    换句话说,f x y z = ((f x) y) z 是一个函数,它接受一个参数并返回一个接受参数的函数,并返回一个接受参数并返回一个值的函数。你可能会看到为什么我们有时候更喜欢撒谎并说一个函数需要多个参数,但从技术上讲它并不正确。 “接受多个参数”的函数实际上是一个返回函数的函数,它可以返回一个函数,依此类推。

答案 2 :(得分:5)

  1. 功能应用程序是左关联的:package stack; public class info { private String namn; public void setTitel(String film) { namn=film; } public String getTitel() { return namn; } public void filmnamn() { System.out.printf("Movie title: %s", getTitel()); } } 解析为a b c
  2. 函数定义是lambda表达式的语法糖:(a b) c表示f x y = ...
  3. 自动计算具有多个参数的Lambda:f = \x y -> ...表示\x y -> ...
  4. 因此

    \x -> (\y -> ...)

    装置

    f g x = g (g x) x
    

    现在让我们尝试派生f = \g -> (\x -> (g (g x)) x) 的类型。让我们给它一个名字:

    f

    但究竟是什么f :: ta ta被定义为lambda,因此其类型涉及f(它是一个函数):

    ->

    即。对于某些类型\g -> (\x -> (g (g x)) x) :: tb -> tc g :: tb \x -> (g (g x)) x :: tc \g -> ...tb -> tc的类型为tbtc(参数)的类型为g,并且结果的类型(函数体)是tb

    由于整个事情都与tc绑定,我们有

    f

    我们还没有完成ta = tb -> tc

    tc

    \x -> (g (g x)) x :: td -> te
    x :: td
    (g (g x)) x :: te
    

    函数体(我们称之为tc = td -> te 的类型)包含一个函数(必须是)对变量te的应用。由此得出:

    x

    ,因为

    g (g x) :: td -> te
    

    再次向下钻,我们有

    x :: td
    (g (g x)) x :: te
    

    因为将g :: tf -> (td -> te) g x :: tf 应用于g必须包含g x类型。最后,

    td -> te

    ,因为

    g :: td -> tf
    

    现在我们有x :: td g x :: tf 的两个方程式:

    g

    因此

    g :: tf -> (td -> te)
    g :: td -> tf
    

    这里我们遇到了一个问题:tf = td td -> te = tf tf -> te = tf 是根据自身定义的,给出类似

    的内容
    tf

    即。一个无限大的类型。这是不允许的,这就是tf = (((((... -> te) -> te) -> te) -> te) -> te) -> te 没有有效类型的原因。