在没有 cabal 的情况下编译具有外部依赖项的 haskell 脚本

Compiling a haskell script with external dependencies without cabal

我对 Haskell 比较陌生,我意识到我可能在这里逆流而上,但尽管如此,我还是要问:

假设我有一个简短的 Haskell 脚本:

import Data.List.Split (splitOn)

main :: IO ()
main = do
  let orders = splitOn "x" "axbxc"
  putStrLn $ head orders

如果我只使用标准函数,我可以用 ghc <script.hs> 编译它。因为我依赖split包提供splitOn功能,编译失败

现在,我可以毫不费力地使用 project.cabalSetup.hs 文件设置一个 cabal 项目,以便实际编译它。但是,对于独立脚本来说,这感觉像是很多额外的样板文件。

那么,有没有办法针对某些外部包编译单个 .hs 文件? [=17 可以完成类似于 Python 中的内容=]、“将包安装到解释器中”,即有没有办法将额外的包安装到“ghc”中,这样​​我例如只需要提供一些额外的链接标志到 ghc?

如果你使用Stack, the simplest way to do this is to write a ‘Stack script’, which is a Haskell file with a description of the required packages in the first line (really an invocation of stack specifying the appropriate command line arguments). An example (slightly modified from the docs):

$ cat turtle-example.hs
-- stack --resolver lts-6.25 script --package turtle
{-# LANGUAGE OverloadedStrings #-}
import Turtle
main = echo "Hello World!"
$ stack ./turtle-example.hs
Completed 5 action(s).
Hello World!
$ stack ./turtle-example.hs
Hello World!

这个脚本使用了turtle包;当 运行 时,Stack 下载并构建此依赖项,之后它在脚本中可用。 (注意第二次是运行,turtle已经建好了,不需要再重新建。)

碰巧,Stack 中的--package 命令并不局限于脚本。它也可以与其他 Stack 命令一起使用!例如,要编译你的程序,你应该能够 运行 stack ghc --resolver lts-16.27 --package split -- -ghc-options your-program-name.hsstack ghci --package split 会给你一个 GHCi 提示符,你可以 import Data.List.Split.

(注意:这个答案侧重于 Stack 而不是 Cabal,仅仅是因为我不太了解 Cabal。但是,我相信所有这些也可以使用 Cabal 来完成。例如,我知道 Cabal与我上面提到的 Stack 脚本非常相似,虽然我现在不记得语法了。)

编辑:参见了解如何使用 Cabal 执行此操作。

您可以通过 cabal install --lib split 安装到当前用户的默认环境。然后该包应该可用于 ghc 和 ghci 而无需任何特殊选项。

更多信息在 this section in the Cabal manual 的底部。它使用的 v2 命令现在是默认的,所以如果你有一个相当新的 cabal,你可以只使用 install 而不是 v2-install.

中 Stack 脚本的 Cabal 等价物是:

#!/usr/bin/env cabal
{- cabal:
build-depends: base
            , split
-}

import Data.List.Split (splitOn)

main :: IO ()
main = do
  let orders = splitOn "x" "axbxc"
  putStrLn $ head orders

脚本可以运行加上cabal run,或者直接给它执行权限。如果需要,可以像往常一样将版本范围添加到脚本顶部的 build-depends

(请注意,如果没有 Cabal,这实际上不是一个解决方案,因为单独使用 GHC 这样做,即使可能,也不值得麻烦。无论如何,它肯定避免了需要多个样板文件。)

我认为这是 Haskell 中包管理之战的典型切入点。不是没有足够的建议,而是有太多的建议,每个都有自己的警告和假设。为了splitOn而攀登那座山对新手来说就像他们做错了一样。

在尝试每个排列后花费了太多时间,我在这里整理了很好的答案,以及来自其他地方的许多其他答案,将它们用于测试,并总结了结果。完整的写法是 here.

解决方案的相关摘要是:

  1. 全局安装
    • 您仍然可以使用 cabal install --lib the-package 进行全局安装。
  2. 将 Stack 用作 运行 命令
    • 可以直接使用栈,例如:stack exec --package containers --package optparse-generic [...and so on] -- runghc hello.hs
  3. 创建 Stack 项目
    • 货真价实。 运行 stack new my-project hraftery/minimal,将代码放在 Main.hs 中,将依赖项放在 my-project.cabal 中(也许 stack.yaml - 查看文章),然后 运行 stack build 自动拉取并构建所有依赖项。
  4. 使用 Stack 脚本
    • 使您的 Haskell 文件本身成为可执行的 Stack 脚本。将 -- stack --resolver lts-6.25 script --package the-package 添加到 .hs 文件的顶部并设置其可执行位。

对于我的 Edit/Test/Run 工作流程(例如分别使用 VS Code、GHCi 和 GHC),我发现在实践中什么是有效的非常清楚。总结:

  1. 在一片沮丧中,全局安装 非常适合我所知道的您的用例。
  2. 全局安装没有意义的地方(例如,用于管理依赖版本或可移植)从我的 minimal 模板开始的 Stack 项目 是一个平滑的过渡到更复杂和流行的方法。