返回文章列表

文章

Get Programming with Haskell-17

组合两个事物意味着什么?这里有一些例子:你可以连接两个列表得到一个新的列表,你可以合并两个文档得到一个新的文档,你可以混合两种颜色得到一种新的颜色。在许多编程语言中,每种组合类型的方法都有其自己独特的操作符或函数。与几乎每种编程语言都提供将类型转换为字符串的标准方法非常相似,Haskell 提供了一种将相同类型的实例组合在一起的标准方法。

# 通过组合设计——semigroups和**monoid**
## 1. 组合性入门——组合函数 在深入研究组合类型之前,让我们先看看更基础的东西:组合函数。一个特殊的、只用一个句点(称为“组合”)表示的高阶函数接受两个函数作为参数。使用函数组合对于以可读的方式动态组合函数尤其有用。以下是一些可以轻松使用函数组合表达的函数示例。 ```haskell import Data.List

-- 使用函数组合创建函数的示例 myLast :: [a] -> a myLast = head . reverse myMin ::Ord a => [a] -> a myMin = head . sort

myMax ::Ord a => [a] -> a myMax = myLast . sort

myAll :: (a -> Bool) -> [a] -> Bool myAll testFunc = (foldr (&&) True) . (map testFunc)

这段代码主要演示了如何使用函数组合(Function Composition)来创建新的函数。函数组合是 Haskell 中一个非常强大的特性,它允许你将多个简单函数链接起来,形成一个更复杂的函数。在 Haskell 中,函数组合操作符是 `.`。表达式 `f . g` 表示一个新的函数,它等价于 `\x -> f (g x)`,即先应用函数 `g` 到输入,然后将结果作为参数传递给函数 `f`。
下面我们逐个分析代码中的函数:
1. import Data.List
	这行代码导入了 Data.List 模块,这个模块提供了许多操作列表的有用函数,比如 reverse 和 sort,这些函数在后面的定义中会用到。
2. `myLast :: [a] -> a`
	`myLast = head . reverse`
	- **类型签名 (****`:: [a] -> a`****)**: 这表示 `myLast` 是一个函数,它接受一个任意类型 `a` 的列表 `[a]` 作为输入,并返回该列表中的一个元素,类型也是 `a`。
	- **定义 (****`= head . reverse`****)**: 这使用了函数组合。
		- `reverse :: [a] -> [a]` 是一个来自 `Data.List` 的函数,它将一个列表反转。
		- `head :: [a] -> a` 是一个函数,它返回一个列表的第一个元素。
		- `head . reverse` 将 `reverse` 和 `head` 组合起来。这意味着对于一个输入的列表,首先对其进行反转,然后取反转后列表的第一个元素。反转后列表的第一个元素,正是原列表的最后一个元素。
	- **功能**: `myLast` 函数计算并返回输入列表的最后一个元素。
3. `myMin :: Ord a => [a] -> a`
	`myMin = head . sort`
	- **类型签名 (****`:: Ord a => [a] -> a`****)**: 这表示 `myMin` 是一个函数,它接受一个类型为 `a` 的列表作为输入,并返回一个类型为 `a` 的元素。`Ord a =>` 是一个类型约束,表示类型 `a` 必须是可排序的(即实现了 `Ord` 类型类),因为 `sort` 函数要求其输入的元素是可排序的。
	- **定义 (****`= head . sort`****)**: 这使用了函数组合。
		- `sort :: Ord a => [a] -> [a]` 是一个来自 `Data.List` 的函数,它将一个列表按升序排序。
		- `head :: [a] -> a` 返回列表的第一个元素。
		- `head . sort` 将 `sort` 和 `head` 组合起来。这意味着对于一个输入的列表,首先对其进行排序,然后取排序后列表的第一个元素。排序后列表的第一个元素,正是原列表中的最小元素。
	- **功能**: `myMin` 函数计算并返回输入列表中的最小元素。
4. `myMax :: Ord a => [a] -> a`
	`myMax = myLast . sort`
	- **类型签名 (****`:: Ord a => [a] -> a`****)**: 类似于 `myMin`,它接受一个可排序类型的列表并返回一个该类型的元素。
	- **定义 (****`= myLast . sort`****)**: 这使用了函数组合。
		- `sort :: Ord a => [a] -> [a]` 将列表按升序排序。
		- `myLast :: [a] -> a` (我们上面定义的)返回列表的最后一个元素。
		- `myLast . sort` 将 `sort` 和 `myLast` 组合起来。这意味着对于一个输入的列表,首先对其进行排序,然后取排序后列表的最后一个元素。排序后列表的最后一个元素,正是原列表中的最大元素。
	- **功能**: `myMax` 函数计算并返回输入列表中的最大元素。
5. `myAll :: (a -> Bool) -> [a] -> Bool`
	`myAll testFunc = (foldr (&&) True) . (map testFunc)`
	- **类型签名 (****`:: (a -> Bool) -> [a] -> Bool`****)**: 这表示 `myAll` 是一个函数,它接受两个参数:
		- 第一个参数是一个函数 `testFunc`,它的类型是 `a -> Bool`,即接受一个类型为 `a` 的元素,并返回一个布尔值 (`True` 或 `False`)。
		- 第二个参数是一个类型为 `a` 的列表 `[a]`。
		- `myAll` 返回一个布尔值 `Bool`。
	- **定义 (****`= (foldr (&&) True) . (map testFunc)`****)**: 这使用了函数组合。
		- `map testFunc :: [a] -> [Bool]` 是一个函数。`map` 函数将一个函数应用到列表的每一个元素上,并返回一个新的列表。在这里,`map testFunc` 将 `testFunc` 函数应用到输入列表 `[a]` 的每一个元素上,生成一个新的布尔值列表 `[Bool]`。这个布尔值列表表示原列表中每个元素通过 `testFunc` 测试的结果。
		- `foldr (&&) True :: [Bool] -> Bool` 是一个函数。`foldr` 是右 FOLD 操作。
			- `(&&)` 是一个二元布尔 AND 操作符。
			- `True` 是初始值(累加器的初始值)。
			- `foldr (&&) True` 将一个布尔值列表从右边开始折叠,使用 `(&&)` 操作符进行逻辑与运算,初始值为 `True`。例如,`foldr (&&) True [True, False, True]` 的计算过程大致是 `True && (False && (True && True))`,结果为 `False`。如果列表为空,结果就是初始值 `True`。
			- 这个 `foldr` 表达式的作用是检查一个布尔值列表中的所有元素是否都是 `True`。只有当列表中的所有布尔值都是 `True` 时,结果才是 `True`。
		- `(foldr (&&) True) . (map testFunc)` 将 `map testFunc` 和 `foldr (&&) True` 组合起来。这意味着对于一个输入的 `testFunc` 和列表 `[a]`,首先使用 `map testFunc` 将列表中的每个元素转换为一个布尔值,得到一个布尔值列表。然后,使用 `foldr (&&) True` 检查这个布尔值列表中的所有元素是否都为 `True`。
	- **功能**: `myAll testFunc list` 函数检查列表 `list` 中的所有元素是否都满足 `testFunc` 条件。只有当列表中的所有元素通过 `testFunc` 测试返回 `True` 时,`myAll` 才返回 `True`,否则返回 `False`。
**总结**
这段代码通过使用函数组合 (`.`) 优雅地定义了几个常见的列表操作函数。它展示了如何将简单的、现有的函数(如 `head`, `reverse`, `sort`, `map`, `foldr`)组合起来,以创建更复杂、更具表达力的函数。这种函数组合的方式是 Haskell 函数式编程风格的一个典型例子,它鼓励构建小型、可重用的函数,并通过组合它们来解决更大的问题。
## 2. 组合相似类型:半群(Semigroups)
为了进一步探索组合性,让我们看看一个非常简单的类型类,叫做 `Semigroup`。为此,你需要在你的文件顶部导入 `Data.Semigroup`。
`Semigroup` 类只有一个你需要的重要方法,即 `<>` 运算符。你可以将 `<>` 视为组合相同类型实例的运算符。你可以通过将 `<>` 定义为 `+`,轻松地为 `Integer` 实现 `Semigroup`。
这看起来可能过于简单,但思考这意味着什么很重要。以下是 `<>` 的类型签名:
```haskell
(<>) :: Semigroup a => a -> a -> a

这个类型签名表明 <> 是一个中缀运算符,它接受两个类型为 a 的值作为参数,并返回一个类型为 a 的值。约束 Semigroup a => 表明只有当类型 aSemigroup 类型类的实例时,才能使用 <> 运算符。 当你将 <> 定义为 Integer+ 时,你实际上是在告诉 Haskell 如何以一种通用的方式“组合”两个整数。虽然在这个简单的例子中,+ 运算符已经存在,但 Semigroup 类型类提供了一种抽象,使得我们可以用统一的方式处理不同类型的组合。 这里有一个使用 IntegerSemigroup 实例的示例:

import Data.Semigroup

instance Semigroup Integer where
    (<>) = (+)

main :: IO ()
main = do
    let num1 = 5
    let num2 = 10
    let combined = num1 <> num2
    print combined -- 输出 15

在这个例子中:

  1. 我们首先导入了 Data.Semigroup 模块。
  2. 然后,我们使用 instance Semigroup Integer where 声明 IntegerSemigroup 类型类的实例。
  3. where 块中,我们定义了 <> 运算符的行为:对于 Integer 类型,<> 被定义为标准的加法运算符 (+)
  4. main 函数中,我们创建了两个 Integer 类型的变量 num1num2
  5. 我们使用 <> 运算符组合了 num1num2,结果存储在 combined 变量中。由于我们为 Integer 定义 <>(+),所以 combined 的值是 5 + 10 = 15
  6. 最后,我们打印了 combined 的值,输出是 15。 这个简单的例子展示了如何为 Integer 类型定义 Semigroup 实例,以及如何使用 <> 运算符来组合两个整数。虽然对于整数来说,直接使用 + 更常见,但这个例子说明了 Semigroup 类型类提供了一个统一的接口来处理不同类型的组合。接下来你会看到如何为其他类型(如 String 和列表)定义 Semigroup 实例,并使用相同的 <> 运算符进行组合操作。

2.1 The Color Semigroup#