
1. 为什么要在 2024 年折腾 Haskell先聊点实在的。标题叫“Haskell 入门学习一之安装试用”但真正动手之前我们得先搞清楚一个问题你为什么想学 Haskell如果你是因为听说“函数式编程能改变思维方式”才来的那这话只说对了一半。Haskell 的确会逼你用另一种方式思考问题——它不让你随手修改变量不让你随意写副作用类型系统严格到近乎苛刻。这种束缚一开始很难受但熬过前两周你会发现自己写任何代码时脑子里都会多一根弦这个函数到底纯不纯这个类型到底能不能表达得更精确这种习惯一旦养成你再回头看 Python 或 Java 代码会本能地捕捉到很多以前看不出来的坏味道。如果你是因为想换一门生产级语言来找机会的Haskell 的生态实话实说不如 Java、Go 或 Rust 那么热闹。但它在编译器、金融风控、区块链、形式化验证这些领域有自己稳固的位置。GHCGlasgow Haskell Compiler本身就是一个极其强大的编译器很多现代语言的特性比如类型类、函子、单子这些概念都能在 Haskell 里找到最纯粹的源头。哪怕你只是把 Haskell 当成“类型系统实验室”也完全不亏。这门语言适合谁来学我个人的建议是如果你已经写过至少一两年代码对循环、变量、函数调用这些基本概念不陌生那 Haskell 很适合作为你的“第二语言”或“思维扩充语言”。如果你完全零基础、第一次接触编程那不建议直接上来就啃 Haskell函数式思维叠加语法学习会很痛苦容易劝退。这一篇先解决一个最务实的痛点安装、跑通编译器、写第一个能运行的程序。我踩过的各种坑都会尽量写出来。2. 动手前的准备工作环境评估与工具链认识2.1 先搞清楚 Haskell 工具链的组成Haskell 不是“下载一个 IDE 就能跑”的语言它有一整套工具链。很多新手死在这一步不知道装哪个、装完发现版本对不上、编译器报错一脸懵。所以先把工具链的构成搞明白后面安装时心里就有底了。一个完整可用 Haskell 环境包括这几块GHCGlasgow Haskell Compiler核心编译器把.hs源码编译成可执行文件。没有它一切都免谈。CabalHaskell 的构建和打包工具负责下载依赖库、配置构建、生成项目。类似 Python 里的 pip setuptools 的合体。Stack另一个构建工具更强调可复现构建可以锁定 GHC 版本和依赖版本。Stack 本身会调用 GHC所以装 Stack 不代表不用装 GHC。HLSHaskell Language Server给编辑器VS Code、Neovim提供补全、跳转、类型提示的语言服务协议实现。不装也能写代码但装了体验会好很多。ghcupHaskell 工具链的版本管理工具类似 Python 的 pyenv 或 Node 的 nvm。它能把 GHC、Cabal、HLS 全部管理起来切换版本非常方便。现在主流的安装方式就是通过 ghcup 来完成不要手动去官网下载 GHC 二进制包然后自己配环境变量。手动装很容易出现路径找不到、版本冲突的问题用 ghcup 统一管理能省掉一大半烦恼。2.2 各操作系统的注意事项我分别说说三平台的情况。Windows 用户微软官方做了一个叫 GHCup 的安装程序原生 Windows 版支持 PowerShell 一键脚本安装也支持通过 MSYS2 来装。Windows 下最容易踩的坑是 PATH 路径里有空格或者中文或者是杀毒软件拦截 ghcup 的下载过程。建议直接去 https://www.haskell.org/ghcup/ 复制官方 PowerShell 命令用管理员权限的终端运行。Windows 下另外一个坑是 HLS 编译时可能缺 Visual Studio Build Tools如果之后遇到“C compiler not found”之类的报错需要先装 VS Build Tools C 工作负载。macOS 用户同样用 ghcup 官方脚本。但 macOS 用户要注意两点一是 Apple SiliconM 系列芯片下载的是aarch64版本的 GHCghcup 会自动检测不用手动干预二是 macOS 自带的 Xcode Command Line Tools 必须装否则后续编译代码时连接器会报错。如果遇到clang: error: linker command failed大概率就是 Command Line Tools 没装完整运行xcode-select --install补一下。Linux 用户最省心。需要系统里有build-essential、curl、libffi-dev、libgmp-dev、zlib1g-dev这些依赖。Debian/Ubuntu 系直接apt install build-essential curl libffi-dev libgmp-dev libgmp10 zlib1g-dev就行。CentOS/RHEL 系用自己的 yum/dnf 同步安装。我自己的经验是有条件的话尽量在 Linux 或 macOS 上学习 Haskell。不是说 Windows 不行而是很多开源教程、代码示例默认在 Unix 环境下测试Windows 偶尔会碰到路径分隔符、命令行参数解析不一致的小问题排查起来比较消耗热情。学习阶段减少环境摩擦就是提高学习效率。3. 安装全过程一步步跑通 GHCup GHC3.1 用 ghcup 安装最小可行步骤先给一个能在 99% 情况下降落的最小步骤清单。以 Linux/macOS 为例# 1. 下载并安装 ghcup官方脚本 curl --proto https --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh # 2. 脚本执行过程中会问是否自动添加 PATH、是否安装 HLS 等 # 新手建议全部选 yes直接回车这样后续不用再手动配置 # 3. 让 shell 加载新环境变量 source ~/.bashrc # 如果你用 zsh就 source ~/.zshrc # 4. 确认 ghcup 安装成功 ghcup --version # 5. 安装推荐版本的 GHCghcup 会自动选择推荐版本 ghcup install ghc recommended # 6. 安装 cabal ghcup install cabal recommended # 7. 设置默认版本这条很关键否则 shell 找不到 ghc 命令 ghcup set ghc recommended ghcup set cabal recommended # 8. 验证 ghc --version cabal --version如果一切顺利你会看到类似这样的输出The Glorious Glasgow Haskell Compilation System, version 9.6.5 cabal-install version 3.10.1.0 compiled using version 3.10.1.0 of the Cabal library3.2 Windows 下的安装细节Windows 下用官方 PowerShell 脚本# PowerShell 以管理员身份运行 Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 Invoke-Command -ScriptBlock ([ScriptBlock]::Create((Invoke-WebRequest https://get-ghcup.haskell.org -UseBasicParsing)))Windows 安装过程会多几个交互选项是否安装 MSYS2建议选 yes它是 Windows 下的 Unix 工具环境后续很多包的编译依赖它。是否安装 HLS建议选 yes编辑器补全要靠它。是否将 ghcup 的目录加到 PATH必须选 yes。装完重新打开终端命令行输入ghc --version测试。3.3 等下载时你需要知道的事ghcup 下载 GHC 时实际上是在下载一个预编译的压缩包体积大概在 200~300MB 之间具体取决于平台。如果你在内网或网络环境一般可以用镜像加速比如设置 ghcup 的下载源。在~/.ghcup/config.yaml里可以配置url-source但新手不需要折腾这个默认源一般都能跑完。下载慢还有另一个物理原因GHC 本身是个大编译器包含了很多架构相关的东西。第一次安装等个几分钟很正常别以为卡住了终止线程。3.4 非 ghcup 的备选Stack 一键安装如果你不想记那么多命令Stack 也提供了一键安装脚本。Stack 在装完自己之后第一次运行stack setup时也会自动下载匹配版本的 GHC 并内部管理对新手来说反而更“无脑”一点。# Linux/macOS curl -sSL https://get.haskellstack.org/ | sh # 创建并运行一个项目 stack new my-project cd my-project stack setup stack build stack exec my-project-exeStack 的优势是项目级隔离每个项目锁定各自使用的 GHC 版本团队协作时不会因为个人环境差异导致构建失败。我个人的经验是如果你准备认真学 Haskell、后面可能会参与开源项目那 ghcup cabal 的组合更接近社区的主流玩法如果你只是想快速跑个 demo、不想关心版本细节Stack 更省心。两条路殊途同归选一条深入即可。4. 编辑器选型与 HLS 配置写第一行代码之前4.1 VS Code新手最稳妥的选择老老实实说如果让我推荐一个最适合新手的 Haskell 编辑器我还是会提名 VS Code。原因很简单HLS 在 VS Code 上集成得最成熟插件安装向导在官方文档里写得也很直白。插件名是Haskell开发者是 haskell.org 官方组织。在 VS Code 扩展商店里搜“Haskell”就会出现直接安装。装完后它会自动定位你系统里的 HLS。如果你按前面的步骤通过 ghcup 安装了 HLS那基本上零配置就能用。打开一个.hs文件你会看到输入时实时显示类型签名这是 Haskell 开发最爽的一个体验。错误信息直接标红波浪线。支持跳转定义、查看所有引用。自动补全模块导出/导入。4.2 Neovim / Emacs 等其他选择用 Neovim 的话LSP 配置方式是通过内置的vim.lsp客户端连接 HLS。这里给一个最小的配置片段如果你用 lazy.nvim 插件管理器return { { neovim/nvim-lspconfig, opts { servers { haskell { settings { haskell { formattingProvider ormolu, }, }, }, }, }, }, }用 Emacs 的话通过lsp-mode或eglot连接 HLS配合haskell-mode使用。具体配置会复杂一点有兴趣的读者可以在官方 HLS 文档里找到 Emacs 的配置示例。4.3 HLS 不工作的排查思路HLS 启动失败的典型症状VS Code 右下角提示 “Haskell language server is not running”。这时候按顺序排查终端跑一下haskell-language-server --version看命令是否存在。如果命令不存在回 ghcup 跑一次ghcup install hls recommended再ghcup set hls recommended。如果版本匹配也没问题但还不行看 HLS 的日志文件。实际工作中最常见的两个原因是 GHC 版本与 HLS 版本不匹配或者项目路径里有中文/空格导致 LSP 无法正常解析。强烈建议来自踩坑新手期项目路径一律只用英文、不要带空格。5. 第一个 Haskell 程序从 GHCi 到编译运行5.1 GHCi交互式环境试水GHCi 是 Haskell 的交互式解释器类似 Python 的 REPL。在终端里输入ghci就能进入。先玩几个最基本的表达式Prelude 1 2 3 Prelude Hello, World Hello, World Prelude :t () () :: Num a a - a - a这里特别注意一下:t命令——它用来查看表达式的类型。Haskell 里一切皆有类型这是理解这门语言的钥匙。比如() :: Num a a - a - a读作()的类型是“对于所有Num类型的a接受一个a和一个a返回一个a”。这种读法要尽快适应。一个新手最容易用错的命令是:q很多人想退出 GHCi 时会输exit或quit全都不识别。正确姿势是:quit或:q。记不住就死死记住这一条真的能避免很多无谓的卡壳。5.2 编写并运行一个真正的程序文件GHCi 适合临时试东西但真正写程序还是要建.hs文件。我们写一个经典的程序顺便展示 Haskell 的核心特性-- hello.hs module Main where -- 带有类型签名的函数定义 greet :: String - String greet name Hello, name ! main :: IO () main do putStrLn Whats your name? name - getLine putStrLn (greet name)这个程序展示了四个关键概念类型签名greet :: String - String表示greet接收一个字符串返回一个字符串。纯函数与 IO 的分界greet是纯函数它不读取任何外部状态同样的输入必然得到同样的输出。而main的类型是IO ()表示这是一个会产生 IO 副作用读用户输入、打印到屏幕的动作。do语法块虽然 Haskell 本身是纯函数式语言但处理 IO 时必须依赖do语法块这背后是 Monad 的概念现在先不用深究。-绑定name - getLine表示执行getLine这个 IO 动作并把结果“取出”绑定给name。-并不是简单的赋值它是从“执行动作”到“拿到纯值”的桥梁。运行方式有三种# 方式一用 runhaskell 直接运行不编译适合快速测试 runhaskell hello.hs # 方式二交给 ghc 临时编译并运行 ghc -o hello hello.hs ./hello # 方式三在 GHCi 里加载 ghci hello.hs *Main main我用ghc -o hello hello.hs编译时特意加了个-o参数来指定输出文件名否则默认会生成一个叫helloLinux/macOS或hello.exeWindows的可执行文件。编译完成后目录里会多出hello和hello.hi、hello.o两个中间文件删掉不影响程序只影响重新编译的效率。第一次看到这些文件不用慌它们就是界面文件.hi和对象文件.o。5.3 消灭第一个编译错误的心法如果你是完全的新手下面这种报错几乎一定会遇到hello.hs:8:13: error: • Couldnt match expected type ‘String’ with actual type ‘IO String’ • In a stmt of a do block: name - getLine这句话翻译过来是getLine的类型是IO String你把它绑定给name但后续使用greet name时要求name是纯的String。解决办法的关键在于理解-已经把IO String里头的String提出来了所以这里的报错实际不是发生在绑定那行而是发生在其他地方。这类错误排查时有个心法看报错位置更要看报错类型。Haskell 的类型错误信息通常比大多数语言精确很多。它告诉你的“期望类型expected type”和“实际类型actual type”差异往往是定位根因的钥匙。不要在没理解类型是什么之前就去网上抄答案那会让你养成瞎猜的习惯。6. 新建项目用 Cabal 搭建标准工程结构6.1 为什么建议从第一课就用 Cabal 项目很多入门教程会让新手直接单文件跑这样确实简单但到了第二、第三课开始引入依赖库时单文件模式马上就不够用了。Cabal 是 Haskell 官方的“标准项目管理工具”学习曲线前期稍微陡一点但早用早享受。创建一个新项目cabal init --interactive # 交互式初始化 cabal init --non-interactive # 也可以用非交互模式但会生成一堆默认值交互式初始化会问几个问题项目名称输入一个合法的包名不能有大写字母和空格。版本号默认 0.1.0.0 就行。许可证选一个比如 BSD-3-Clause或者直接选 AllRightsReserved学习项目无所谓。是否生成测试套件选 yes方便以后练习写测试。源目录默认src目录保持默认。生成之后项目结构长这样my-project/ ├── src/ │ └── Main.hs ├── my-project.cabal ├── cabal.project ├── CHANGELOG.md ├── LICENSE └── Setup.hs6.2 在 Cabal 里配置依赖打开.cabal文件找到executable那一节你会看到executable my-project main-is: Main.hs build-depends: base ^4.17 hs-source-dirs: src default-language: Haskell2010build-depends是核心它声明了这个可执行文件的依赖包。例子里的base ^4.17表示“使用 base 库的 4.17.x 系列”。base 库是 GHC 自带的包含 Prelude预加载的标准库每个 Haskell 程序都隐式依赖它。如果我们需要 JSON 解析库aeson就把它加进去build-depends: base ^4.17, aeson ^2.1然后在终端跑cabal buildCabal 会自动解析依赖、下载库、编译这个过程可能需要一些时间因为会拉取传递依赖。6.3 一个小坑镜像源与依赖下载在国内网络环境下HackageHaskell 的中央包仓库有时下载很慢这会让cabal build变成漫长的折磨。此时可以在~/.cabal/config文件里配置镜像。查找文件里的repository hackage.haskell.org段落修改如下repository hackage.haskell.org url: https://HackageMirrorURL secure: True镜像地址的完整写法以你实际能访问到的为准。这类个人经验供参考万一真的访问不动官方源搜索引擎搜“Hackage 镜像”很快就有答案。我自己实测下来国内网络环境下使用镜像后cabal build的速度从经常超时变成了几分钟内稳定跑完。7. 新手最容易踩的五个坑7.1 路径含中文或空格导致 HLS 不工作这个问题在前面提过一嘴但值得单独放到坑名单里。VS Code 打开项目路径如果带了中文HLS 的连接偶尔会有路径解析错误GHCi 加载模块也可能出问题。解决方案只有一个项目目录用纯英文、小写字母、不用空格。我的 macOS 用户名是中文拼音所以吃过好多次这个亏后来索性在主目录下建了一个~/haskell专门放实验项目。7.2 GHC 版本与包版本不匹配cabal install some-package报Package has never been built或者cannot meet PVP之类的问题大多数是因为包的版本与 GHC 版本冲突。Cabal 的依赖解析机制非常严格它要求所有包版本满足约束条件。新手这时候别硬装比如用--force-reinstalls那是大人物的武器不是新手的工具。正确的处理是让 Cabal 自主选择一套可行的依赖集合cabal update cabal build --constraint some-package x.y.z或者直接cabal build让 Cabal 告诉你哪个版本冲突了再按提示调整。Haskell 的 PVP 规则a.b.c.d版本号中首位增加意味着 API 破坏性变化在初期会保护你但前提是你别用--force去破坏它。7.3 被pure和IO搞晕看到pure或return时很多 Haskell 新人会误以为它们是流程控制的关键字比如退出函数。实际上在 Haskell 里return只是一个把纯值“包”进 Monad 的普通函数main :: IO () main do let x 5 return x -- 这一行的结果直接被丢弃了程序没有输出我一开始学的时候写过这种代码怎么运行都没输出怀疑了半天人生。后来才理解return不是“返回并结束”而是构造一个值为x的IO Int动作。如果想要打印得print x或者putStrLn (show x)。把return当成一种“包装动作”而非“提前退出”对理解 Haskell 的 IO 模型至关重要。7.4 死磕惰性求值的顺序陷阱Haskell 默认惰性求值lazy evaluation这意味着表达式默认不立即计算而是在需要时按需计算。这给 IO 的顺序带来一个微妙的陷阱main :: IO () main do putStrLn Step 1 let x expensiveComputation putStrLn Step 2 print x这个程序输出顺序是Step 1 Step 2 然后才执行 expensiveComputation 并打印结果因为let x ...绑定了一个“未求值的表达式”而不是“算好的值”。x只有在print x需要它的值时才被真正求值。这个特性在纯计算里是优化神技但在新手期容易造成困惑。7.5 分组混乱布局规则引发的缩进报错Haskell 用缩进layout来代替花括号表示代码块。这意味着main :: IO () main do putStrLn Hello name - getLine putStrLn (Hi, name)do后的每个语句必须对齐在同一列的缩进位置。如果某个语句多缩进四格GHC 会认为它是一个嵌套代码块的开始立刻报parse error。这个问题我在前两周几乎每隔几个文件就犯一次。排查方法很简单看报错行和上一行是否同一缩进级别以及do关键字后是否忘了换行。8. 从安装到能用这份 checklist 请收好结尾之前把整篇的核心步骤整理成清单方便你照着走。这是我几次在干净机器上装 Haskell 总结出来的按顺序执行基本不会出错。安装前确认系统依赖Linux 装 build-essentialmacOS 装 Command Line ToolsWindows 装 VS Build Tools。安装 ghcup运行官方脚本交互选项全部选 yes。安装 GHC 和 Cabalghcup install ghc recommended然后cabal update。安装 HLSghcup install hls recommended装完验证haskell-language-server --version。配置编辑器VS Code 装 Haskell 插件打开一个空的.hs文件测试补全是否生效。跑交互式测试在 GHCi 里输入几个表达式确认类型查询:t好用。写第一个文件复制上文hello.hs在项目目录用runhaskell hello.hs直接跑一版。用 Cabal 建项目cabal init --non-interactivecabal build跑一遍标准构建流程。安全检查确认所有项目路径里没有中文和空格。9. 下一步学什么安装试用只是第一步。经过这一篇你已经能够运行表达式、编译程序、构建 Cabal 项目了。这已经超过了 60% 只是在网上看过教程的人。接下来的学习路径我的建议是按这个顺序推进类型系统基础代数数据类型、模式匹配、类型类。这是 Haskell 最强大的部分也是最容易理解“为什么会有函数式编程”的起点。纯函数与递归思维忘掉 for 循环用迭代器和递归解决问题。这个过程很痛苦但价值极高。函子、应用函子、单子这三个概念被誉为 Haskell 的“三大件”。别急着一口气全搞懂先在代码里用起来用熟之后回头再看理论。常用库的实践text高效字符串、containersMap/Set/Sequence、aesonJSON 解析、http-conduitHTTP 请求、optics或lens复杂记录访问。真实项目练手写一个命令行工具、写一个小型 web 服务用 servant 或 scotty用真实需求逼自己查 API 文档。每学会一个新概念就回到 GHCi 里亲手验证一遍这是我在 Haskell 学习路上最高效的方法。别怕写错编译器就是你的导师——它会用精确的类型错误告诉你哪里想歪了。写到这里我想起自己刚装完 Haskell 时盯着ghci提示符发呆的场景满屏英文一个程序都写不出来前面的空白仿佛在嘲讽我。但在跑了几天:t之后那种“一个类型能被语言自己推出来”的感觉真的会上瘾。希望这一篇搭建环境的小文章能帮你跨过最枯燥的第一道门槛早点体验到这种乐趣。现在打开终端输入ghci亲手敲一行map (1) [1,2,3]看看结果是什么。这是你 Haskell 之旅的第一个指令玩得开心。