大家好,从更新频率来看已经将近半年没更新了。虽然手头的草稿一直有在积累与更新,然而不分享出来几乎等于没有意义。所以近期在把Hakyll折腾地差不多后。终于决定开工啦。

这一次我们来简单介绍一下Haskell的工作环境如何搭建较为合理。

故事背景

大学的时候我们主要学的集中在C与Java。严格来说那时的专业和(国外)正规的计算机科学专业比有一定内容的差距。不过感谢当时的操作系统与微机课老师,在他们指导下学习还是让保持了对计算机实践方面的兴趣。然而工作后因为主要负责与Web相关较大的工作,所以接触的动态脚本语言更多,例如PHP、Perl、Python与Ruby等等。所以非常想借机能把静态编译类型的语言再捡起来加强基础知识与技能。C几乎没有什么悬念,便寻找另一种思路,更接近编程本质与数学的函数式编程就引起了兴趣。再经过尝试和比较后最终选择了静态类型,编译运行的纯函数式语言Haskell,更重要地是在学习Haskell的时候会学习到更多数学学科方面的知识,这对计算机科学领域本身也是非常有帮助的。

谈到学习一门新的语言,许多快速教程(不乏质量颇优)会在开始快速介绍基础语法,代码的写法等等,而对这种语言的思路与设计原理很少提及,认为是进阶话题。然而另一种极端则是讲故事那样将技法与原理娓娓道来,好是好,只是……对于那些有基础的大大们总有些隔靴搔痒的味道。

在我看来,学习一样新事物想要快速实现生产力(即使明知道质量必然不如打扎实基础后的),那么我们总需要一些快速上手的实践指引,即使不是“最佳”的,也至少是昏暗森林里的一缕月光。

废话少说,笔者在这里想提的,就是从工程角度出发,如何应用Haskell生态下的工具,构建工作环境。

准备工作

简而言之,得先有能编译运行Haskell的环境。可以根据自己的系统选择包管理器安装或者从官方下载预先编译好的二进制分发版本

提示:如果有巨巨和笔者一样使用的是Gentoo Linux,那么可以考虑直接使用预先编译好的二进制包,没有带CFLAGS优化,所以理论上可以兼容同样ARCH的CPU,请注意仅提供x86_64版本。

一般情况下,得至少有以下必要的工具

  • GHC,应该至少有7.6版本以上
  • cabal库,这个GHC安装会自带
  • cabal-install,需要另外安装,推荐各发行版自带的包管理器里的版本,因为其依赖的其他库版本如果搞错,会相当麻烦。

安装完成后应该可以使用以下命令:

  • ghc,Haskell社区的开源编译器
  • ghci,交互式命令行,方便尝试Haskell各种代码效果
  • cabal,Haskell自己的包管理器,用于方便地安装分发第三方源代码,注意这个要安装了cabal-install后方可使用。

代码的执行

运行自己写的代码

最快捷的测试少量代码的方式是使用ghci,直接在交互式命令行中敲入命令就可以看到效果。

$ ghci
GHCi, version 7.8.4: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> let { factorial 1 = 1; factorial n = n * factorial (n - 1) }
Prelude> factorial 10
3628800

ghci还可以方便地用来读取文件中的源代码,例如有这样一个文件hello.hs,内容如下:

main :: IO()
main = putStrLn "Hello, Haskeller!"

ghci中使用:l命令即可加载运行,ghci会帮助用户完成繁琐的编译命令过程。

$ ghci
GHCi, version 7.8.4: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :l hello.hs
[1 of 1] Compiling Main             ( hello.hs, interpreted )
Ok, modules loaded: Main.
*Main> main
Hello, Haskeller!
*Main>

编译单个源代码文件

$ ghc hello.hs
[1 of 1] Compiling Main             ( hello.hs, hello.o )
Linking hello ...
$ ./hello
Hello, Haskeller!

而对于拥有多个源代码文件的项目而言,可以采用import机制来解决。然而这种方法并不总能解决问题,所以才会有更全面的构建工具Cabal

Cabal,Haskell项目的构建工具

现代开源软件的代码分发机制的精髓,就在于包机制(开源软件架构:(Python)打包机制)。用以解决软件代码见的依赖、分发等等问题。

主流编程语言几乎都有一套构建方法以及分发工具,例如:

  • Java - Maven
  • Python - setuptools、pip等
  • Ruby - Bundler
  • PHP - PECL、Composer
  • JavaScript - NodeJS NPM、bower
  • Perl - CPAN
  • .NET - 甚至微软都有NuGet
  • C与C++情况比较特殊,不过传统来说,各操作系统的包管理器已经起到了这个作用。而构建工具有Make,CMake等。

对于一个不算年轻的社区来说,Haskell自然也有担任这角色的工具,即Cabal

Cabal的基本用法有如包管理器,并且和Ruby的Bundler类似,没有root权限也可以为当前用户安装。 和传统一样,默认root权限安装将会把资源安装到/usr/local下,对于普通用户将会安装至$HOME/.cabal。在使用cabal编译构建项目时也会根据实际情景应用。这点在其他包管理器与分发机制中已经相当熟悉,这里不再赘述。

Cabal的基础用法,笔者也不浪费篇幅照抄,而是交给文档吧。

由于GHC与Cabal默认皆是以静态链接编译的,所以全局安装或使用同一个库文件时便会出现问题。(那么如何使用动态链接?) 所以这里从工程角度想提的是,隔离环境的重要性(Heroku工程师们总结的现代软件作为服务的12个要素)。让每个项目的工作目录隔离的好处在于:

  • 运行环境在特定语言层面上是完全隔离的,所以更换依赖版本不会影响其他环境。(显示依赖与隔离
  • 包的一个特点是有,包的meta信息描述,其中会包括依赖信息,将这个一起发布,将有助于在分发其他环境里也构建出较为一致的运行环境。(尽量保持环境的一致

所以笔者介绍的重点,就在于Cabal的沙盒模型。

cabal sandbox

这个功能有如Python的virtualenv与Ruby的Bundler。我们动手来演示,以目前笔者正在使用基于Hakyll搭建的本站来说。

先初始化一个cabal包的工作路径:

$ cabal init

这会创建一个*.cabal描述文件,我们来看能够使用的最小配置。

name:               momoka
version:            0.1.0.0
build-type:         Simple
cabal-version:      >= 1.10

executable site
main-is:            site.hs           -- 指定主函数入口文件
build-depends:      base == 4.*       -- 申明依赖
                  , hakyll == 4.7.*
ghc-options:        -threaded
default-language:   Haskell2010

有了这个文件我们才能开始构建工作。现在我们来创建沙盒隔离环境:

$ cabal sandbox init

这会自动创建一个cabal.sandbox.config文件(不需要手工编辑)与.cabal-sandbox目录,以后这个工作目录下的编译构建工作与结果都将在这里体现。

沙盒的另一个用处就在于添加本地的依赖,这样便于修改依赖。例如Pandoc虽然支持mediawiki的格式文件转换,但Hakyll在文件支持上并没有加入。所以笔者作了一些简单的修改使其能够使用。这时需要让cabal使用这个修改过的依赖,需要进行如下操作

$ cabal sandbox add-source /path/to/hakyll

其他情况下是否使用沙盒,cabal使用起来基本是一样的:

$ cabal build --dependencies-only
$ cabal install

在这个项目中,我们还指定了最终结果是一个可执行的程序叫做site。编译安装完成后这个命令在哪里呢?

$ ls .cabal-sandbox/bin/
site ...

每次都敲完整的路径比较麻烦,修改一下PATH变量。

$ export PATH=".cabal-sandbox/bin:$PATH"

在项目根目录下就可以直接运行命令了:

$ site clean
$ site build
$ site serve
$ site watch

总结

以上,我们简略地实践了一下Cabal在构建Haskell工作环境中的用途和常见情景。然而这些仅仅是知识技能的冰山一角,强烈推荐想学习Haskell的巨巨们翻开参考资料与链接那头的文章,它们包含了更为全面,完整的使用情景与设计初衷。希望本文能为您带来启发,我们便可更自如地处理运行写下的Haskell代码啦。

参考与扩展阅读

__END__