Haskell快餐教程(1) - 初见

Haskell是一门由委员会发明的纯函数式语言。最早的标准制定于1990年,后来在1998年有较大的修订。最新的标准是2010年推出的,具体内容可以在这里看到:https://www.haskell.org/onlinereport/haskell2010/

编译器我们可以使用ghc: https://www.haskell.org/ghc/

ghc本身是个编译器,但是也提供了一个交互式的环境ghci. 我们后面的操作都以ghci为例。

数据类型

Haskell是门静态类型的语言,数据类型是我们基本上比较熟悉的。
为了查看数据类型,我们先在ghci中打开数据类型的开关:

:set +t

布尔值

Haskell使用==表示相等,使用/=表示不等。

Prelude> 1 == 1
True
it :: Bool
Prelude> 1 /= 1
False
it :: Bool

与C语言一样,&&表示逻辑与,||表示逻辑或。

例:

Prelude> let a = 1
a :: Num p => p
Prelude> a > 0 && a < 10
True
it :: Bool

字符与字符串

Haskell的字符串也是字符数组。字符用单引号,字符串用双引号:

Prelude> let c1 = 'C'
c1 :: Char
Prelude> c1
'C'
it :: Char
Prelude> let s2 = "C"
s2 :: [Char]
Prelude> s2
"C"
it :: [Char]

我们用字符数组的方式来表示,结果获取的一样是个字符串,我们看个例子:

Prelude> let s3 = ['H','e']
s3 :: [Char]
Prelude> s3
"He"
it :: [Char]

字符串采用++进行连接:

Prelude> let s1 = "Hello" ++ "World"
s1 :: [Char]
Prelude> s1
"HelloWorld"
it :: [Char]

数值

Haskell的数值系统比较有趣。
首先,底层没啥神秘的,像整数Int,单精度浮点数Floating,双精度浮点数Double之类的都如你所想:

Prelude> let a2 = 1 :: Double
a2 :: Double
Prelude> a2
1.0
it :: Double
Prelude> let b1 = 1 :: Int
b1 :: Int
Prelude> b1
1
it :: Int

在Standard ML中我们用":"来指示类型,而在Haskell中使用两个冒号。

指定一个类型之后,剩下的类型由系统自动推断:

Prelude> let b2 = b1 * 2
b2 :: Int
Prelude> b2
2
it :: Int

如果一个整型与浮点数进行操作会报错:

Prelude> let b3 = b1 + 0.1<interactive>:99:15: error:• No instance for (Fractional Int) arising from the literal ‘0.1’• In the second argument of ‘(+)’, namely ‘0.1’In the expression: b1 + 0.1In an equation for ‘b3’: b3 = b1 + 0.1

但是,如果不指定类型,一个整型字面值与浮点型字面值是可以计算的,因为系统默认是用Fractional类来处理它们的:

Prelude> let b4 = 1 + 0.1
b4 :: Fractional a => a
Prelude> b4
1.1
it :: Fractional a => a

Haskell也支持像log, sin, sqrt之类的数学函数,它们的返回值是Floating类型的:

Prelude> log(2)
0.6931471805599453
it :: Floating a => a
Prelude> sin(pi)
1.2246467991473532e-16
it :: Floating a => a
Prelude> sqrt(2)
1.4142135623730951
it :: Floating a => a

元组

与其它语言类似,Haskell使用"()"来表示元组。元组中的类型任意。
例:

*Test2> let t2 = (2:: Int, 3::Double)
t2 :: (Int, Double)
*Test2> t2
(2,3.0)
it :: (Int, Double)

列表

作为一种函数式语言,Haskell当然也支持列表,也是用"[]"来表示,每个列表中的元素都是相同的。

例:

*Test2> let l1 = [1::Int,2,3,4]
l1 :: [Int]
*Test2> l1
[1,2,3,4]
it :: [Int]

不出意外的,Haskell也支持"…"运算符生成序列:

*Test2> let l2 = [(1::Int)..10]
l2 :: [Int]
*Test2> l2
[1,2,3,4,5,6,7,8,9,10]
it :: [Int]

hs和lhs文件

下面我们开始在文件中写haskell源代码。
haskell支持两种格式的代码:hs格式和lhs格式。
其中hs格式就是普通的源代码了,举个例子:

module Test wheredouble x = (+) x x

这里面顺便刚好介绍下运算符的前序用法,除了像x+x这样的中序用法,也可以将运算符放在前面,此时需要用括号括起来。

在hs文件中,用"–“来表示单行注释。”{- -}"来表示多行注释。

lhs则是带有文档的格式,代码需要用">"加上空格开头。
例:

> module Test2 where
> double2 x = x * 2

对于lhs来说,因为是代码被标明,其余的全都是注释,也就不用专门的注释符号了。

head和tail用于取表头元素和除去表头剩下的部分:

*Test2> head l2
1
it :: Int
*Test2> tail l2
[2,3,4,5,6,7,8,9,10]
it :: [Int]

另外,还可以采用模式匹配的方法来实现取表头尾:

*Test2> let(h:t)=l2
h :: Int
t :: [Int]
*Test2> h
1
it :: Int
*Test2> t
[2,3,4,5,6,7,8,9,10]
it :: [Int]

取最后一个元素的话,使用last函数:

*Test2> l3
[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
it :: [Double]
*Test2> last l3
10.0
it :: Double

可以通过length函数来求列表长度:

*Test2> length l2
10
it :: Int

函数

像上面的double和double2都是函数定义的方法。不需要fun和fn之类的关键字。

定义函数可以像上面一样直接定义。也可以采用模式匹配和哨兵表达式。

模式匹配

模式匹配就是列举出函数的各种情况,然后从前到后去挨个匹配。
我们举个例子计算斐波那契数列:

> fib1 0 = 1
> fib1 1 = 1
> fib1 x = (+) (fib1(x-1)) (fib1(x-2))

我们还可以给函数加个类型声明:

> fib1 :: Integer -> Integer
> fib1 0 = 1
> fib1 1 = 1
> fib1 x = (+) (fib1(x-1)) (fib1(x-2))

哨兵表达式

哨兵表达式是用布尔表达式来替代要匹配的内容。

> fib2 x
>  | x<0 = 0
>  | x<2 && x>=0 = 1
>  | otherwise = (+) (fib2(x-1)) (fib2(x-2))

lambda表达式

lambda表达式也就是匿名函数,在Haskell中使用"\参数列表 -> 函数体"的方式表示。

我们来一个取列表中每个元素的倒数的例子:

*Test2> let l3 = [1..10::Double]
l3 :: [Double]
*Test2> l3
[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
it :: [Double]
*Test2> map(\x -> 1/x)l3
[1.0,0.5,0.3333333333333333,0.25,0.2,0.16666666666666666,0.14285714285714285,0.125,0.1111111111111111,0.1]
it :: [Double]

匿名函数也可以用于filter函数,用于过滤列表中的内容:

*Test2> filter(\x -> x > 5)l3
[6.0,7.0,8.0,9.0,10.0]
it :: [Double]

光有map不行,还得有能reduce的函数啊。比如对列表求和,可以用sum函数:

*Test2> sum(l3)
55.0
it :: Double

也有求乘积的product函数:

*Test2> l3
[1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0,10.0]
it :: [Double]
*Test2> last l3
10.0
it :: Double

我们可以用fold系函数来完成这样的操作, foldl是从左往右计算。我们尝试用foldl来实现下product的功能:

*Test2> foldl (\x prod -> (*) prod x) 1 l3
3628800.0
it :: Double

其实根据交换律,从右往左算也没啥区别:

*Test2> foldr (\x prod -> (*) prod x) 1 l3
3628800.0
it :: Double

对于运算符,还不用这么复杂,有个foldl1:

*Test2> foldl1 (*) l3
3628800.0
it :: Double

生成可编译运行的代码

刚才我们都一直在ghci中工作,下面我们尝试可以通过ghc编译过的代码。
可编译的代码有两个特殊的要求:一个是要有Main模块作为入口;另一个是要有一个叫做main的IO Monad。
一听IO Monad很吓人啊,我们先看个例子:

module Main wheredouble x = (+) x xmain = putStrLn "Hello,World"

这样在不了解细节的情况下,看起来跟命令式语言好像也区别不太大哈。
我们调用ghc去编译,运行编译后的代码,就可以打印Hello,World了。

我们先来个lhs的例子:

> module Main where
> double2 x = x * 2
> fact22 0 = 1
> fact22 x = (*) (x) (fact22(x-1))
> fact32 x
>  | x > 1 = (*) x (fact32(x-1))
>  | otherwise = 1
> fib1 :: Integer -> Integer
> fib1 0 = 1
> fib1 1 = 1
> fib1 x = (+) (fib1(x-1)) (fib1(x-2))
> fib2 :: Integer -> Integer
> fib2 x
>  | x<0 = 0
>  | x<2 && x>=0 = 1
>  | otherwise = (+) (fib2(x-1)) (fib2(x-2))
> main = putStrLn "Welcome to Haskell"

haskell的包管理工具

haskell使用cabal做为包管理工具。

以ubuntu和debian为例,可以通过

sudo apt install cabal-install

来安装cabal。
macOS上可以通过brew install cabal-install来安装。

然后就可以像其它工具一样使用cabal了,比如cabal update更新包列表,cabal install来安装包。

例:

cabal install yesod

要想查找包列表或者是搜索包,可以到http://hackage.haskell.org/上查看。

如果想像python的virtualenv一样管理haskell,可以使用haskell-stack: https://docs.haskellstack.org/.

小结

作为一门委员会定义的纯函数语言,Haskell语言的初衷是可以成为函数式语言的通用平台。
我们这一节介绍了Haskell的基本特性,这些特性因为广泛地被近些年的新语言学习,所以其实我们并不陌生。关于函数式编程的方法,柯里化,类,monad等较深入的话题,我们在后面会慢慢展开。

Haskell快餐教程(1) - 初见相关推荐

  1. Standard ML快餐教程(1) - 初识

    Standard ML快餐教程(1) - 初识 好久没写快餐教程了,下面开始一个新的系列,关于函数式编程语言的系列.打算写三种语言:Standard ML,ocaml和Haskell. 这几门语言都不 ...

  2. ocaml快餐教程(3) - 基本结构

    ocaml快餐教程(3) - 基本结构 分支结构 ocaml中支持用if-then-else表达式. 例: # let pass x = if x>=60 then "pass&quo ...

  3. Tensorflow快餐教程(12) - 用机器写莎士比亚的戏剧

    高层框架:TFLearn和Keras 上一节我们学习了Tensorflow的高层API封装,可以通过简单的几步就生成一个DNN分类器来解决MNIST手写识别问题. 尽管Tensorflow也在不断推进 ...

  4. Tensorflow快餐教程(1) - 30行代码搞定手写识别

    摘要: Tensorflow入门教程1 去年买了几本讲tensorflow的书,结果今年看的时候发现有些样例代码所用的API已经过时了.看来自己维护一个保持更新的Tensorflow的教程还是有意义的 ...

  5. 代码补全快餐教程(1) - 30行代码见证奇迹

    代码补全快餐教程(1) - 30行代码见证奇迹 下面是我用30多行代码,包含了很多空行和注释的代码写成的代码补全模型.我们先看看效果吧. 补全效果案例 先来看个比较普通的(Python, Keras) ...

  6. PyTorch快餐教程2019 (1) - 从Transformer说起

    PyTorch快餐教程2019 (1) - 从Transformer说起 深度学习已经从热门技能向必备技能方向发展.然而,技术发展的道路并不是直线上升的,并不是说掌握了全连接网络.卷积网络和循环神经网 ...

  7. ocaml快餐教程(1) - 强类型语言

    ocaml快餐教程(1) - 强类型语言 Keep说:自律给我自由. 在汽车.航天.铁路等高可靠性要求的代码中,经常要求使用MISRA C/C++标准,该标准对于C语言中不同整数类型之间的赋值有比较严 ...

  8. 强化学习快餐教程(3) - 一条命令搞定atari游戏

    强化学习快餐教程(3) - 一条命令搞定atari游戏 通过上节的例子,我们试验出来,就算是像cartpole这样让一个杆子不倒这样的小模型,都不是特别容易搞定的. 那么像太空入侵者这么复杂的问题,建 ...

  9. opengrok搭建快餐教程2020

    opengrok搭建快餐教程2020 安装tomcat 看这个中文版教程的大概在国内,大家下载就找个镜像会快一些,比如清华的镜像: https://mirrors.tuna.tsinghua.edu. ...

最新文章

  1. JAVA - HashMap和HashTable
  2. Python设计模式-享元模式
  3. 使用HTML5的WebSocket实现服务端和客户端数据通信(有演示和源码)
  4. 每日英语:Relationship Repair: 10 Tips for Thinking Like a Therapist
  5. 超图桌面版加载obj 3D模型
  6. jmeter linux安装,Linux下安装Jmeter
  7. C#提取HTML代码中的文字(转)
  8. ubuntu启动进程笔记
  9. Compound Words UVA - 10391(c++用法中substr函数用法+map实现)
  10. 显示控制器注释_欧姆龙可编程控制器CS1D-CPU产品型号说明及功能介绍
  11. c语言汉诺塔问题详解
  12. 三维建筑动画让你看懂真实的设计图
  13. 4本图神经网络中文书籍的比较
  14. 迪杰斯特拉算法(dijkstra)
  15. 软件授权 x-auth
  16. 典型相关分析及相关知识
  17. LBM学习讨论群推荐
  18. godaddy 域名 绑定阿里云服务器 绑定tomcat (.fm的域名可以看看)
  19. 休息时间!哪些业余活动能提升开发人员的技能?
  20. PT 系列 00·小谈 pt-kill

热门文章

  1. 金红石型TiO2单晶基片|金红石光学极二氧化钛TiO2单晶;氧化镓单晶衬底|Ga2O3单晶衬底
  2. 教婴幼儿学编程,别说不可思议
  3. 微软发布 FY23 Q3(自然年 2023年 Q1)财报
  4. Dropout 层应该加在什么地方?
  5. Linux做代理上网服务器
  6. LaTex使用技巧9:argmin / argmax下标写法
  7. (c语言)求两个整数较大值
  8. 【并行算法】知识点总结(一、二、三、九章)
  9. 阿里云服务器镜像是什么?镜像系统怎么选择?
  10. 为什么说B2B企业的供给侧改革来源于“协同”?