Haskell编程:深入高阶函数与特性
1. 前期探索与任务
在深入学习Haskell之前,有一些前期的探索任务:
-
查找资源
:
- 找到Haskell的维基页面。
- 加入支持你所选编译器的Haskell在线群组。
-
实践任务
:
- 尝试找出多种方式来编写
allEven
函数。
- 编写一个函数,该函数接受一个列表并返回其反转后的列表。
- 编写一个函数,生成包含黑色、白色、蓝色、黄色和红色中任意两种颜色的所有可能组合的二元组,注意避免重复组合,如只包含
(black, blue)
和
(blue, black)
中的一个。
- 使用列表推导式构建一个儿童乘法表,该表是一个三元组列表,前两个元素是1 - 12的整数,第三个元素是前两个元素的乘积。
- 用Haskell解决地图着色问题。
2. Haskell的强大之处
Haskell的强大之处在于其可预测性和逻辑的简单性。许多大学在程序推理的背景下教授Haskell,因为它比命令式语言更容易创建正确性证明。接下来我们将深入探讨一些实用概念,以提高编程的可预测性,包括高阶函数、部分应用函数、柯里化和惰性计算。
3. 高阶函数
几乎每一种语言都涉及高阶编程的概念,而Haskell对其依赖程度极高。我们先从匿名函数开始。
-
匿名函数
:Haskell中匿名函数的语法非常简单,形式为
(\param1 .. paramn -> function_body)
。例如:
Prelude> (\x -> x) "Logical."
"Logical."
Prelude> (\x -> x ++ " captain.") "Logical,"
"Logical, captain."
单独使用匿名函数时,其作用有限,但与其他函数结合使用时,会变得非常强大。
-
map和where
:
map
函数可将匿名函数应用于列表中的每个元素,并收集结果。例如:
map (\x -> x * x) [1, 2, 3]
为了使代码更易于理解,我们可以将匿名函数封装为局部作用域的函数:
module Main where
squareAll list = map square list
where square x = x * x
运行结果如下:
*Main> :load map.hs
[1 of 1] Compiling Main
( map.hs, interpreted )
Ok, modules loaded: Main.
*Main> squareAll [1, 2, 3]
[1,4,9]
此外,还可以使用部分函数与
map
结合,如:
Prelude> map (+ 1) [1, 2, 3]
[2,3,4]
这里的
(+ 1)
是一个部分应用函数,它将
+
函数的一个参数固定,返回一个单参数函数。
-
filter、foldl和foldr
:
filter
函数用于对列表中的元素进行筛选,例如:
Prelude> odd 5
True
Prelude> filter odd [1, 2, 3, 4, 5]
[1,3,5]
foldl
和
foldr
函数用于对列表进行折叠操作,例如:
Prelude> foldl (\x carryOver -> carryOver + x) 0 [1 .. 10]
55
Prelude> foldl1 (+) [1 .. 3]
6
foldl1
是
foldl
的一种特殊形式,使用
+
运算符作为纯函数对列表元素进行累加。同样,也可以使用
foldr1
进行从右到左的折叠操作。
4. 部分应用函数和柯里化
在Haskell中,每个函数实际上只有一个参数。例如,定义一个乘法函数:
Prelude> let prod x y = x * y
Prelude> prod 3 4
12
Prelude> :t prod
prod :: (Num a) => a -> a -> a
这里的
Num a =>
表示
a
是一个数值类型。Haskell通过部分应用将多参数函数拆分为多个单参数函数。例如:
Prelude> let double = prod 2
Prelude> let triple = prod 3
Prelude> double 3
6
Prelude> triple 4
12
当调用
prod 2 4
时,实际上是先应用
prod 2
得到
(\y -> 2 * y)
,再将其应用于
4
,这个过程称为柯里化。几乎所有多参数函数在Haskell中都会被柯里化,这使得代码更加灵活和简洁。
5. 惰性求值
Haskell广泛使用惰性求值,允许定义返回无限列表的函数。例如,定义一个生成无限范围的函数:
module Main where
myRange start step = start:(myRange (start + step) step)
运行示例:
*Main> take 10 (myRange 10 1)
[10,11,12,13,14,15,16,17,18,19]
*Main> take 5 (myRange 0 5)
[0,5,10,15,20]
使用惰性求值可以高效地定义递归函数,如斐波那契数列:
module Main where
lazyFib x y = x:(lazyFib y (x + y))
fib = lazyFib 1 1
fibNth x = head (drop (x - 1) (take (x) fib))
运行示例:
*Main> take 5 (lazyFib 0 1)
[1,1,2,3,5]
*Main> take 5 (fib)
[1,1,2,3,5]
*Main> take 5 (drop 20 (lazyFib 0 1))
[10946,17711,28657,46368,75025]
*Main> fibNth 3
2
*Main> fibNth 6
8
惰性求值还可以与高阶函数结合,创造出意想不到的效果。例如,将两个斐波那契数列相加:
*Main> take 5 (zipWith (+) fib (drop 1 fib))
[2,3,5,8,13]
或者对无限范围进行加倍操作:
*Main> take 5 (map (*2) [1 ..])
[2,4,6,8,10]
还可以使用函数组合和部分应用函数与惰性序列轻松组合:
*Main> take 5 (map ((* 2) . (* 5)) fib)
[10,10,20,30,50]
6. 与Simon Peyton-Jones的访谈
Simon Peyton-Jones是Haskell编译器的主要设计者,他分享了关于Haskell的一些看法:
-
Haskell的创建
:Haskell是一个成功的委员会语言,由大约二十名国际研究人员共同设计。其对纯度和无副作用的坚持使其在设计后的二十年才逐渐流行起来,未来主流语言可能会有更强的副作用控制机制。
-
喜欢的特性
:除了纯度,Haskell的类型系统是其最独特和有趣的特性之一。静态类型是目前最广泛使用的程序验证技术,Haskell的类型系统从一开始就具有很高的表达性,并且一直在探索新的类型系统概念。
-
待改进之处
:希望有更好的记录系统和模块系统,目前的记录系统过于简单,模块系统缺乏正式的接口导入和导出机制。
-
有趣的应用
:Haskell是一种通用编程语言,虽然没有单一的“杀手级应用”,但人们可以用它以优雅和独特的方式解决问题,如功能反应式动画、解析器和漂亮打印组合器库以及金融衍生品描述库等。
7. 第二天学习总结
-
高阶函数
:学习了
map、fold等列表库函数,以及zip和zipWith等其他函数。 - 部分应用函数和柯里化 :学会将多参数函数转换为单参数函数,理解了Haskell中所有函数都是柯里化的。
- 函数组合 :掌握了使用一个函数的返回值作为另一个函数的输入的方法。
- 惰性求值 :能够定义返回无限列表的函数,并在需要时进行处理。
8. 第二天自我学习任务
-
查找
:
- 查找可用于列表、字符串或元组的函数。
- 找到一种对列表进行排序的方法。
-
实践
:
- 编写一个排序函数,接受一个列表并返回排序后的列表。
- 编写一个排序函数,接受一个列表和一个比较函数,并返回排序后的列表。
-
编写一个Haskell函数,将形如
$2,345,678.99的字符串转换为数字,该字符串可能包含前导零。 -
编写一个函数,接受一个参数
x,返回一个从x开始,每隔三个数的惰性序列;再编写一个函数,接受一个参数y,返回一个从y开始,每隔五个数的惰性序列;最后将这两个函数组合,返回一个从x + y开始,每隔八个数的序列。 -
使用部分应用函数定义一个返回数字一半的函数和一个在字符串末尾添加
\n的函数。
- 挑战任务 :编写一个函数,计算两个整数的最大公约数。
以下是一个简单的mermaid流程图,展示了Haskell中函数调用的柯里化过程:
graph LR
A[prod x y] --> B[prod x]
B --> C[\y -> x * y\]
C --> D[应用于y]
D --> E[x * y]
通过这些学习和实践,你将对Haskell有更深入的理解,并能够解决一些复杂的编程问题。但要处理I/O、状态和错误处理等问题,还需要进一步学习高级理论,如第三天将学习的单子(Monads)。
Haskell编程:深入高阶函数与特性
9. 自我学习任务解析
在第二天的自我学习任务中,我们需要完成一系列查找和实践任务,下面对这些任务进行详细解析。
9.1 查找资源
-
可用于列表、字符串或元组的函数
:Haskell的标准库提供了丰富的函数,如
map、filter、foldl、foldr等可用于列表操作;length、reverse等可用于字符串(字符串在Haskell中本质上是字符列表);fst、snd可用于元组操作。 -
列表排序方法
:可以使用
sort函数对列表进行排序,需要导入Data.List模块。示例代码如下:
import Data.List
main = do
let myList = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print (sort myList)
9.2 实践任务
-
编写排序函数
-
简单排序函数
:可以使用
sort函数实现,代码如下:
-
简单排序函数
:可以使用
import Data.List
simpleSort :: Ord a => [a] -> [a]
simpleSort = sort
- **带比较函数的排序函数**:使用`sortBy`函数,示例代码如下:
import Data.List
compareSort :: (a -> a -> Ordering) -> [a] -> [a]
compareSort cmp = sortBy cmp
-
字符串转数字函数
:需要去除字符串中的
$和逗号,然后使用read函数将其转换为数字。示例代码如下:
import Data.Char
stringToNumber :: String -> Double
stringToNumber str = read (filter (\c -> c /= '$' && c /= ',') str) :: Double
-
惰性序列函数
- 每隔三个数的惰性序列 :
everyThird :: Int -> [Int]
everyThird x = [x, x + 3 ..]
- **每隔五个数的惰性序列**:
everyFifth :: Int -> [Int]
everyFifth y = [y, y + 5 ..]
- **组合函数**:
everyEighth :: Int -> Int -> [Int]
everyEighth x y = zipWith (+) (everyThird x) (everyFifth y)
-
部分应用函数
- 返回数字一半的函数 :
half :: Fractional a => a -> a
half = (/ 2)
- **在字符串末尾添加`\n`的函数**:
addNewLine :: String -> String
addNewLine = (++ "\n")
9.3 挑战任务
编写一个计算两个整数最大公约数的函数,可以使用欧几里得算法。示例代码如下:
gcd' :: Integral a => a -> a -> a
gcd' a 0 = abs a
gcd' a b = gcd' b (a `mod` b)
10. 总结与展望
通过第二天的学习,我们深入了解了Haskell的高阶函数、部分应用函数、柯里化和惰性求值等特性。这些特性使得Haskell在处理复杂问题时具有很高的灵活性和简洁性。
在自我学习任务中,我们进一步巩固了所学知识,通过编写各种函数,提高了对Haskell的编程能力。然而,目前我们还不能处理一些常见的编程问题,如I/O、状态管理和错误处理,这些问题需要我们在第三天深入学习单子(Monads)的概念。
以下是一个表格,总结了第二天学习的主要内容:
| 学习内容 | 描述 |
| ---- | ---- |
| 高阶函数 |
map
、
fold
等列表库函数,以及
zip
和
zipWith
等函数的使用 |
| 部分应用函数和柯里化 | 将多参数函数转换为单参数函数,理解函数的柯里化过程 |
| 函数组合 | 使用一个函数的返回值作为另一个函数的输入 |
| 惰性求值 | 定义返回无限列表的函数,并在需要时进行处理 |
下面是一个mermaid流程图,展示了从学习Haskell特性到解决实际问题的过程:
graph LR
A[学习高阶函数] --> B[学习部分应用函数和柯里化]
B --> C[学习函数组合]
C --> D[学习惰性求值]
D --> E[完成自我学习任务]
E --> F[解决实际编程问题]
通过不断学习和实践,我们将逐步掌握Haskell的高级特性,能够应对更复杂的编程挑战。期待第三天对单子的学习,进一步拓展我们的编程能力。
超级会员免费看
3

被折叠的 条评论
为什么被折叠?



