19、Haskell 入门:第 1 天学习指南

Haskell 入门:第 1 天学习指南

1. Haskell 简介

Haskell 对于许多函数式编程纯粹主义者来说,代表着纯粹和自由。它功能丰富且强大,但要掌握它也并非易事,就像你不能只浅尝辄止,而需要全身心投入到函数式编程的世界中。与 Scala、Erlang 和 Clojure 不同,Haskell 没有给命令式概念留下太多空间,是一门纯粹的函数式语言。

Haskell 是从底层开始构建的纯粹函数式语言,它融合了其他优秀函数式语言的思想,尤其强调惰性处理。它具有强大的静态类型系统,类型模型大多可以被推断,被广泛认为是函数式语言中最有效的类型系统之一,支持多态性和简洁的设计。此外,Haskell 还支持 Erlang 风格的模式匹配和守卫、Clojure 风格的惰性求值,以及来自 Clojure 和 Erlang 的列表推导式。

作为纯粹的函数式语言,Haskell 不产生副作用,而是函数可以返回一个副作用,后续再执行。在后续的学习中,我们会看到使用“单子(monads)”来保存状态的示例。

2. 第 1 天:逻辑基础

我们使用 GHC(Glasgow Haskell Compiler)6.12.1 版本开始学习,在控制台输入 ghci 启动:

GHCi, version 6.12.1: http://www.haskell.org/ghc/ :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading package ffi-1.0 ... linking ... done.

启动后就可以输入命令进行操作。

2.1 表达式和基本类型

我们先从基本类型开始学习,包括数字、字符数据和布尔值。

  • 数字 :和其他语言一样,我们可以输入一些简单的表达式:
Prelude> 4
4
Prelude> 4 + 1
5
Prelude> 4 + 1.0
5.0
Prelude> 4 + 2.0 * 5
14.0
Prelude> 4 * 5 + 1
21
Prelude> 4 * (5 + 1)
24

可以看到,运算顺序和预期一致,并且可以使用括号来分组运算。

  • 字符数据 :字符串用双引号表示,字符串拼接使用 ++ 而不是 +
Prelude> "hello"
"hello"
Prelude> "hello" + " world"
<interactive>:1:0:
No instance for (Num [Char])
arising from a use of `+' at <interactive>:1:0-17
Possible fix: add an instance declaration for (Num [Char])
In the expression: "hello" + " world"
In the definition of `it': it = "hello" + " world"
Prelude> "hello" ++ " world"
"hello world"

单个字符用单引号表示,字符串实际上是字符列表:

Prelude> 'a'
'a'
Prelude> ['a', 'b']
"ab"
  • 布尔值 :布尔值是另一种基本类型,相等和不相等表达式返回布尔值:
Prelude> (4 + 5) == 9
True
Prelude> (5 + 5) /= 10
False

在 Haskell 中, if 是一个函数,而不是控制结构,它返回一个值。注意,Haskell 是强类型语言, if 语句的条件必须是布尔类型:

Prelude> if (5 == 5) then "true"
<interactive>:1:23: parse error (possibly incorrect indentation)
Prelude> if (5 == 5) then "true" else "false"
"true"
Prelude> if 1 then "true" else "false"
<interactive>:1:3:
No instance for (Num Bool)
arising from the literal `1' at <interactive>:1:3
...
Prelude> "one" + 1
<interactive>:1:0:
No instance for (Num [Char])
arising from a use of `+' at <interactive>:1:0-8
...

我们可以使用 :t 查看 Haskell 的类型推断结果:

Prelude> :set +t
Prelude> 5
5
it :: Integer
Prelude> 5.0
5.0
it :: Double
Prelude> "hello"
"hello"
it :: [Char]
Prelude> (5 == (2 + 3))
True
it :: Bool
2.2 函数

函数是 Haskell 编程范式的核心,由于 Haskell 具有强静态类型,每个函数通常由可选的类型声明和实现两部分组成。

  • 定义基本函数 :在控制台中,我们可以使用 let 绑定变量和函数:
Prelude> let x = 10
Prelude> x
10
Prelude> let double x = x * 2
Prelude> double 2
4

在编写 Haskell 模块时,函数定义如下:

-- double.hs
module Main where
double x = x + x

加载模块并使用函数:

Prelude> :load double.hs
[1 of 1] Compiling Main
( double.hs, interpreted )
Ok, modules loaded: Main.
*Main> double 5
10

我们也可以为函数添加类型声明:

-- double_with_type.hs
module Main where
double :: Integer -> Integer
double x = x + x

加载并使用:

[1 of 1] Compiling Main
( double_with_type.hs, interpreted )
Ok, modules loaded: Main.
*Main> double 5
10
*Main> :t double
double :: Integer -> Integer

没有类型声明时,函数的类型会更通用:

*Main> :t double
double :: (Num a) => a -> a
  • 递归 :我们可以使用递归实现阶乘函数:
Prelude> let fact x = if x == 0 then 1 else fact (x - 1) * x
Prelude> fact 3
6

使用模式匹配可以让代码更简洁:

-- factorial.hs
module Main where
factorial :: Integer -> Integer
factorial 0 = 1
factorial x = x * factorial (x - 1)

也可以使用守卫来实现:

-- fact_with_guard.hs
module Main where
factorial :: Integer -> Integer
factorial x
  | x > 1 = x * factorial (x - 1)
  | otherwise = 1
  • 元组和列表 :我们以斐波那契数列为例,展示元组和列表的使用。

  • 简单斐波那契数列

-- fib.hs
module Main where
fib :: Integer -> Integer
fib 0 = 1
fib 1 = 1
fib x = fib (x - 1) + fib (x - 2)
  • 使用元组优化斐波那契数列
-- fib_tuple.hs
module Main where
fibTuple :: (Integer, Integer, Integer) -> (Integer, Integer, Integer)
fibTuple (x, y, 0) = (x, y, 0)
fibTuple (x, y, index) = fibTuple (y, x + y, index - 1)

fibResult :: (Integer, Integer, Integer) -> Integer
fibResult (x, y, z) = x

fib :: Integer -> Integer
fib x = fibResult (fibTuple (0, 1, x))

运行结果:

*Main> fib 100
354224848179261915075
*Main> fib 1000
43466557686937456435688527675040625802564660517371780
40248172908953655541794905189040387984007925516929592
25930803226347752096896232398733224711616429964409065
33187938298969649928516003704476137795166849228875
  • 使用元组和函数组合 :我们可以通过组合函数来实现一些功能,例如计算列表的第二个元素:
*Main> let second = head . tail
*Main> second [1, 2]
2
*Main> second [3, 4, 5]
4

另一种斐波那契数列的实现:

-- fib_pair.hs
module Main where
fibNextPair :: (Integer, Integer) -> (Integer, Integer)
fibNextPair (x, y) = (y, x + y)

fibNthPair :: Integer -> (Integer, Integer)
fibNthPair 1 = (1, 1)
fibNthPair n = fibNextPair (fibNthPair (n - 1))

fib :: Integer -> Integer
fib = fst . fibNthPair
  • 遍历列表 :我们可以使用模式匹配将列表拆分为头和尾:
let (h:t) = [1, 2, 3, 4]
*Main> h
1
*Main> t
[2,3,4]

定义列表的 size prod 函数:

-- lists.hs
module Main where
size [] = 0
size (h:t) = 1 + size t
prod [] = 1
prod (h:t) = h * prod t

使用 zip 函数组合列表:

*Main> zip "kirk" "spock"
[('k','s'),('i','p'),('r','o'),('k','c')]
Prelude> zip ["kirk", "spock"] ["enterprise", "reliant"]
[("kirk","enterprise"),("spock","reliant")]
2.3 生成列表

我们可以通过递归、范围和列表推导式来生成新的列表。

  • 递归 :使用 : 运算符组合头和尾来构建列表:
Prelude> let h:t = [1, 2, 3]
Prelude> h
1
Prelude> t
[2,3]
Prelude> 1:[2, 3]
[1,2,3]

定义返回列表中偶数的函数:

-- all_even.hs
module Main where
allEven :: [Integer] -> [Integer]
allEven [] = []
allEven (h:t) = if even h then h:allEven t else allEven t
  • 范围和组合 :Haskell 支持范围表示,默认增量为 1:
Prelude> [1..2]
[1,2]
Prelude> [1..4]
[1,2,3,4]
Prelude> [10..4]
[]
Prelude> [10, 8 .. 4]
[10,8,6,4]
Prelude> [10, 9.5 .. 4]
[10.0,9.5,9.0,8.5,8.0,7.5,7.0,6.5,6.0,5.5,5.0,4.5,4.0]
Prelude> take 5 [ 1 ..]
[1,2,3,4,5]
Prelude> take 5 [0, 2 ..]
[0,2,4,6,8]
  • 列表推导式 :列表推导式可以方便地构建和转换列表:
Prelude> [x * 2 | x <- [1, 2, 3]]
[2,4,6]
Prelude> [ (y, x) | (x, y) <- [(1, 2), (2, 3), (3, 1)]]
[(2,1),(3,2),(1,3)]
Prelude> [ (4 - x, y) | (x, y) <- [(1, 2), (2, 3), (3, 1)]]
[(3,2),(2,3),(1,1)]
Prelude> let crew = ["Kirk", "Spock", "McCoy"]
Prelude> [(a, b) | a <- crew, b <- crew]
[("Kirk","Kirk"),("Kirk","Spock"),("Kirk","McCoy"),
("Spock","Kirk"),("Spock","Spock"),("Spock","McCoy"),
("McCoy","Kirk"),("McCoy","Spock"),("McCoy","McCoy")]
Prelude> [(a, b) | a <- crew, b <- crew, a /= b]
[("Kirk","Spock"),("Kirk","McCoy"),("Spock","Kirk"),
("Spock","McCoy"),("McCoy","Kirk"),("McCoy","Spock")]
Prelude> [(a, b) | a <- crew, b <- crew, a < b]
[("Kirk","Spock"),("Kirk","McCoy"),("McCoy","Spock")]
3. 与 Philip Wadler 的访谈

我们采访了参与设计 Haskell 的 Philip Wadler,了解了 Haskell 的设计初衷、优点、改进方向以及有趣的应用案例。

  • 设计初衷 :在 20 世纪 80 年代末,有许多不同的团队在设计和实现函数式语言,大家意识到合作会更强大。最初的目标是让 Haskell 成为研究的基础、适合教学并能用于工业用途。

  • 优点 :Philip 喜欢使用列表推导式,认为它很方便,并且已经被其他语言采用。类型类提供了简单的泛型编程形式,只需添加一个关键字 derived 就可以获得比较值、转换值等功能。Haskell 还非常适合嵌入其他特定领域的编程语言,其惰性、lambda 表达式、单子和箭头符号、类型类、表达性强的类型系统和模板 Haskell 都支持以各种方式扩展语言。

  • 改进方向 :随着分布式变得越来越重要,需要关注在多台机器上运行的程序,发送值时可能希望是值本身(急切求值),而不是一个可以求值得到该值的程序。因此,在分布式环境中,默认采用急切求值,但在需要时可以方便地使用惰性求值会更好。

  • 有趣的应用案例 :Haskell 在自然语言处理、蛋白质折叠、金融、代码更新工具以及垃圾收集等领域都有应用。

4. 第 1 天学习总结

第 1 天我们学习了 Haskell 的基本表达式、简单数据类型、函数定义、递归、元组和列表的使用,以及列表的生成方法。通过这些学习,我们对 Haskell 的基本特性有了初步的了解。

5. 第 1 天自我学习任务

为了巩固所学知识,我们可以完成以下自我学习任务:
- 查找资料
- 查找 Haskell 维基百科。
- 找到支持你所使用编译器的 Haskell 在线社区。
- 实践任务
- 尝试用不同的方法实现 allEven 函数。
- 编写一个函数,将列表反转。
- 编写一个函数,生成包含黑色、白色、蓝色、黄色和红色中任意两种颜色的所有可能组合的二元组,且只包含 (black, blue) (blue, black) 中的一个。
- 编写一个列表推导式,生成一个乘法表,乘法表是一个三元组列表,前两个元素是 1 到 12 的整数,第三个元素是前两个元素的乘积。
- 使用 Haskell 解决地图着色问题。

通过这些任务,我们可以进一步熟悉 Haskell 的语法和特性,提高编程能力。

下面是一个简单的流程图,展示了我们第 1 天学习 Haskell 的主要步骤:

graph TD;
    A[启动 GHCi] --> B[学习基本表达式和类型];
    B --> C[学习函数定义];
    C --> D[学习递归和元组列表];
    D --> E[学习列表生成方法];
    E --> F[完成自我学习任务];

通过今天的学习,我们已经迈出了学习 Haskell 的第一步,后续我们将继续深入学习 Haskell 的高级特性,如参数化类型系统和单子。希望大家在学习过程中不断实践,加深对 Haskell 的理解。

Haskell 入门:第 1 天学习指南

6. 关键知识点总结

为了更好地回顾第 1 天所学内容,下面通过表格形式总结关键知识点:
| 知识点 | 描述 | 示例代码 |
| — | — | — |
| 基本类型 | 包括数字、字符数据、布尔值 | 数字: 4 + 1 ;字符数据: "hello" ++ " world" ;布尔值: (4 + 5) == 9 |
| 函数定义 | 由可选类型声明和实现组成 | double :: Integer -> Integer; double x = x + x |
| 递归 | 实现阶乘、斐波那契数列等 | factorial x = if x == 0 then 1 else x * factorial (x - 1) |
| 元组和列表 | 用于存储数据和优化算法 | 元组: fibTuple (x, y, index) = fibTuple (y, x + y, index - 1) ;列表: size (h:t) = 1 + size t |
| 列表生成 | 递归、范围、列表推导式 | 递归: allEven (h:t) = if even h then h:allEven t else allEven t ;范围: [1..4] ;列表推导式: [x * 2 | x <- [1, 2, 3]] |

7. 自我学习任务解析

下面对第 1 天的自我学习任务进行详细解析,帮助大家更好地完成任务。

7.1 用不同方法实现 allEven 函数

除了之前使用的递归方法,还可以使用列表推导式实现:

-- all_even_alt.hs
module Main where
allEven :: [Integer] -> [Integer]
allEven lst = [x | x <- lst, even x]
7.2 编写列表反转函数

可以使用递归和 foldl 函数实现列表反转:

-- reverse_list.hs
module Main where
-- 递归实现
reverseRecursive :: [a] -> [a]
reverseRecursive [] = []
reverseRecursive (x:xs) = reverseRecursive xs ++ [x]

-- foldl 实现
reverseFoldl :: [a] -> [a]
reverseFoldl = foldl (flip (:)) []
7.3 生成颜色组合二元组
-- color_combinations.hs
module Main where
colors = ["black", "white", "blue", "yellow", "red"]
colorCombinations :: [(String, String)]
colorCombinations = [(a, b) | a <- colors, b <- colors, a < b]
7.4 生成乘法表
-- multiplication_table.hs
module Main where
multiplicationTable :: [(Integer, Integer, Integer)]
multiplicationTable = [(x, y, x * y) | x <- [1..12], y <- [1..12]]
7.5 解决地图着色问题

地图着色问题可以通过回溯算法解决。以下是一个简单示例:

-- map_coloring.hs
module Main where
-- 定义地图区域和相邻关系
regions = ["A", "B", "C", "D"]
neighbors = [("A", "B"), ("A", "C"), ("B", "C"), ("B", "D"), ("C", "D")]
colors = ["red", "green", "blue"]

-- 检查分配是否合法
isValid :: [(String, String)] -> (String, String) -> Bool
isValid assignment (region, color) = all (\(r, c) -> r /= region || c /= color) [(r, c) | (r, c) <- assignment, (r, region) `elem` neighbors || (region, r) `elem` neighbors]

-- 回溯算法
colorMap :: [(String, String)] -> [String] -> [(String, String)]
colorMap assignment [] = assignment
colorMap assignment (r:rs) = head [colorMap ((r, c):assignment) rs | c <- colors, isValid assignment (r, c)]

main :: IO ()
main = print (colorMap [] regions)
8. 学习路径规划

接下来的学习可以按照以下步骤进行,形成一个清晰的学习路径:
1. 巩固基础 :复习第 1 天所学内容,完成自我学习任务,确保对基本概念和语法有扎实的掌握。
2. 深入学习高级特性 :学习参数化类型系统和单子等高级特性,理解 Haskell 的强大之处。
3. 实践项目 :选择一些小型项目进行实践,如命令行工具、简单的 Web 应用等,将所学知识应用到实际中。
4. 参与社区 :加入 Haskell 社区,与其他开发者交流经验,学习优秀的代码和设计模式。

下面是相应的流程图:

graph LR;
    A[巩固基础] --> B[深入学习高级特性];
    B --> C[实践项目];
    C --> D[参与社区];
9. 总结与展望

通过第 1 天的学习,我们对 Haskell 这门纯粹的函数式语言有了初步认识。从基本表达式、函数定义到递归、元组和列表的使用,再到列表生成方法,每个知识点都为我们打开了 Haskell 编程的一扇窗。

在后续的学习中,我们将面临更具挑战性的内容,如参数化类型系统和单子。这些高级特性虽然理解起来有一定难度,但它们是 Haskell 强大功能的核心所在。希望大家保持积极的学习态度,不断实践,逐步掌握 Haskell 的精髓,在函数式编程的世界中探索更多的可能性。

让我们继续努力,在 Haskell 的学习道路上不断前进!

复杂几何的多球近似MATLAB类及多球模型的比较 MATLAB类Approxi提供了一个框架,用于使用具有迭代缩放的聚集球体模型来近似解剖体积模型,以适应目标体积和模型比较。专为骨科、生物力学和计算几何应用而开发。 MATLAB class for multi-sphere approximation of complex geometries and comparison of multi-sphere models 主要特点: 球体模型生成 1.多球体模型生成:与Sihaeri的聚集球体算法的接口 2.音量缩放 基于体素的球体模型和参考几何体的交集。 迭代缩放球体模型以匹配目标体积。 3.模型比较:不同模型体素占用率的频率分析(多个评分指标) 4.几何分析:原始曲面模型和球体模型之间的顶点到最近邻距离映射(带颜色编码结果)。 如何使用: 1.代码结构:Approxi类可以集成到相应的主脚本中。代码的关键部分被提取到单独的函数中以供重用。 2.导入:将STL(或网格)导入MATLAB,并确保所需的函数,如DEM clusteredSphere(populateSpheres)和inpolyhedron,已添加到MATLAB路径中 3.生成多球体模型:使用DEM clusteredSphere方法从输入网格创建多球体模型 4.运行体积交点:计算多球体模型和参考几何体之间的基于体素的交点,并调整多球体模型以匹配目标体积 5.比较和可视化模型:比较多个多球体模型的体素频率,并计算多球体模型与原始表面模型之间的距离,以进行2D/3D可视化 使用案例: 骨科和生物力学体积建模 复杂结构的多球模型形状近似 基于体素拟合度量的模型选择 基于距离的患者特定几何形状和近似值分析 优点: 复杂几何的多球体模型 可扩展模型(基于体素)-自动调整到目标体积 可视化就绪输出(距离图)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值