【问题标题】:operator and function precedence in HaskellHaskell中的运算符和函数优先级
【发布时间】:2014-06-26 12:23:37
【问题描述】:

所以这不起作用:

take 50 iterate (* 2) 1

因为它需要在 take 的第二个参数中加上括号。我的问题是为什么。

Haskell 正确地看到了类型的差异:

 Couldn't match expected type `[a0]'
            with actual type (a1 -> a1) -> a1 -> [a1]'

我的问题是:

1) haskell 似乎首先尝试在 iterate 函数之前解析 take 50 函数。为什么haskell会这样做?在数学中,如果你有 f g t w u (x),你首先要评估 u(x),然后再做其他事情。在这种情况下,为什么haskell 从评估 f 开始?

2) Haskell 足够聪明,可以检测到实际类型是:

(a1 -> a1) -> a1 -> [a1]

现在,如果它看到这个函数的输出是[a1],一个可以和预期类型[a0]统一的类型,为什么haskell不统一呢?

3) 为什么 $ 运算符会解决这个问题?我知道:

($) :: (a -> b) -> a -> b

所以基本上这个操作符所做的就是说“write FUNCTION $ ARGUMENT”,然后你会得到在那个参数处评估的函数的值。在 take 50 的情况下,类型是:

take 50 :: [a2]->[a2]
take 50 $ :: a->b 
where a ~a2 and b~b2 then
take 50 $ :: [a2]->[a2]

所以基本上我的情况与第一种情况相同,没有使用括号或 $。这种情况是我需要一个类型为 [a2] 的参数(haskell 称它为 [a0] 但它是相同的。所以.. 为什么 haskell 将 [a2] 与 (a1 -> a1) -> a1 -> [a1] 统一起来当我使用 $ 但我不使用它时它没有?

【问题讨论】:

  • “在数学中,如果你有 f g t w u (x),你首先要评估 u(x),然后再做其他事情。”呃……不是吗?并列通常意味着数学中的乘法,而它在 Haskell 中是函数应用。你有更好的例子吗?
  • 我的意思是函数组合,键盘没有数学中使用的函数组合符号,虽然我同意我可以用点来暗示组合。

标签: function haskell operator-keyword infix-notation operator-precedence


【解决方案1】:

take 50 iterate (* 2) 1 在带有括号调用的语言中与take (50, iterate, * 2, 1) 是等价的,但你想要的是take (50, iterate(* 2, 1))$ 完全符合“在此处打开括号并尽可能关闭它”的目的。它就像调用一个函数,但右关联而不是左关联。您可以使用take 50 (iterate (*2) 1) 在不使用$ 运算符的情况下编写它。

【讨论】:

  • $ 与括号无关。任何其他中缀运算符在这里都会有相同的“分组”效果(因为没有其他中缀运算符涉及,所以优先级无关紧要),即take 50 + iterate (* 2) 1 也会强制take 50iterate (* 2) 1 成为两个独立的表达式,因此(* 2)1 被视为iterate 的参数,而不是take 的更多参数。显然这将是一个类型错误,但这里区分 $+ 的不是“分组”,而是“将左参数应用于右参数”的含义。
【解决方案2】:

f g t w u (x) 也不是数学中的函数应用序列。但是,在数学中,如果我们将. 作为函数组合运算符,那么(f . g . t . w . u) (x) 首先应用u 函数,然后是w 函数,依此类推。这在 Haskell 中也是一样的:(f . g . t . w . u) x

在您的第一个类型错误中,它试图将[a0] 类型与(a1 -> a1) -> a1 -> [a1] 类型统一,而不是与[a1] 类型统一。这不可能统一,所以会报错。

并列是 Haskell 中的函数 应用程序,而不是函数组合(顺便说一下,在数学中,并列通常是乘法)。这意味着像f x y z 这样的表达式(本质上)意味着“将函数f 应用于参数xyz”。这相当于((f x) y) z。 Haskell 中的每个函数都只接受一个参数。这意味着像f x y z = x + y * z 这样的东西实际上与f = \x -> \y -> \z -> x + y * z 相同。函数应用是左关联的,因此f x y z 与编写((f x) y) z 相同,即将f 应用于x,首先获取结果函数并将其应用于y。最后,将该函数应用于z

这意味着您的原始表达式被解释为(再次转换为标准数学符号):take(50, iterate, (* 2), 1)

【讨论】:

  • f x y z等同于f(x,y,z)。它相当于((f x) y) z。 Haskell 中的所有函数都接受一个参数。
  • @ReinHenrichs 我知道,但我觉得此时添加的内容无关紧要。这就是我说“基本上”的原因。
  • 在回答有关函数应用如何工作的问题时,正确解释函数应用当然是相关的!
  • @ReinHenrichs 我只是在创建一个更简单的标准数学符号模拟,所以事情不会变得太混乱。在我看来,先用这种方式理解事物然后了解它真正是如何工作的(无论如何,很多东西都是这样学习的。当你学习虚数时,你不会学习虚数)了解平方根。)
  • @ReinHenrichs 我为此添加了一条注释。如果您觉得应该进一步扩展,请告诉我。另外,我认为它甚至没有必然错误,这取决于我们理解该数学符号的含义。也许它可以是部分应用。
【解决方案3】:

函数应用是左关联的,所以

f g a b

((f g) a) b

所以你有

(((take 50) iterate) (*2)) 1

这是不正确的,因为它将take 50 :: [a] -> [a] 应用于具有(a -> a) -> a -> [a] 类型的iterate,显然不正确。

【讨论】:

  • @user3558296 如果我已经回答了您的问题,请随时接受我的回答并带有复选标记:)
【解决方案4】:

函数应用程序(用“并置”表示,将函数及其参数彼此相邻)必须以某种方式进行解析,并且它需要是左关联或右关联才能被明确地解析。1

函数应用其实是左关联的,所以你写的等价于

((((take 50) iterate) (* 2)) 1)

如果它是右结合的,你会有

(take (50 (iterate ((* 2) 1))))

这也不是您想要的,而且作为默认选择更糟糕:很少有程序会让右关联运算符感觉更自然。

由于没有统一的解析规则可以生成您想要的程序,因此程序员有必要通过$ 或一些括号的形式给出提示,告诉haskell 您的实际意思。

至于为什么$有帮助:它被定义为解析时优先级很低,所以写

take 50 $ iterate (* 2) 1

解析为

(take 50) $ ((iterate (* 2)) 1))

这正是你想要的。

1 明确的解析对于拥有可理解的程序来说是一个非常理想的属性,而让类型检查器决定如何解析事物(正如您所建议的那样)将是一团糟。

【讨论】:

  • 谢谢,解释得很好。
【解决方案5】:

(我会给你一个更详细的答案,涵盖一些我认为其他答案未被发现的角落,至少没有明确)。

在 Haskell 中,括号表示函数调用。他们只是为了分组。所以u (x)u x 完全相同,在 Haskell 中。

您首先显示的类型错误是由 Haskell 尝试使用 iterate 作为 take 的第二个参数生成的:

take    :: Int -> [a0]                     -> [a0]
take       50     iterate                  :: t 
iterate ::        (a1 -> a1) -> a1 -> [a1]
                  ------------------------
                  [](...) and (->)(...) do not match

它不会尝试“在解析 take 50 函数后解析 iterate 函数”。它还没有看你长长的表情。它只是试图使用iterate 。这恰好是一个功能。

函数只是 Haskell 中的值,这就是为什么没有特殊的语法来调用它们,就像在各种 foo(args){exps;}; 语言中一样。

编译器试图为你纠正你的表达式太危险了:太多的错别字或错误会突然得到意想不到的含义。最多这可能是一些假设的交互式开发环境的一个特性。

当您编写take 50 $ iterate (*2) 1 时,第二个表达式的类型(在$ 运算符之后)是

iterate ::        (a1 -> a1) -> a1 -> [a1]
iterate           (*2)          1  :: [Int]  -- it's actually (Num a => [a]) 
                                             -- but that's besides the point

list-of-numbers 类型 (:: Num a => [a]) 是与调用 take 中的第二个参数匹配的,不是裸非应用类型iterate自己

take    :: Int ->               [a0]       -> [a0]
take       50     (iterate (*2) 1  )       :: t 
iterate (*2) 1    :: (Num a) => [a ]
                  ------------------
                  a0 ~ (Num a)=> a            t ~ (Num a) => [a]

所以它“typechecks”,即它可以工作。事实上,在 GHCi 中我们得到

Prelude> :t 取 50 (迭代 (*2) 1 )
取 50 (迭代 (*2) 1 ) :: (Num a) => [a]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-21
    • 2020-03-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多