我正在做一些练习,我必须添加一个函数的类型并解释它的作用。我坚持这个:
phy = uncurry ($)
根据 GHCi,类型是phy :: (a -> b, a) -> b
. 我的haskell知识是基础的,所以我真的不知道它是做什么的。
我正在做一些练习,我必须添加一个函数的类型并解释它的作用。我坚持这个:
phy = uncurry ($)
根据 GHCi,类型是phy :: (a -> b, a) -> b
. 我的haskell知识是基础的,所以我真的不知道它是做什么的。
让我们系统地拼出类型部分。我们将从 and 的类型uncurry
开始($)
:
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
由于目标表达式($)
作为 的参数uncurry
,让我们排列它们的类型以反映这一点:
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
整个类型($)
与 的第一个参数类型对齐uncurry
,并且参数和结果类型($)
与的第一个参数类型uncurry
对齐,如图所示。这是对应关系:
uncurry's a <==> ($)'s a -> b
uncurry's b <==> ($)'s a
uncurry's c <==> ($)'s b
这有点令人困惑,因为一种类型中的a
andb
类型变量与另一种类型不同(就像x
inplusTwo x = x + 2
与x
in不同timesTwo x = x * 2
)。但是我们可以重写类型来帮助解释这一点。在像这样的简单 Haskell 类型签名中,任何时候你看到一个类型变量,你都可以用任何其他类型替换它的所有出现,也可以得到一个有效类型。如果您选择新的类型变量(原始类型中没有出现的类型变量),您将得到一个等效类型(可以转换回原始类型);如果您选择非新鲜类型,您将获得适用于更窄范围类型的原始版本的专用版本。
但无论如何,让我们将此应用于uncurry
::的类型
-- Substitute a ==> x, b ==> y, c ==> z:
uncurry :: (x -> y -> z) -> (x, y) -> z
让我们使用重写的类型重做“排队”:
uncurry :: (x -> y -> z) -> (x, y) -> z
($) :: (a -> b) -> a -> b
现在很明显x <==> a -> b
:y <==> a
和z <==> b
。现在,将 的类型变量替换uncurry
为 中的对应类型($)
,我们得到:
uncurry :: ((a -> b) -> a -> b) -> (a -> b, a) -> b
($) :: (a -> b) -> a -> b
最后:
uncurry ($) :: (a -> b, a) -> b
所以这就是你如何确定类型。它的作用如何?好吧,在这种情况下,最好的方法是查看类型并仔细考虑,弄清楚我们必须编写什么才能获得该类型的函数。让我们用这种方式重写它,让它更加神秘:
mystery :: (a -> b, a) -> b
mystery = ...
由于我们知道mystery
它是一个参数的函数,我们可以扩展这个定义以反映:
mystery x = ...
我们也知道它的参数是一对,所以我们可以扩展一点:
mystery (x, y) = ...
因为我们知道这x
是一个函数 and y :: a
,所以我喜欢用它f
来表示“函数”并将变量命名为与其类型相同的名称——这有助于我推理函数,所以让我们这样做:
mystery (f, a) = ...
现在,我们在右手边放什么?我们知道它必须是 type b
,但我们不知道 typeb
是什么(它实际上是调用者选择的任何内容,所以我们不知道)。所以我们必须以某种方式b
使用我们的 functionf :: a -> b
和 value a :: a
。啊哈!我们可以使用以下值调用函数:
mystery (f, a) = f a
我们没有看 就写了这个函数uncurry ($)
,但事实证明它做的事情和做的一样uncurry ($)
,我们可以证明它。让我们从 and 的定义uncurry
开始($)
:
uncurry f (a, b) = f a b
f $ a = f a
现在,用equals代替equals:
uncurry ($) (f, a) = ($) f a -- definition of uncurry, left to right
= f $ a -- Haskell syntax rule
= f a -- definition of ($), left to right
= mystery (f, a) -- definition of mystery, right to left
因此,攻击您在 Haskell 中不理解的类型的一种方法是尝试编写一些具有该类型的代码。Haskell 与其他语言的不同之处在于,这通常是比尝试阅读代码更好的策略。
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
uncurry ($) :: (a -> b, a) -> b
如果您检查类型uncurry
及其$
描述:
uncurry 将 curried 函数转换为对上的函数。
它所做的只是接受一个函数(a -> b -> c)
并返回一个将参数作为元组的函数。
phy
与 做同样的事情$
,但不是f $ x
or你($) f x
叫它 like phy (f, x)
。
其他两个答案都很好。我只是对此略有不同。
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
由于类型签名中的“->”与右侧相关联,因此我可以等效地编写这两个类型签名,如下所示:
uncurry :: (a -> b -> c) -> ((a, b) -> c)
($) :: (a -> b) -> (a -> b)
uncurry
接受两个输入的任意函数并将其更改为一个参数的函数,其中该参数是原始两个参数的元组。
($)
接受一个简单的单参数函数并将其变成......它自己。它的唯一作用是语法。 f $
相当于f
。
(确保您了解高阶函数和柯里化,阅读有关高阶函数的 Learn You a Haskell章节,然后阅读.(点)和 $(美元符号)与函数组合 (.) 和函数应用 ($)之间的区别成语)
($)
只是一个函数应用,f $ x
相当于f x
. 但这很好,因为我们可以使用显式函数应用程序,例如:
map ($2) $ map ($3) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]
这相当于:
map (($2) . ($3)) [(+), (-), (*), (**)] -- returns [5.0,1.0,6.0,9.0]
检查($)
:的类型($) :: (a -> b) -> a -> b
。你知道类型声明是右关联的,因此类型($)
也可以写成(a -> b) -> (a -> b)
. 等一下,那是什么?接收一元函数并返回相同类型的一元函数的函数?这看起来像一个特定版本的标识函数id :: a -> a
。好的,首先是一些类型:
($) :: (a -> b) -> a -> b
id :: a -> a
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry ($) :: (b -> c, b) -> c
uncurry id :: (b -> c, b) -> c
在编写 Haskell 代码时,请始终查看类型,它们甚至在您查看代码之前就为您提供了大量信息。那么,什么是($)
? 它是 2 个参数的函数。什么是uncurry
? 它也是 2 个参数的函数,第一个是 2 个参数的函数。所以uncurry ($)
应该进行类型检查,因为第一个参数uncurry
应该是 2 个参数的函数,即($)
。现在尝试猜测uncurry ($)
. 如果($)
's 的类型是(a -> b) -> a -> b
,则将其替换为(a -> b -> c)
: a
become (a -> b)
, b
become a
, c
become b
,因此uncurry ($)
返回一个 type 的函数((a -> b), a) -> b
。或者(b -> c, b) -> c
如上所述,这是同一件事。那么这种类型告诉我们什么?uncurry ($)
接受一个元组(function, value)
. 现在尝试仅从类型猜测它的作用。
现在,在回答之前,一个插曲。Haskell 是强类型的,如果类型声明有一个类型变量作为返回值类型,它禁止返回一个具体类型的值。所以如果你有一个带有 type 的函数a -> b
,你就不能 return String
。这是有道理的,因为如果您的函数的类型是a -> a
并且您总是返回String
,那么用户如何能够传递任何其他类型的值?您应该有一个类型String -> String
,或者有一个类型a -> a
并返回一个完全取决于输入变量的值。但是这种限制也意味着不可能为某些类型编写函数。type 没有函数a -> b
,因为没有人知道应该用什么具体类型来代替b
. 或者[a] -> a
,你知道这个函数不能是total,因为用户可以传递一个空列表,在这种情况下函数会返回什么?类型a
应该取决于列表内部的类型,但列表没有“内部”,它是空的,所以你不知道空列表中元素的类型是什么。这个限制只允许在特定类型下为可能的函数留出非常狭窄的肘部空间,这就是为什么你仅仅通过阅读类型就可以获得关于函数可能行为的如此多信息的原因。
uncurry ($)
返回 type 的东西c
,但它是一个类型变量,而不是具体的类型,所以它的值取决于也属于 type 的东西c
。我们从类型声明中看到元组中的函数返回 type 的值c
。同一个函数要求一个类型的值b
,它只能在同一个元组中找到。没有具体的类型也没有类型类,所以唯一uncurry ($)
能做的就是取snd
一个元组的 ,把它作为一个参数放在fst
一个元组的函数中,返回它返回的任何东西:
uncurry ($) ((+2), 2) -- 4
uncurry ($) (head, [1,2,3]) -- 1
uncurry ($) (map (+1), [1,2,3]) -- [2,3,4]
有一个可爱的程序djinn可以根据类型生成 Haskell 程序。试一试,看看我们对uncurry ($)
's 功能的类型猜测是正确的:
Djinn> f ? a -> a
f :: a -> a
f a = a
Djinn> f ? a -> b
-- f cannot be realized.
Djinn> f ? (b -> c, b) -> c
f :: (b -> c, b) -> c
f (a, b) = a b
这也表明,fst
并且snd
是唯一可以具有各自类型的函数:
Djinn> f ? (a, b) -> a
f :: (a, b) -> a
f (a, _) = a
Djinn> f ? (a, b) -> b
f :: (a, b) -> b
f (_, a) = a