如果你以...为例
map :: (a -> b) -> [a] -> [b]
这意味着map函数需要两个参数:
- 从类型a到类型b的函数
- 类型为a的列表
它返回:
- b类型的列表
你已经可以看出其中的模式,但是当我们用'a'和'b'替换时,它将更加清晰。
- a = 字符串(String)
- b = 整数(Int)
因此,这个类型定义将是:
map :: (String -> Int) -> [String] -> [Int]
现在它是一个接受字符串并返回整数以及接受字符串列表并返回整数列表的函数。
假设我们的接受字符串并返回整数的函数为read
。 read
使用您提供的字符串将其转换为不同的内容。因为我们在此处将其放入整数上下文中,所以它会将字符串转换为整数。
map read ["1", "2", "112", 333"]
这将导致
[1, 2, 112, 333]
因为
map
接受你的函数
read
并将其应用于列表的每个元素。你不必告诉
read
它的参数,比如
read "1"
或者
read n
,因为这些都由
map
代劳。
这就是所有内容了。一个函数的类型只表示它接受哪些类型的参数和返回什么类型的值。当然,还有柯里化,但你可能会在后面无意中或有意地学习到它!它基本上意味着一个函数不接受
多个 参数,而只接受
一个。比如你取函数
+
。如果你计算
1+2
,它会返回一个函数,该函数接受另一个数字,该数字将添加到
1
中,并且因为这里确实有另一个数字
2
,所以它将使用它。你也可以对其求值为
(1+)
,稍后再添加数字。当你没有中缀语法的加号时,它更清晰。它可以被写成
(+) 1 2
,因此首先这个语句变成
(+) 1
,它的类型(简化! 我不会在这里介绍Num类型类)为
Int -> Int
。因此,
(+) 1
(或者说
(1+)
)只是另一个你可以将值应用于其中的函数,在这一点上,结果将能够计算为3。
以下是它在实践中的样子:(如果这里的
(Num a) =>
让你困惑,那么请忽略它,这是你之后会更多了解的概念。如果你愿意,可以将此处的
a
类型替换为
Int
,并完全忽略
(Num a) =>
部分。)
(+) :: (Num a) => a -> a -> a
(+2) :: (Num a) => a -> a
(1+) :: (Num a) => a -> a
(1+2) :: (Num a) => a
Prelude> (+2) 5
7
Prelude> map (+3) [1,2,3]
[4,5,6]
针对你的第二个问题:你根本不需要定义推断类型。它们被称为“推断”,因为编译器/解释器会自动“推断”(计算)类型,而无需显式命名它们。
关于foldX
函数的区别:它们都执行相同的操作:将列表减少为单个值。这些函数之间的差异仅在于内部定义。foldl
从左侧折叠列表,而foldr
则从右侧进行折叠。
因此,你可以像这样使用所有这些函数来汇总一个列表...
foldl1 (+) [1,2,3] == 6
foldr (+) 0 [1,2,3] == 6
foldl (+) 0 [1,2,3] == 6
除了foldl1之外,你需要向fold提供一个函数、一个起始值(累加器)和一个要折叠的列表。而fold1
有它自己的累加器,你不需要提供你自己的。
在实践中,你应该使用foldr
,因为与fold
不同,它适用于大型列表,避免由于堆栈溢出而崩溃(哈哈)。这个规则的例外是Data.Map
中的foldl'
(注意“'”!)- 它是一个严格的折叠,也适用于大型列表。
如果你想给函数指定类型(如果你是写它们的人),可以考虑以下示例:
double :: Int -> Int
double n = 2 * n
或者用 Haskell 的方式
double :: Int -> Int
double = (*2)
两个例子的第一行 (double :: Int -> Int
) 是你要找的。这是如何强制编译器或解释器识别函数的方法 - 你可以省略它们,编译器/解释器会找出它们(有时甚至比人们最初想象的还要好)
>> :t foldr map foldr map :: [b] -> [b -> b] -> [b]
- jkramer