Haskell是一个惰性的(lazy)、纯(pure)函数式程序设计语言。
Stack是一个跨平台的Haskell开发工具。它有如下特性:
- 自动安装GHC,并且允许安装多个不同版本的GHC;
- 自动安装项目依赖;
- 构建项目;
- 测试项目;
- 等等。
Linux/Unix下的脚本安装:
curl -sSL https://get.haskellstack.org/ | sh
或者
wget -qO- https://get.haskellstack.org/ | sh
Linux/Unix下的手动安装:
-
下载最新的软件包;
-
将下载的软件包解压到
~/.local/bin/
下; -
设置环境变量:
echo 'export PATH=~/.local/bin:$PATH' >> ~/.bashrc
-
安装依赖:
sudo apt install g++ gcc libc6-dev libffi-dev libgmp-dev zlib1g-dev make xz-utils git gnupg
由于国内网络问题,在使用Stack时,可以使用TUNA镜像站来加速开发。
在 Stack 的全局配置文件(Linux/OSX 下默认为~/.stack/config.yaml
,Windows下默认为%APPDATA%\stack\config.yaml
)里这样写即可:
package-indices:
- name: Tsinghua
download-prefix: https://mirrors.tuna.tsinghua.edu.cn/hackage/package/
http: https://mirrors.tuna.tsinghua.edu.cn/hackage/00-index.tar.gz
setup-info: "http://mirrors.tuna.tsinghua.edu.cn/stackage/stack-setup.yaml"
urls:
latest-snapshot: http://mirrors.tuna.tsinghua.edu.cn/stackage/snapshots.json
lts-build-plans: http://mirrors.tuna.tsinghua.edu.cn/stackage/lts-haskell/
nightly-build-plans: http://mirrors.tuna.tsinghua.edu.cn/stackage/stackage-nightly/
这样,stack 工具下载 Hackage 包、Stackage snapshot 配置文件、ghc 安装包的位置都在 TUNA 镜像站,不需要像之前一样走 github/s3。
Haskero是VSCode的一个Haskell插件。
在VSCode中安装完成Haskero后,还要在项目根目录上执行下列命令,安装intero:
$ stack build intero
intero是Haskero的一个后端,它用于抽取Haskell的类型信息和位置等。
ghc.exe
是一个Haskell的优化编译器。
命令格式:ghc 脚本 依赖
依赖
可以是脚本(.hs),也可以是目标文件(.o)。
执行结果将产生三个文件:
main.exe
或main
脚本名.o
脚本名.hi
.hi
是一个接口文件,它存储着模块的导出信息,二进制格式。
.o
是目标文件,它包含编译产生的机器码。
命令格式:ghc --make 脚本 依赖
执行结果将产生三个文件:
脚本名.exe
或脚本名
脚本名.o
脚本名.hi
命令格式:ghc -o 可执行文件 脚本 依赖
执行结果将产生三个文件:
可执行文件
脚本名.o
脚本名.hi
命令格式:ghc -c 脚本 依赖
执行结果将产生两个文件:
脚本名.o
脚本名.hi
前面三种方式的脚本中必须包含main
函数,这样才能产生可执行文件,而这种方式适用任何模块源文件的编译,源文件中可以不包含main
函数。
命令格式:ghc -package 扩展库名 …
例如:当前Haskell已经将Parsec包安装到lib/extralibs目录下,要使用Parsec包可以使用下面命令:
$ ghc -package parsec simple.hs
例如:假设已经手工将最新版本的Parsec编译到“c:\parsec”目录下(通常要编译Haskell库,只需要在命令行下在库所在的目录下执行make命令即可,因为Haskell库通常都带makefile),则可以使用下面命令进行编译:
$ ghc -c myfile -ic:\parsec
-i
参数来告诉ghc导入路径,即告诉ghc在哪里能找到所需的接口文件(.hi)。
如果要链接,则需要使用-L
告诉GHC在哪里找到库,使用-l
告诉GHC链接什么库:
$ ghc -o myprogram myfile1.o myfile2.o -Lc:\parsec -lparsec
假如A.hs中导入了B.hs中的某些变量,则要编译A.hs,只要:
ghc-c A.hs
就可以了,不需要先编译B.hs,然后再:ghc -c A.hs B.o
。因为GHC会自动计算依赖。
交互式的解释器和调试器。
runghc.exe
可以直接运行Haskell源文件,而不需要事先编译。
REPL是一个交互式编程环境。要启动REPL可以运行下列命令:
$ ghci
或者
$ stack ghci
GHC的命令行中有一个全局变量it
,用于保存上一次计算的结果:
ghci> it
"foobar"
it :: [Char]
ghci> it ++ 3
<interactive>:1:6:
…
ghci> it
"foobar"
it :: [Char]
ghci> it ++ "baz"
"foobarbaz"
it :: [Char]
当求值失败时,不改变当前it的值。
在交互模式下(ghci),定义一个变量与在源文件(.hs)中不一样,要在变量之前加一个let
:
ghci> let lostNumbers = [4,8,15,16,23,48]
ghci> lostNumbers
[4,8,15,16,23,48]
这里的let
与Haskell中let…in
中的let
没关系。也可以理解为,这里的let
结构的in
部分被省略,则默认为整个交互环境。
命令格式:
:load 脚本名
:load "脚本名"
:load ——表示消除已经加载的脚本(“prelude.hs”除外)。
脚本名
可以不包括扩展名.hs
,但可以包括路径。路径中的分隔符最好用/
。
例子:
ghci> :l add.hs
命令格式:
:edit 文件名
:edit ——打开上次使用edit命令打开的文件。
用默认编辑器打开指定任意类型文件,文件名
必须包含扩展名。并且,如果编辑的是Haskell脚本,则在编辑器被关闭后,将自动重载该脚本。
例子:
ghci> 3 + 2
5
ghci> :t it
it :: Integer
ghci> :t 3 + 2
3 + 2 :: (Num t) => t
注意:
:type
并不计算表达式的值,只是输出它的类型信息。
例子:
ghci> :i (+)
例子:
ghci> :m + Data.Ratio ——导入“Data.Ratio”模块
ghci> :m - Data.Ratio ——移除“Data.Ratio”模块
例如:GHCi提示符将显示当前顶层模块名,可以使用“:set”命令改变默认的显示:
:s prompt "ghci>"
该命令将默认的命令提示符改成“ghci>”。
如果要在GHCi输出计算结果的后面跟上它的类型信息,则需要:
ghci> :s +t
ghci> 'c'
'c'
it :: Char
在交互模式下,默认没有开启扩展功能,要开启它们,要加“-fglasgow-exts”命令行参数。
使用模板新建一个项目:
$ stack new my-project -p "category:office" ——使用默认模板创建项目
$ stack new my-project new-template -p "category:office" ——使用“new-template”模板创建项目
可以通过命令:stack templates
来查看所有可用的模板。
安装编译工具:
$ cd my-project
$ stack setup
它会根据需要安装编译工具,默认安装在~/.stack
目录下。
可以通过命令:stack path
来查看Stack的各种路径设置。
hello.hs
:
main = putStrLn "Hello, World!"
Haskell的源文件也称脚本有两种扩展名:
- 以
.hs
为后缀的脚本:它是传统的脚本风格。注释用--
或{- … -}
表示,其它的部分表示程序文本。 - 以
.lhs
为后缀的脚本。- Bird-scripts风格:每一以
>
开始的行表示程序文本,其它部分表示注释内容。注意:代码与注释之间必须有至少一个空行分隔。 - LaTEX风格:所有被
\begin{code}
与\end{code}
包围的部分为代码部分,除此以外为注释部分。注意:代码与注释可以不需要有空行分隔。它特别适合使用LaTeX的文本处理系统。
- Bird-scripts风格:每一以
Haskell的代码既支持自由编码风格,也支持利用缩进来表示代码结构的缩进编码风格。
--自由编码风格
foo = let {a = 1; b = 2;
c = 3} in a + b + c
--缩进编码风格
foo = let a = 1
b = 2
c = 3
in a + b + c
缩进规则:
在源文件的开始,第一个顶层声明或定义可以从任意一列开始,Haskell编译器或解释器将记住此缩进位置。后续的所有顶层声明必须缩进到同样位置。 后续如果是空白行或者缩进更加靠右,则被当作当前项的延续。如果缩进与前一个项开始的位置相同,会被当作在同一个块中开始一个新的项。 缩进时,尽量使用空格,而不要使用
Tab
键。
Haskell的代码总是定义在一个模块中。上面的代码没有显式定义在一个模块中,则默认为Main
模块。
Main
模块的main
是程序的入口。如果模块不需要独立运行(例如作为库),则main
不是必须的。
$ stack build
这将生成:
- 库文件(位于
my-project/.stack-work/install/i386-linux/lts-10.5/8.2.2/lib/i386-linux-ghc-8.2.2/
) - 一个可执行文件(位于
my-project/.stack-work/install/i386-linux/lts-10.5/8.2.2/bin/hello-exe
)。
$ stack exec hello-exe
$ stack test
Haskell有六种命名空间:
- 变量(variable):表示值。
- (值)构造器(constructor):表示值。
- 类型变量(type variable):表示类型。
- 类型构造器(type constructor):表示类型。
- 类型类(type class):类似于接口。
- 模块名(module name):表示模块。
变量和类型变量必须以小写字母或下划线开头,其他四种名称必须以大写字母开头。 在同一个作用域中,类型构造器与类型类不能同名。
函数可以是0元或任意多元:
pi = 3.14
triple x = x * 3
操作符是中置式的函数。
Haskell程序实际上是由声明和表达式组成的。
表达式求值就是将它归约为范式(normal form)的过程。实际上,Haskell只是将表达式归约为弱头范式(weak head normal form,WHNF)。
范式就是不可进一步归约的表达式。
可归约的表达式也称为redexes。
以小写字母(下划线看作小写字母)开头,由小写字母、大写字母、数字、单引号组成的标识符为名字的函数的应用,默认采用前置式表示:
plus 1 2
1 `plus` 2 --如果包围在“`”中,则变成中置式表示。
操作符默认采用中置式表示:
1 + 2
(+) 1 2 --将操作符包围在括号中,则为成前置式表示。
Prelude
包含了Haskell的标准函数库,它是唯一一个不需要导入或加载,就可以访问的模块。