如何安装 Haskell 工具链?

本文介绍如何在类 UNIX 系统上(Linux、MacOS、FreeBSD)从零开始一步步安装 Haskell 工具链(编译器、构建工具、编辑器),特别是考虑到国内特殊网络环境,会介绍如何使用国内源。注意:

  • 在 2023 年的今天,Cabal 已经取得了长足进步,因此本文将完全不涉及 Stack。

  • 如果你使用 Windows,好消息是 Haskell 工具链完整支持 Windows,但超出本文范围之外。尽管如此,本文仍然适用于 WSL。

  • 目前国内只有上海交大和中科大有 Haskell 工具链的完整镜像(GHCup、Hackage、Stackage)。本文采用中科大镜像,你可以手动修改命令来使用其他镜像。

本文的目标读者是对 Haskell 没有任何了解的初学者。

安装 GHCup、GHC 和其他工具

GHCup 是一个 Haskell 工具链的版本管理器。简单来说,它可用于安装不同版本的 GHC、Cabal、HLS 等工具。

准备工作

GHCup 是一个近期出现的工具,因此国内目前只有中科大上海交大有镜像。在安装 GHCup 之前,我们要做一件额外的工作,创建 ~/.cabal 目录,并创建 ~/.cabal/config 文件,填入如下内容:

repository mirrors.ustc.edu.cn
  url: https://mirrors.ustc.edu.cn/hackage/
  secure: True

这是因为 GHCup 在安装 Cabal 时会进行初始化(会下载一个 100MB 的文件),但此时我们还没有替换 Hackage 源!这一步首先替换 Hackage 源。之后安装过程就会如丝般顺滑。

执行安装

在终端中运行如下命令:

curl --proto '=https' --tlsv1.2 -sSf https://mirrors.ustc.edu.cn/ghcup/sh/bootstrap-haskell | BOOTSTRAP_HASKELL_YAML=https://mirrors.ustc.edu.cn/ghcup/ghcup-metadata/ghcup-0.0.7.yaml sh

(是的,一行。)

很快,你会看到如下界面:

ghcup 安装脚本
ghcup 安装脚本

**安装过程请仔细阅读所有提示。**针对三个问题,下面是我的推荐:

  1. **是否将 ghcup 目录加入 PATH?**直接回车接受默认值,如果你在看这篇文章,说明你一定会用到。

  2. **是否安装 Haskell Language Server?**安装与否均可,之后想装也很容易。这里输入 Y 安装(你大概是希望使用更高级的 IDE 功能的)。

  3. **是否安装 Stack?**输入 N,不安装。

之后一路回车,GHCup 将下载安装推荐版本的 GHC 和 Cabal,并完成初始化。(截至本文写作时,GHC 版本是 8.10.7)。结束后,你会看到一大片提示,请仔细阅读所有输出。这里必做的事情是设置当前 shell 的 PATH (或者重启一下 shell 也行):

source ~/.ghcup/env

测试安装

搞定!你已经可以用起来 Haskell 了。

$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 8.10.7

$ cabal --version
cabal-install version 3.6.2.0
compiled using version 3.6.2.0 of the Cabal library

$ ghci
GHCi, version 8.10.7: https://www.haskell.org/ghc/  :? for help
Prelude> putStrLn "Hello, Haskell!"
Hello, Haskell!
Prelude> 
Leaving GHCi.

配置 GHCup 源

我们上面用了环境变量临时修改了 GHCup 元数据地址,这里我们把镜像写到配置里让国内源永久生效。修改 ~/.ghcup/config.yaml(如不存在就创建),添加如下内容:

url-source:
    OwnSource: https://mirrors.ustc.edu.cn/ghcup/ghcup-metadata/ghcup-0.0.7.yaml

GHCup 的用法

ghcup 有一个很好用的命令叫 tui,所有 ghcup 的操作均可以从这里完成,运行 ghcup tui 会显示如下界面。

ghcup tui
ghcup tui

其中打双对勾的是当前选中版本。比如你安装了 GHC 9.2.1 和 GHC 8.10.7 两个版本,但是目前选中的是 8.10.7,那么运行 ghc 命令会调用 8.10.7 版本而非 9.2.1 版本。你可以安装多个版本,但一次只能选中一个版本(用 s 键选中别的版本)。好了,剩下的命令都顾名思义,自己试试即可。

如果想安装 HLS 的话,随时可以从这里安装。

配置编辑器

本文的重点不是配置编辑器,下面只给出一些提示。请参阅其他资料(尤其是相应插件自己的文档)了解如何配置、如何使用。

Visual Studio Code

Visual Studio Code 是目前最流行的代码编辑器之一,Haskell 语言服务器则为 LSP 提供了较好的支持,两者相得益彰。只需在扩展市场中搜索名为 Haskell 的扩展并安装即可。

之后,任意打开一个 .hs 文件,Visual Studio Code 就会自动调用 GHCup 已经安装的 HLS,可以充分体验 Haskell 的 IDE 编辑体验了!(如果 VSCode 提示你要下载 HLS,请下转故障排除章节。

恭喜,至此你已经完成配置了 2023 年最先进的 Haskell 工具链+编辑环境!尽情享受吧!

Emacs

Emacs 默认没有 Haskell 代码高亮,但可以安装 haskell-mode。它的功能非常丰富,推荐阅读其文档。比如一个很有用的命令是 haskell-process-load-or-reload ,可以用来快速打开一个 ghci 并载入当前文件。

如果想要使用 HLS:

  1. 先手动通过 ghcup 安装 HLS。

  2. 再安装 lsp-mode、lsp-haskell。

That’s all!

Vim 和 Neovim

vim 原生支持 Haskell 的代码高亮。要与 HLS 集成,可使用 coc.nvim

Cabal 应急生存手册

使用 Haskell 时,强烈建议首先创建一个项目。这是因为 Cabal 让使用第三方库非常方便。Cabal 有一个不错的使用指南,值得收藏:

https://cabal.readthedocs.io/en/3.6/

创建项目

$ mkdir my-first-haskell-project
$ cd my-first-haskell-project
$ cabal init     # 在当前目录下初始化一个 Cabal 项目

Guessing dependencies...

Generating LICENSE...
Warning: unknown license type, you must put a copy in LICENSE yourself.
Generating CHANGELOG.md...
Generating app/Main.hs...
Generating my-first-haskell-project.cabal...

Warning: no synopsis given. You should edit the .cabal file and add one.
You may want to edit the .cabal file and add a Description field.

$ tree .
.
├── CHANGELOG.md
├── app
│   └── Main.hs
└── my-first-haskell-project.cabal

1 directory, 3 files

可以看到,Cabal 创建了 3 个文件。其中 CHANGELOG.md 是用来记录版本更新内容的,对初学者来说不用关心。

增加第三方依赖

打开 .cabal 文件,除去基本的项目信息外,有一个 executable 段落:

executable my-first-haskell-project
    main-is:          Main.hs               -- 入口文件名
    build-depends:    base ^>=4.14.3.0      -- 依赖
    hs-source-dirs:   app                   -- 代码目录
    default-language: Haskell2010           -- 默认语言

其中, ^>=base (Haskell 标准库) 的版本增加了限制。对学习者来说,可以忽视这些限制(直接去掉版本限制)。下面我们增加 time 这个库为一个依赖:

executable my-first-haskell-project
    main-is:          Main.hs
    build-depends:    base, time     -- 这里添加了 time 库
    hs-source-dirs:   app
    default-language: Haskell2010

然后,就可以在代码文件 app/Main.hs 中使用了:

module Main where

import Data.Time.LocalTime   -- 来自 timemain :: IO ()
main = do
  now <- getZonedTime
  print now

(补充说明:Cabal 在编译时会自动求解一套互相兼容的包的版本,因此不写版本限制对学习者来说最方便,出问题的概率小了很多。但同时要注意这是不好的开发实践,在发布软件时最好先使用 cabal gen-bounds 命令生成兼容的版本范围。尽管不写版本范围让可行解的范围大了不少,但仍然有可能出现不兼容问题——会输出大量令人生畏的报错信息——这多数情况是因为你的某个依赖实在是太旧了,最好换一个新的同类包。)

编译、运行和 REPL

$ cabal build # 编译
[一大堆输出]
[1 of 1] Compiling Main [一堆输出]
Linking ..../my-first-haskell-project

$ cabal run   # 运行
[cabal run 会自动调用 cabal build,所以这里可能是 Up to date 也可能是和 cabal build 一样的一大堆输出]
2022-01-11 01:38:32.87706 CST

$ cabal repl
[一大堆输出]
*Main>        # ghci,但自动载入 Main 模块,并且可以自由用任何第三方依赖里的模块

Cabal 在编译时会自动从 Hackage 上下载并构建第三方依赖。因此你可以自由地在 Hackage 上找自己感兴趣的包,然后加到项目依赖里来实验。比如下面这些好玩的包,可以试试看!

  • gloss, 2D 图形库。

  • reanimate, 类似 manim 的动画库。首页有很多酷炫例子。

  • gi-gtk-declarative, GUI 库。

  • inline-r, 与 R 语言无缝协作。

  • Chart, 绘制 2D 图表。

  • accelerate, 硬件加速的数组运算库,可以编译到 GPU 上运行!

  • ad, 自动微分。

  • 等等等等……

Hackage 上有很多有趣的包,现在都是你的了。

故障排除

如何彻底卸载?如何彻底重新安装?

rm -rf ~/.cabal ~/.ghcup ~/.ghc

VSCode 提示我要下载 HLS,但我在 GHCup 那里已经下载过了?

有如下原因:

  1. 你没有使用 GHCup 安装 HLS。现在去安装一下?如果还不行,再看下面的。

  2. 你没有添加 GHCup 的路径到 PATH 中。如果始终不行,参考下面的配置。

在 Haskell 扩展页面,点击页面上的小齿轮打开扩展配置。

  1. 找到 “Haskell: Server Executable Path” 配置项。

  2. 输入 ~/.ghcup/bin/haskell-language-server-wrapper

  3. 重启 VSCode。

这时候重新打开文件应该就可以正确调用到 GHCup 安装的 HLS 上了。

编辑器提示我 HLS 启动失败,怎么回事?

最可能的原因是你启用了 HLS 目前不支持的新版本 GHC。如果想使用 HLS,请务必在 ghcup tui 中选中一个有 hls-powered 标记的 GHC 版本(比如 9.0.1, 8.10.7 等)。

其他环境错误

  1. Mac 上安装时,若报错信息中有 configure 字样,是因为没有安装 Xcode Command Line Tools。

  2. 如果步骤“执行安装”中运行命令后提示类似 curl: command not found,需要安装curl。Linux 下命令为:sudo apt install curl

  3. 在 Linux 下可能需要额外安装一些包。以 Linux Mint 20.3(同 Ubuntu 20.04)为例:执行sudo apt install build-essential libgmp-dev libncurses5-dev libncursesw5-dev 来安装 gcc、gmp 库、ncurses 库。其他发行版可参照之。


修订记录

  1. 2023-07-09: 少量格式和字句修正;「2022」更新到「2023」。

  2. 2022-09-15: 将 ghcup metadata 版本升级至 0.0.7。