使用 Docker 搭建 Haskell 开发环境

最近又又又又开始学习 Haskell,之前学习的时候都使用单文件的形式,这对使用第三方库产生了很大的障碍(最终也没学下去),最近研究了通过 Docker 去配置 Haskell 环境的方式,这里记录一下,并且同时也去使用 Stack 创建空项目和 Yesod 项目、配置 plfa 环境。

前置条件:

  • 安装 Docker
  • 安装 VS Code,以及 Dev Containers 扩展

创建 linux 容器,安装依赖

首先要创建 linux 容器,使用 fedora 做例子:

我曾尝试过 ubuntu 镜像,但其在创建 Yesod 项目的时候遇到奇怪的编码问题(表现在编译 language-javascript 库时 getContents 报错),应该是系统区域相关的玩意被裁剪的原因

1
docker run -di -p 8080:8080 --name haskell-container fedora

然后打开本机的 VSCode,点击左下角蓝色图标,选择 Attach to Running Container,选择该容器。

打开后,参考https://mirrors.ustc.edu.cn/help/fedora.html,执行:

1
2
3
4
5
6
7
8
sed -e 's|^metalink=|#metalink=|g' \
-e 's|^#baseurl=http://download.example/pub/fedora/linux|baseurl=https://mirrors.ustc.edu.cn/fedora|g' \
-i.bak \
/etc/yum.repos.d/fedora.repo \
/etc/yum.repos.d/fedora-modular.repo \
/etc/yum.repos.d/fedora-updates.repo \
/etc/yum.repos.d/fedora-updates-modular.repo
dnf makecache

安装 Ghcup,修改 Ghcup,cabal,stack 源

这里参考https://mirrors.ustc.edu.cn/help/ghcup.html

首先安装 ghc 所需依赖:

1
dnf install -y gcc gcc-c++ gmp gmp-devel make ncurses ncurses-compat-libs xz perl

执行下面的命令,一路 y 下去:

1
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

如果上面这个命令出现了奇怪的错误,或许得使用官方的脚本(注意网络):

1
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh

安装后,尝试执行 ghci:

1
2
ghci> putStrLn "Hello, Happy World!"
Hello, Happy World!

bingo!

配置 Ghcup,Cabal,Stack 源

这节参考https://mirrors.ustc.edu.cn/help/ghcup.htmlhttps://mirrors.ustc.edu.cn/help/hackage.htmlhttps://mirrors.ustc.edu.cn/help/stackage.html

(用 vi 或者 VSCode)编辑~/.ghcup/config.yaml,添加:

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

编辑~/.stack/config.yaml,添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package-indices:
- download-prefix: https://mirrors.ustc.edu.cn/hackage/
hackage-security:
keyids:
- 0a5c7ea47cd1b15f01f5f51a33adda7e655bc0f0b0615baa8e271f4c3351e21d
- 1ea9ba32c526d1cc91ab5e5bd364ec5e9e8cb67179a471872f6e26f0ae773d42
- 280b10153a522681163658cb49f632cde3f38d768b736ddbc901d99a1a772833
- 2a96b1889dc221c17296fcc2bb34b908ca9734376f0f361660200935916ef201
- 2c6c3627bd6c982990239487f1abd02e08a02e6cf16edb105a8012d444d870c3
- 51f0161b906011b52c6613376b1ae937670da69322113a246a09f807c62f6921
- 772e9f4c7db33d251d5c6e357199c819e569d130857dc225549b40845ff0890d
- aa315286e6ad281ad61182235533c41e806e5a787e0b6d1e7eef3f09d137d2e9
- fe331502606802feac15e514d9b9ea83fee8b6ffef71335479a2e68d84adc6b0
key-threshold: 3 # number of keys required

# ignore expiration date, see https://github.com/commercialhaskell/stack/pull/4614
ignore-expiry: true

setup-info-locations:
- http://mirrors.ustc.edu.cn/stackage/stack-setup.yaml
urls:
latest-snapshot: http://mirrors.ustc.edu.cn/stackage/snapshots.json
snapshot-location-base: http://mirrors.ustc.edu.cn/stackage/stackage-snapshots/

编辑~/.cabal/config,找到repository hackage.haskell.org一行,编辑为:

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

然后执行下列命令:

1
2
mkdir ~/.stack/pantry
curl https://mirrors.ustc.edu.cn/stackage/stackage-content/stack/global-hints.yaml > ~/.stack/pantry/global-hints-cache.yaml

使用 Stack 创建项目

执行stack new project-name可以创建空项目,创建后进入该文件夹,执行stack build检查是否正常。

执行stack build若出现”Could not load module ‘Distribution.Simple’”的错误,移除~/.ghc/相应版本 GHC/environments/default应当能解决,参照https://stackoverflow.com/questions/70994294/problem-with-loading-module-distribution-simple

给 VSCode 安装 Haskell 扩展,打开项目目录并打开特定 hs 文件就应当可以开始编码了,可以尝试打开 src/Lib.hs 文件,起空行输入-- >>> show "Aloha",若出现 Evaluate 按钮则证明工作正常。

添加依赖

添加依赖时不要使用 cabal,可能会出现上一节的问题

但这还不够,如果能识别到 Stack 项目的依赖并给与补全才更好。

编辑 package.yaml,在 dependencies 配置下加入 random 依赖:

1
2
3
dependencies:
- base >= 4.7 && < 5
- random

然后,执行stack build,在另起一个文件 Playground.hs,添加下添加下列内容,导入所有所需依赖并点击 Evaluate:

1
2
3
4
5
6
7
8
9
10
11
12
module Playground (  ) where
import System.Random ( uniformR, mkStdGen, RandomGen, StdGen )
import Data.List ( unfoldr )

roll :: RandomGen g => g -> (Word, g)
roll = uniformR (1, 6)
rolls :: RandomGen g => g -> [Word]
rolls = unfoldr (Just . roll)
pureGen :: StdGen
pureGen = mkStdGen 42

-- >>> take 10 (rolls pureGen)

不管你能不能跑,反正我是能跑了 w。


一些书籍/框架环境的搭建

使用 Stack 创建 Yesod 项目

参考https://www.yesodweb.com/page/quickstart,执行 stack build 的时候会出错,查询日志发现缺失 zlib 头文件,使用 dnf 安装zlib-static, zlib-devel两个依赖即可。

执行完毕后,创建文件src/HelloWorld.hs,粘贴下面的代码(这类型安全的 html 模板和路由定义,震撼):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
( warp,
mkYesod,
whamlet,
parseRoutes,
Html,
Yesod(defaultLayout),
RenderRoute(renderRoute) )

data HelloWorld = HelloWorld

mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET
|]

instance Yesod HelloWorld

getHomeR :: Handler Html
getHomeR = defaultLayout [whamlet|
<p>Hello, Happy World!
|]

main :: IO ()
main = warp 8080 HelloWorld

然后,执行stack runhaskell src/HelloWorld.hs,在本机访问localhost:8080,若能成功访问,bingo!

The Haskell School of Music

首先安装需要的系统库:

1
dnf install -y alsa-lib-devel GLC_lib-devel

创建新 stack 项目,编辑 package.yaml 中 dependencies:

1
2
3
4
dependencies:
- base >= 4.7 && < 5
- Euterpea
- HSoM

编辑 stack.yaml 中 extra-deps 配置(默认是注释掉的),并配置allow-newer为true:

1
2
3
4
5
6
7
8
allow-newer: true
extra-deps:
- git: https://github.com/Euterpea/Euterpea2.git
commit: 55f78907ad29ce35e7e0b5ca101b60cd0efca555
- PortMidi-0.2.0.0
- HSoM-1.0.0
- UISF-0.4.0.0
- pure-fft-0.2.0

为何要修改 stack.yaml 呢?因为这些依赖不在 LTS 里,它们的版本号没有被指定,且Euterpea的最新版不在Stackage里;allow-newer表示无视版本的上界约束。

plfa

参考https://agda-zh.github.io/PLFA-zh/GettingStarted,以及https://schneide.blog/2020/09/21/compiling-agda-2-6-2-on-fedora-32

执行下面的命令去:

  • 安装 git
  • 对一个系统库进行链接(Haskell 为什么这么麻烦?)
  • 安装 agda
  • 获取 agda 标准库和 plfa 库
  • 拷贝库到指定位置
1
2
3
4
5
6
dnf install -y git
ln -s /usr/lib64/libtinfo.so.6 /usr/lib64/libtinfo.so
stack install Agda-2.6.2.2
git clone --depth 1 --recurse-submodules --shallow-submodules https://github.com/plfa/plfa.github.io ~/plfa
mkdir -p ~/.agda
cp ~/plfa/data/dotagda/* ~/.agda

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 协议 ,转载请注明出处!