Haskell(四)总结和工具链
|Word Count:2.3k|Reading Time:9mins|Post Views:
- Haskell(一)入门
- Haskell(二)函数式编程
- Haskell(三) Monad
- Haskell(四)总结和工具链
- Haskell(五) 总结和展望
- Haskell(六) Project Euler 练习1-26
总结
- 安装方式:https://www.haskell.org/ghcup/ 这是最推荐的安装方式,建议都安装上。
- 特性介绍:https://www.haskell.org/
- 如何入门:
- 理解基础规则,比如不可变性、绑定、递归
- 学完基本语法,包括列表、元组、类型、模式匹配、Guard 写法、where 用法、let … in 用法、case … of 用法、
@
用法;
- 理解函数的类型签名
- 函数柯里化和 folder、map、zip、filter 等高阶函数、lambda 表达式;
$
.
的用法
- 递归的写法习惯
- 模块和导出,
- 语言拓展
- Data 库中的 List、Char、Map、Set。一些对应的常见操作 ,比如列表的
take
drop
sum
takeWhile
group
等,可以等用到再查。
- data 自定义类型(包括 product type 和 sum type 和类似泛型的 polymorphic types);从 Maybe、Either 理解 Kind 和多态类型,从而认识到 List 的实现;type 设置别名和 newtype 设置全新类型、
- 类型类和类型类继承 deriving,实例 instance、
- 一些特殊类型类(typeclass) Functor 和 fmap、Applicative 和
<*>
、Monad 和>>=
- 之后就是工程经验和 IO 了,在实际中慢慢积累吧。
- 入门路径:
- Haskell 生态速览:https://github.com/Gabriella439/post-rfc/blob/main/sotu.md
工具链
stack
stack 非常常用的工具链,它有很多 snapshot,把一些特定版本的库,都集成在这个 snapshot 里,然后要编译时,就会自动拉取这些库。这样就提供了可以复现的环境。除此之外,还可以自定义依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| resolver: lts-21.17
packages: - '.'
extra-deps: - git: https://github.com/ethereum/hevm.git commit: 91d906b6593f2ba74748fff9a7d34eadf1980ceb
- restless-git-0.7@sha256:346a5775a586f07ecb291036a8d3016c3484ccdc188b574bcdec0a82c12db293,968 - s-cargot-0.1.4.0@sha256:61ea1833fbb4c80d93577144870e449d2007d311c34d74252850bb48aa8c31fb,3525
extra-include-dirs: - /home/learner/.local/include extra-lib-dirs: - /home/learner/.local/lib
|
比如上面使用了 lts-21.17
的环境,所有的 snapshot 可以在 stackage 中查看。其他的是额外的依赖、额外的库,这里是 hevm 依赖了一些 C/CPP 写的密码学库。
stack path --stack-root
中就能查看到 stack 的存储位置,目录结构如下图所示。可以看到 config.yaml
是全局的配置,如果缺少项目的配置文件,就默认选择全局的,否则优先项目的配置。
在开发中,为了使用 stack 的环境,需要在命令前加 stack,比如说
1 2
| stack ghc -- -O2 -o main stack ghci
|
stack 会自动加载依赖,然后 --
之后是传给 runghc
的参数,优化等级 2 然后编译成可执行文件 main。
另外 stack 可以自己安装一些可执行文件。
另外需要注意的是,在较新的版本中,我们一般不直接编辑stack.yam
配置,而是有个 package.yaml
作为配置文件,然后会自动生成 stack 和 cabal 的配置文件。package.yaml
中定义了依赖、语言标准、默认语言拓展、库目录、可执行文件的配置和测试的配置。
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| name: echidna
author: Trail of Bits <echidna-dev@trailofbits.com> maintainer: Trail of Bits <echidna-dev@trailofbits.com>
version: 2.2.1
# https://github.com/haskell/cabal/issues/4739 ghc-options: -Wall -fno-warn-orphans -O2 -optP-Wno-nonportable-include-path
dependencies: - base - aeson - base16-bytestring - binary - bytestring - code-page - containers - data-bword
language: GHC2021
default-extensions: - DuplicateRecordFields - LambdaCase - MultiWayIf - NoFieldSelectors - OverloadedLabels - OverloadedRecordDot - OverloadedStrings
library: source-dirs: lib/
when: - condition: "!os(windows)" cpp-options: -DINTERACTIVE_UI dependencies: - brick - unix - vty
executables: echidna: main: Main.hs source-dirs: src/ dependencies: echidna ghc-options: -threaded -with-rtsopts=-N when: - condition: (os(linux) || os(windows)) && flag(static) ghc-options: - -optl-static - condition: os(linux) || os(windows) ghc-options: - -O2 - -optl-pthread - condition: os(darwin) extra-libraries: c++ ld-options: -Wl,-keep_dwarf_unwind ghc-options: -fcompact-unwind - condition: os(windows) && impl(ghc >= 9.4) dependencies: system-cxx-std-lib - condition: os(windows) && impl(ghc < 9.4) extra-libraries: stdc++
tests: echidna-testsuite: main: Spec.hs source-dirs: src/test dependencies: - echidna - tasty - tasty-hunit - tasty-quickcheck when: - condition: (os(linux) || os(windows)) && flag(static) ghc-options: - -optl-static - condition: os(linux) || os(windows) ghc-options: - -O2 - -optl-pthread - condition: os(darwin) extra-libraries: c++ ld-options: -Wl,-keep_dwarf_unwind ghc-options: -fcompact-unwind - condition: os(windows) && impl(ghc >= 9.4) dependencies: system-cxx-std-lib - condition: os(windows) && impl(ghc < 9.4) extra-libraries: stdc++
flags: static: description: Pass -static to ghc when linking the stack binary. manual: true default: false
|
cabal
我基本不用,因为 stack 集成了它的功能,一些 haskell 工具可能会建议使用它安装。
Nix
nix 是很方便的环境管理工具,但是只支持 Linux 和 MacOS,有部分项目使用 Nix 来开发。nix 自己维护着一套环境,然后进入 nix shell,就可以从优先使用 nix 的环境,从而不影响用户的环境。但是比较烦恼的是,我的编辑器无法使用 Nix 的 LSP。
主要讲 flake.nix 的管理方式,因为 hevm 是这样管理的,完整文件可见:https://github.com/ethereum/hevm/blob/ba00516bfbffcf14cf11211de94901833cb7eef2/flake.nix
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
| inputs = { flake-utils.url = "github:numtide/flake-utils"; nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; foundry.url = "github:shazow/foundry.nix/monthly"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; solidity = { url = "github:ethereum/solidity/1c8745c54a239d20b6fb0f79a8bd2628d779b27e"; flake = false; }; ethereum-tests = { url = "github:ethereum/tests/v12.2"; flake = false; }; cabal-head = { url = "github:haskell/cabal"; flake = false; }; forge-std = { url = "github:foundry-rs/forge-std"; flake = false; }; };
|
input 定义了一串的依赖的来源,比如 foundry 是从 GitHub 的 shazow/foundry.nix 仓库的 monthly 分支拉取,而且不是 flake 应用。其他的类似。
outputs 函数里接收包 nixpkgs 和 inputs 中的参数:
1
| outputs = { self, nixpkgs, flake-utils, solidity, forge-std, ethereum-tests, foundry, cabal-head, ... }:
|
然后在 flake-utils.lib.eachDefaultSystem (system: ...)
定义函数主体。
1 2
| let pkgs = (import nixpkgs { inherit system; config = { allowBroken = true; }; });
|
导入 nix 包,设置参数,system
参数是当前系统架构,允许包含那些被标记为不稳定或损坏的包。
1 2 3 4 5 6 7 8 9
| testDeps = with pkgs; [ go-ethereum solc z3 cvc5 git ] ++ lib.optional (!(pkgs.stdenv.isDarwin && pkgs.stdenv.isAarch64)) [ foundry.defaultPackage.${system} ];
|
测试的依赖包括了上面的 5 个工具,还有一个可选的工具,当系统环境不是 ARM64 架构下的 Darwin 平台时,还会引入根据 system 参数选择的 foundry。
接着定义需要的 Haskell 的包,ghc 9.4 作为默认包的集合,然后修改部分包的配置。self 是当前配置,super 是默认的父配置,rec 允许{…}里的元素相互定义。
1 2
| pkgs.haskell.packages.ghc94.override { overrides = with pkgs.haskell.lib; self: super: rec {...};};
|
覆盖的部分包括,dontCheck 不运行包的测试套件,self.callCabal2nix 这个函数为 Cabal 相关的包自动生成 Nix 表达式,比如对于 cabal-install
包,从定义好的来源获取,使用默认参数。doJailbreak 忽略版本限制。
1 2 3 4 5 6 7 8
| cabal-install = dontCheck (self.callCabal2nix "cabal-install" "${cabal-head}/cabal-install" {});
cabal-install-solver = dontCheck (self.callCabal2nix "cabal-install-solver" "${cabal-head}/cabal-install-solver" {});
unix = dontCheck (doJailbreak super.unix_2_8_1_1); filepath = dontCheck (doJailbreak super.filepath_1_4_100_4); process = dontCheck (doJailbreak super.process_1_6_17_0);
|
那么简单的说,用自定义的来源重新定义了 cabal 相关的包的属性,然后一些包构建的时候不运行测试,而且忽视严格的版本限制。
1 2 3
| secp256k1-static = stripDylib (pkgs.secp256k1.overrideAttrs (attrs: { configureFlags = attrs.configureFlags ++ [ "--enable-static" ]; }));
|
修改 secp256k1 库的属性,追加了生成静态库而不是动态库,用于构建完整可独立运行的软件。
开始准备构建 hevm 的参数,初始化处理流水线的参数,然后()
里的值以此传给[]
里的多个函数,比如第一个函数处理完,把结果传给第二个函数:
1
| hevmUnwrapped = (with pkgs; lib.pipe (...)[..])
|
首先从当前目录下的 hevm.cabal
文件,生成 nix 表达式。然后依赖 secp256k1 的 C 语言密码学库。
第一个函数修改构建时 Cabal 的参数,构建时执行测试。第二个函数把 solc 等工具依赖加入测试的依赖里。第三个函数在构建时添加 -v3,输出详细日志。后面的函数也是类似的,增加了传递给 ghc 的依赖库,编译参数等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| (haskell.lib.compose.overrideCabal (old: { testTarget = "test"; })) (haskell.lib.compose.addTestToolDepends testDeps) (haskell.lib.compose.appendBuildFlags ["-v3"])
(haskell.lib.compose.appendConfigureFlags ( [ "-fci" "-O2" "--extra-lib-dirs=${stripDylib (pkgs.gmp.override { withStatic = true; })}/lib" "--extra-lib-dirs=${stripDylib secp256k1-static}/lib" "--extra-lib-dirs=${stripDylib (libff.override { enableStatic = true; })}/lib" "--extra-lib-dirs=${zlib.static}/lib" "--extra-lib-dirs=${stripDylib (libffi.overrideAttrs (_: { dontDisableStatic = true; }))}/lib" "--extra-lib-dirs=${stripDylib (ncurses.override { enableStatic = true; })}/lib" ] ++ lib.optionals stdenv.isLinux [ "--enable-executable-static" # TODO: replace this with musl: https://stackoverflow.com/a/57478728 "--extra-lib-dirs=${glibc}/lib" "--extra-lib-dirs=${glibc.static}/lib" ]))
|
等等这些定义好了后,在 in rec{...}
里执行编译命令。
总而言之,Nix 提供了一种专门的语法,用于描述依赖关系和构建的参数。并且它提供了全局的且独立的工具,这样能够避免环境之间的冲突。但是可以知道,很多工具它都自己编译,可能第一次运行速度会比较慢。另外,nix shell 可能需要额外的配置,才能让 vim 等软件用上它的环境。·