如果你举个例子
map :: (a -> b) -> [a] -> [b]
这意味着 map 需要 2 个参数
- 从a型到b型的函数
- a 类型的列表
然后它返回
- b 列表
您已经可以看到这里的模式,但是当我们替换 'a' 和 'b' 时会更清楚
所以这个类型定义将是
map :: (String -> Int) -> [String] -> [Int]
所以现在它是一个函数,它接受一个字符串并返回一个整数,一个字符串列表并返回一个整数列表。
假设我们的函数接受一个字符串并返回,而 Int 是read。 read 使用您给它的字符串将其转换为不同的东西。因为我们这里把它放到了一个Int上下文中,它会把字符串转换成int
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,它的类型是Int -> Int(简化了!这里就不介绍Num类型类了)。所以(+) 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,你提供了一个折叠函数、一个起始值(累加器)和一个要折叠的列表。 fold1 有它自己的累加器,你不要给它自己的。
在实践中,您最好使用foldr,因为与fold 不同,它适用于大列表而不会因堆栈溢出而崩溃(tee,hee)。这条规则的例外是foldl'(注意“'”!)在Data.Map - 这是一个严格的折叠,也适用于大列表。
如果您想自己为函数指定类型(如果您编写了它们),请考虑以下示例:
double :: Int -> Int
double n = 2 * n
或者以一种haskellish方式
double :: Int -> Int
double = (*2)
这两个示例的第一行 (double :: Int -> Int) 就是您要查找的内容。这就是你可以强制编译器或解释器识别函数的方式——你可以省略,编译器/解释器会发现它们(有时甚至比人们最初想到的更好)