我正在做一些练习,我必须添加一个函数的类型并解释它的作用。我坚持这个:
phy = uncurry ($)
根据GHCi的类型是phy :: (a -> b, a) -> b
。我的haskell知识是基本的,所以我真的不知道它的作用。
答案 0 :(得分:13)
让我们系统地拼出类型部分。我们将从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
和b
类型变量与另一种类型中的变量不同(就像x
中的plusTwo x = x + 2
一样与x
中的timesTwo x = x * 2
不同。但我们可以重写这些类型以帮助解决这个问题。在像这样的简单Haskell类型签名中,只要您看到类型变量,就可以将其所有出现的内容替换为任何其他类型,也可以获得有效类型。如果您选择 fresh 类型变量(类型变量未出现在原始文件中的任何位置),您将获得等效类型(可以转换回原始类型) ;如果您选择非新鲜类型,则会获得原型的专业版本,该版本适用于较窄范围的类型。
但无论如何,我们将其应用于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
是一个函数而y :: a
,我喜欢使用f
来表示“函数”并将变量命名为与其类型相同 - 这有助于我推理关于功能,让我们这样做:
mystery (f, a) = ...
现在,我们在右手边放了什么?我们知道它必须是b
类型,但我们不知道b
是什么类型(它实际上是调用者选择的内容,所以我们不能知道)。因此,我们必须使用函数b
和值f :: a -> b
以某种方式创建a :: a
。啊哈!我们可以用值:
mystery (f, a) = f a
我们在没有查看uncurry ($)
的情况下编写了这个函数,但事实证明它与uncurry ($)
完全相同,我们可以证明这一点。让我们从uncurry
和($)
:
uncurry f (a, b) = f a b
f $ a = f a
现在,用等于替换等于:
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与其他语言的不同之处在于,这通常比尝试阅读代码更好。
答案 1 :(得分:10)
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
而不是($) f x
或phy (f, x)
。
答案 2 :(得分:5)
另外两个答案很好。我对它的看法略有不同。
uncurry :: (a -> b -> c) -> (a, b) -> c
($) :: (a -> b) -> a -> b
由于“ - &gt;”在类型签名关联到右边,我可以等效地写这两个类型的签名:
uncurry :: (a -> b -> c) -> ((a, b) -> c)
($) :: (a -> b) -> (a -> b)
uncurry
接受两个输入的任意函数,并将其更改为一个参数的函数,其中该参数是原始两个参数的元组。
($)
采用简单的单参数函数并将其转换为......本身。它的唯一作用是语法。 f $
相当于f
。
答案 3 :(得分:2)
(确保您了解更高阶的函数并进行讨论,阅读higher-order functions上的了解您的Haskell 一章,然后阅读difference between . (dot) and $ (dollar sign)和{{3} })功能
($)
只是一个功能应用,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
的1 st 参数应该是2个参数的函数,($)
是。{1}}。现在尝试猜测uncurry ($)
的类型。如果($)
的类型为(a -> b) -> a -> b
,请将其替换为(a -> b -> c)
:a
变为(a -> b)
,b
变为a
,{ {1}}变为c
,因此,b
会返回uncurry ($)
类型的函数。或者((a -> b), a) -> b
如上所述,这是一回事。那个类型告诉我们什么? (b -> c, b) -> c
接受元组uncurry ($)
。现在尝试从单独的类型中猜测它是做什么的。
现在,在答案之前,插曲。 Haskell是如此function composition (.) and function application ($) idioms,如果类型声明具有类型变量作为返回值类型,它禁止返回具体类型的值。因此,如果您的函数类型为(function, value)
,则无法返回a -> b
。这是有道理的,因为如果你的函数的类型是String
并且你总是返回a -> a
,那么用户如何能够传递任何其他类型的值?您应该具有类型String
或具有类型String -> String
并返回仅依赖于输入变量的值。但是这种限制也意味着不可能为某些类型编写函数。没有类型a -> a
的函数,因为没有人知道,应该使用什么具体类型而不是a -> b
。或者b
,你知道这个函数不能是strongly typed,因为用户可以传递一个空列表,在这种情况下函数会返回什么?类型[a] -> a
应该取决于列表中的类型,但列表没有“内部”,它是空的,所以你不知道空列表中的元素类型是什么。这种限制只允许非常狭窄的肘部空间用于特定类型下的可能功能,这就是为什么只需读取类型就可以获得有关函数可能行为的大量信息。
a
返回类型为uncurry ($)
的内容,但它是一个类型变量,而不是具体类型,因此它的值取决于类型为c
的内容。我们从类型声明中看到元组中的函数返回类型c
的值。并且相同的函数要求类型c
的值,它只能在同一个元组中找到。没有具体的类型,也没有类型类,所以b
唯一能做的就是取一个元组的uncurry ($)
,把它作为参数放在元组的snd
函数中,返回无论它返回什么:
fst
有一个可爱的程序total可以根据类型生成Haskell程序。与它一起玩,看看我们对uncurry ($) ((+2), 2) -- 4
uncurry ($) (head, [1,2,3]) -- 1
uncurry ($) (map (+1), [1,2,3]) -- [2,3,4]
的功能的类型猜测是正确的:
uncurry ($)
这也表明,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