Type of anonymous functions in Haskell
我在Haskell中键入匿名函数时遇到问题。 例如,当我们有:
GHCI中检查的类型是Num t1 => (t1 -> t2) -> t2,但我确定它是相反的。
是Num a => a -> a(我知道我们需要假设a是一个Integer,因为(*)的类型是Int-> Int-> Int(没有类型类)。
另一个例子是
据我所检查的是……(a-> b)-> b
但是我完全担心键入匿名函数。 了解这一点的魔力是什么? 也许可以将此函数重写为"正常"函数以清楚地看到类型?
我的问题是:我们如何获得这些类型? 他们来自哪里以及如何评估?
-
除了泛泛的"我不理解类型推断如何工作"之外,我无法理解这里存在的问题。你可以说得更详细点吗?
-
@chi问题基本上就是这些示例,它们是对它们的类??型的评估,因此可以计算出给定的结果
-
@ heisenberg7584您已在此处输入了类型。您在理解为什么这些类型正确时遇到什么具体问题?请编辑您的问题以添加此信息。
-
@AJFarmar编辑完成
-
可能重复:您不了解Hindley-Milner的哪一部分?如果不是重复的话,说说为什么。
-
在这里,关于匿名函数没有什么特别的。 func x = x 5与第一个函数完全相同,因此具有相同的类型。
-
并且该函数接受其参数并将其应用于5,因此该参数必须是接受数字的函数,然后整个函数返回该函数参数的返回类型。这是完全合乎逻辑的,而且正是类型签名告诉您的内容-因此,请进一步说明您要努力理解的那一部分。
>>我们如何获得这些类型的?
这些类型是Haskell系统可以从其唯一的东西(即用户给出的定义,例如" x 5")中得出的。
重要的不是该函数是匿名的。这是因为该函数未显式键入,因此Haskell系统必须从表达式中"猜测"该类型。
1 2 3 4 5 6 7 8
| 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 > |
因此,匿名和命名版本的类型完全相同。
当然,您可以更具体:
Haskell不会强迫您显式键入所有内容。
它接受所有显式键入信息(例如对于f4),以及由定义和对库函数的调用所产生的隐式键入信息,作为键入约束。
如果可以明确解决这些限制,那就好了;如上文chi所提到的,这称为类型推断。否则,如果键入约束是矛盾的或不明确的,则执行将中止。
-
Okey,它很有帮助...但是,如果我们有一个类似的表达式呢? \x -> f x y。据我了解,这与func x = x y相同吗?当此处y甚至都不是参数时,我们如何键入此值...
-
@ heisenberg7584正确,在您定义f和y之前,系统拒绝为 x-> f x 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-> f x y这样的表达式,则类型结果为A-> B,其中A是x的类型,而B是整个表达式f x y的类型
-
@ heisenberg7584如果在ghci下,您要求输入":t 4",然后要求":t(+)",您将看到4没有类型,只是类型约束为Num a; +号具有" Num a => a-> a-> a"的含义,这意味着+会强制其两个操作数及其结果具有相同的(数字)类型。因此,在x + 4之类的表达式中,+号的种类将类型约束4 Num扩展到另一个操作数x。因此,如果不使用某些显式转换运算符,就不能添加Int和Double。" x y-> x + y"具有与+相同的类型约束,因为它与+本质上是相同的。希望能有所帮助。
以下是三种等效的编写第一个函数的方法:
1 2 3 4 5 6 7 8
| 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
我认为您正在以不必要的方式使其复杂化。基本上,当您在Haskell中具有匿名函数并想要查找其类型时,您需要查找两种类型:首先是->符号之前的一种,然后是箭头->右侧的整个表达式的类型。根据您的示例:
我们需要找到x的类型,我们知道它是一个函数,该函数具有一个属于类型类Num的某种类型的参数,并返回一些未知类型-假设t:
因此x函数返回的类型为t:
这是我们的答案:
1
| \x -> x 5 :: Num a => (a -> t ) -> t |
最后一种情况相同:
f还是某个函数,但是这次我们不知道其参数的类型
所以a是x的类型,右边的整个表达式返回b
再一次-在这里,我们在->的左侧和右侧都有一种表达式类型。
1
| \f -> f x :: (a -> b) -> b |
-
...除了\f -> f x没有类型。
-
它有。我们假设x是α的类型。 x是函数f中的一个参数,因此f ::α->β。 β是函数f的结果类型。总结一下我们得到( f-> f x)::(α->β)->β