【问题标题】:Type of anonymous functions in HaskellHaskell 中的匿名函数类型
【发布时间】:2019-10-08 00:44:57
【问题描述】:

我在 Haskell 中输入匿名函数时遇到问题。例如,当我们有:

\x -> x 5

在 GHCI 中检查的类型是 Num t1 => (t1 -> t2) -> t2,而我确定它是相反的。类似的类型

\x -> a * x

是Num a => a -> a(我知道我们需要假设 a 是一个整数,因为 (*) 的类型是 Int -> Int -> Int(没有类型类)。
另一个例子是

\f -> f x

据我检查,就像 (a -> b) -> b

但我完全关心输入匿名函数。有什么魔力可以理解这个?也许一种方法可以将此函数重写为“正常”函数以清楚地查看类型?

所以我的问题是:我们如何获得这些类型?它们来自哪里以及如何评估?

【问题讨论】:

  • 我不明白这里的问题是什么,除了笼统的“我不明白类型推断是如何工作的”,这太宽泛了。你能说得更具体点吗?
  • @chi 一个问题基本上是给出了这些例子,它们对它们的类​​型进行评估,因此将计算给定的结果
  • @heisenberg7584 你已经在那儿给出了类型。在理解这些类型为何正确时,您遇到了什么具体问题?请编辑您的问题以添加此信息。
  • 可能重复:What part of Hindley-Milner do you not understand?。如果不是重复,请谈谈原因。
  • 这里的函数是匿名的并没有什么特别之处。 func x = x 5 与您的第一个函数完全相同,因此具有相同的类型。

标签: haskell


【解决方案1】:

以下是编写第一个函数的三种等效方法:

f :: Num t1 => (t1 -> t2) -> t2
f = \x -> x 5

g :: Num t1 => (t1 -> t2) -> t2
g x = x 5

h :: Num t1 => (t1 -> t2) -> t2
h = ($ 5)

请注意,无论您在其定义中使用哪种语法,此函数都是二阶函数。这意味着它的参数x 本身就是一个函数。也就是说,x 必须具有x :: Constraint1 t1, Constraint2 t2 => t1 -> t2 形式的类型。更具体地说,x 必须接受 5 作为它的第一个(也可能是唯一的)参数。但这是其论点的唯一内在限制。 5 :: Num t1 => t1,所以x :: Num t1, Constraint2 t2 => t1 -> t2。 x的返回值没有内在的约束,所以最通用(允许)的类型是x :: Num t1 => t1 -> t2。

这样就为我们提供了函数参数的类型:\x -> x 5 :: Num t1 => (t1 -> t2) -> ?。但是返回类型呢?好吧,您的函数只是将 x 应用于 5 并计算(返回)结果,因此您的函数返回与 x 相同的类型。假设你的函数可以接受任何可能的函数x,那么它的类型就是\x -> x 5 :: Num t1 => (t1 -> t2) -> t2。

还请注意,您可以为参数和类型变量使用任何您想要的小写名称。因此你也可以写\function -> function 5 :: Num five => (five -> result) -> result。

【讨论】:

  • 您还可以选择更严格的类型。例如,您可以将函数的更专业版本定义为:\function -> function 5 :: (Float -> String) -> String
【解决方案2】:

>> 我们如何获得这些类型?

这些类型是 Haskell 系统可以从它唯一拥有的东西中推断出来的,即用户给出的定义,例如“x 5”。

重要的不是函数是匿名的。就是函数没有明确类型,所以 Haskell 系统必须从表达式中“猜测”类型。

Prelude> let a=4
Prelude> :t  \x -> a * x
\x -> a * x :: Num a => a -> a
Prelude> 
Prelude> let f1 x = a*x
Prelude> :t f1
f1 :: Num a => a -> a
Prelude> 

因此,匿名版本和命名版本的类型完全相同。

当然,你可以更具体:

Prelude> let f4 :: Double -> Double ; f4 x = 4*x
Prelude> :t f4
f4 :: Double -> Double
Prelude>

Haskell 不会强制您显式输入所有内容。 它将所有显式类型信息(例如 f4)以及由您的定义和对库函数的调用产生的隐式类型信息作为类型约束。

如果约束可以明确解决,很好;这就是上面 chi 提到的 type inference。否则,如果类型约束相互矛盾或不明确,则执行中止。

【讨论】:

  • 好吧,这很有帮助...但是如果我们有这样的表达式; \x -> f x y。据我了解,这与 func x = x y 相同,对吗?当 y 甚至不是这里的参数时,我们如何输入这个...
  • @heisenberg7584 正确,系统拒绝为 \x -> f x y 提供类型,直到您定义了 f 和 y。从理论上讲,也许它可以说类似 (f:: t1->t2->t3) => t1-> t3 但不能被打扰。我猜比抱歉更安全。
  • 很抱歉,只是最后一个。所以我是对的,当我们有 \y -> x + y 时,类型是 Num a => a -> a 因为在表达式的左侧只有 y 所以作为 x 我们放置一些常数,例如 x = 3。所以我们删除一个部分来自 (+) 类型,因为 x 在这里是一个常数。如果我们的表达式是 \ xy -> x + y 我们这里没有常量,所以类型将是 Num a => a -> a -> a 而另一方面 g=x+y 的类型只是 Num a=>a 无论 x 和 y 只是假设它们数字对吗?
  • @heisenberg7584 基本上以一种非常简单的方式,如果你有像 \x -> fxy 这样的表达式,类型结果是 A -> B 其中 A 是 x 的类型,B 是整个表达式 fxy 的类型
  • @heisenberg7584 如果在 ghci 下,你要求 ":t 4" 然后 ":t (+)",你会看到 4 没有类型,只是 个数 ; + 符号具有“Num a => a->a->a”,这意味着 + 强制其两个操作数及其结果具有相同的(数字)类型。因此,在诸如 x+4 这样的表达式中,+ 号可以将 4 的类型约束 Num 扩展到另一个操作数 x。因此,如果不使用某些显式转换运算符,就无法添加 Int 和 Double。并且 "\x y ->x+y" 具有与 + 相同的类型约束,因为它本质上与 + 相同。希望对您有所帮助。
【解决方案3】:

我认为您正在以一种不必要的方式使其复杂化。基本上 当你在 Haskell 中有一个匿名函数并想找到它的类型时,你需要找到两种类型:第一种是 -> 符号之前的类型,然后是箭头右侧的整个表达式的类型 @ 987654322@。根据您的示例:

\x -> x 5

我们需要找到 x 的类型,我们知道它是一个函数,它有一个属于类型类 Num 的某种类型的参数,并返回一些未知类型 - 比如说 t:

x :: Num a => a -> t

所以x 函数返回t 类型的东西:

x 5 :: t

这是我们的答案:

\x -> x 5 :: Num a => (a -> t) -> t

最后一个情况也一样:

\f -> f x

f 又是一个函数,但这次我们不知道它的参数类型

f :: a -> b

所以a 是x 的类型,右侧的整个表达式返回b

f x :: b

再一次 - 就是这样,我们在 -> 的左侧和右侧都有一个表达式类型。

\f -> f x :: (a -> b) -> b

【讨论】:

  • ... 除了 \f -> f x 没有类型。
  • 它有。我们假设 x 是 α 的类型。 x 是函数 f 中的参数,因此 f :: α -> β。 β 是函数 f 的结果类型。所以总结起来我们得到 (\f -> f x) :: (α -> β ) -> β
猜你喜欢
  • 1970-01-01
  • 2019-04-04
  • 2012-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多