在这篇文章中,我们将看一下堆栈。这是有关Haskell入门的系列文章中的第三篇。第一篇文章探讨了设置Haskell开发环境的基础知识,而第二篇文章则是为初学者介绍Cabal的基础。这篇文章介绍了stack,它是cabal-install的替代构建工具。
这篇文章将快速浏览针对初学者的堆栈的基本方面。它不仅会列出应执行的命令,还将尝试提供尽可能多的上下文说明。目的是对堆栈进行介绍,该指南中没有要记住的命令,但可以帮助初学者更好地理解堆栈,以便他们可以自己进一步探索该工具。
在工作和构建Haskell程序时,了解一些基本术语和概念很重要。这包括理解当我们说诸如Cabal之类的东西时的含义,模块,库,包之间的区别等。这些术语已经在上一篇文章中进行了解释,因此,请花点时间阅读澄清,以免再次在此处重复。初学者的Cabal旋风之旅文章中的“条款”部分。
需要指出的关于软件包和项目概念的一件事是,堆栈通过称为stack.yaml的文件为项目提供配置,而软件包通过package.yaml文件或.cabal文件进行配置。在创建新项目部分中将更深入地介绍这些文件。
stack是Haskell的构建工具。它是cabal-install的替代方法,但仍使用cabal的软件包格式和Cabal的库。它非常注重可复制的构建计划。它的构想是解决曾经困扰cabal-install的依赖关系问题(通常称为cabal hell)。它通过提供保证始终编译的精选程序包集来实现此目的。
应当强调的是,过去困扰了cabal-install的cabal地狱问题已基本解决。该解决方案采用了与采用的堆栈不同的方法。它使用了与Nix中类似的方法。这一点的细节并不重要,只是使用cabal-install或stack成为优先选择和UX选择的问题。
堆栈还带有一些可能会使初学者感到困惑的活动部件和术语。在本节中,将在转至如何安装堆栈之前解释这些术语。
Hackage是Haskell的开源软件中央软件包存档。任何人都可以从Hackage上传(也可以下载)软件包,因此它包含不保证兼容的不同版本的软件包。然后,精选的软件包集是来自Hackage的软件包的子集,定期对其兼容性进行测试。堆栈利用策展的软件包集的思想来解决不兼容依赖项的问题。
Stackage是Haskell软件包的开源软件的中央软件包存档,已对其进行了策划和兼容性测试。因此,堆栈使用的策划包集托管在Stackage上。 Stackage在fpcomplete由人们带头,但是仍然是社区的努力,因此任何人都可以提交软件包以包含在Stackage上托管的精选软件包集中。
不难想象,使用Stackage上托管的这些精选软件包集时需要版本控制。其中特定版本唯一标识特定策展的软件包集及其包含的软件包。在堆栈中,策展的软件包集的版本被称为或指定为解析程序。
解析器唯一地标识GHC版本和包含在软件包集中的其他Haskell软件包。 LTS(长期支持)解析器版本号可以识别经过彻底测试并可以长期维护的软件包,而夜间解析器则可以识别仍在不断变化的软件包。可用的LTS解析器可以在这里找到,而夜间解析器可以在这里看到。例如lts-16.22是一个LTS解析器,而nightly-2020-11-19是一个夜间解析器。
基于其名称,将解析器视为执行解析行为的软件可能会很诱人。更好的方法是将解析器视为标识符或标签。这是引用特定策展的软件包集的一种方式。
如澄清术语部分所述,Haskell使用cabal软件包格式,cabal软件包格式是一种基于文本的键值格式,用于描述Haskell软件包。 hpack是cabal软件包格式的替代格式。它使用yaml代替了自定义的键值基于文本的格式。
因此,虽然cabal软件包格式使用在以.cabal扩展名结尾的文件中指定的基于键值的自定义自定义格式,但hpack使用yaml并在package.yaml文件中指定了软件包描述。 stack内置了对hpack的支持。这意味着stackcan可以解析hpacks的package.yaml并使用它生成.cabal文件。
总之,您可以将堆栈作为Haskell构建工具进行查看,该工具利用由解析器标识并托管在Stackage上的精选软件包集,并内置支持hpack作为替代软件包规范格式。
为了确认堆栈已正确安装,请运行stack --version,将给出类似以下的输出:
请注意,使用堆栈时无需单独安装GHC。 stackhandles获取创建的每个项目所需的GHC版本。这也是堆栈与cabal-install不同的方法之一,而cabal-install需要单独下载和安装GHC。因此,stack不仅要解决依赖关系,编译Haskell代码,还要处理执行此操作所需的工具链的管理,并且GHC包含在工具链中。
现在我们已经安装了堆栈,接下来要做的就是创建一个新的Haskell项目。这样做很简单。要创建一个新项目,请运行stack new。
$ stack new firstproject下载模板" new-template"创建项目" firstproject"在firstproject /中...模板需要以下参数,但未提供:author-name您可以在/Users/schooloffp/.stack/config.yaml中提供它们,如下所示:templates:params:author-name:value或者您可以像这样传递每个参数:stack new firstproject new-template -p" author-name:value"模板需要以下参数,但未提供:author-email,author-name,category ,版权,github用户名您可以在/Users/schooloffp/.stack/config.yaml中提供它们,如下所示:模板:参数:author-email:值author-name:值类别:值版权:值github-用户名:valueOr您可以像这样传递每个参数:stack new firstproject new-template -p" author-email:value" -p"作者名:值" -p" category:value" -p"版权所有:值" -p" github-username:value"寻找用于初始化项目的.cabal或package.yaml文件。使用Cabal软件包:-firstproject /在18个快照中选择最好的快照... *匹配lts- 16.22选定的解析器:lts-16.22使用解析器初始化配置:lts-16.22考虑的用户软件包总数:1将配置写入文件:firstproject / stack.yaml所有完成的./Users/schooloffp/.stack/templates/new-template.hs文件: 3.72 KiB下载...
上面的命令将创建一个名为firstproject的目录,其中将包含项目的源文件。
上面的命令基于默认模板创建一个项目,该模板指示将要创建的默认文件和配置。应该注意的是,有可能将一个额外的选项与new一起传递,该选项指定堆栈应该用来创建新项目的模板。当模板选项被忽略时,默认情况下堆栈使用新模板模板。因此,堆栈新的firstproject与运行堆栈新的firstproject new-template相同。运行堆栈模板以查看可用的模板。
$ tree firstproject / firstproject /├──ChangeLog.md├──许可├──README.md├──Setup.hs├──app│└──Main.hs├──firstproject.cabal├──包。 yaml├──src│└──Lib.hs├──stack.yaml└──test└──Spec.hs
让我们暂停一下,谈谈运行stack new命令后生成的文件。
从名称中可以明显看出,这是一个Markdown文件,应使用它来捕获项目的修订历史记录。
这就是指定用于管理项目的许可证的位置,这在名称中也很明显。
如果需要在编译Cabal软件包时避开cabal-install或stack之类的工具以直接使用Cabal库的情况,则Setup.hs文件非常有用。 Setup.hs本质上是一个可运行的Haskell程序,可以对其进行进一步配置并用于编译Cabal软件包。
这是一个例外的用例。 Haskell的日常开发可能永远不需要Setup.hs,因此可以忽略甚至安全删除它。
Haskell软件包可以包含一个可执行文件(要运行)或一个库(要在编写Haskell代码时使用)。通常将生成可执行文件和库的源文件放在单独的目录中。使用stack new时,它将可执行文件放入名为app的目录中。 Main.hs是默认可执行堆栈生成的源文件,因此将其放在应用程序目录中。
这是cabal软件包格式文件。 stackmaintain这个文件:生成它并在必要时更新它,因此永远不需要直接更新此文件。
这是hpack软件包格式文件,已经被解释为cabal软件包格式的替代方法。这是您需要更新的文件,以提供元信息,例如依赖项,有关维护程序包的开发人员的信息,可以找到可执行文件和库的目录等。堆栈使用此文件作为输入来生成.cabal该项目的文件。这意味着firstproject.cabal是从package.yaml的内容生成的。
有关可在package.yaml文件中设置的属性的概述,请查阅hpack快速参考。
Haskell软件包可以包含一个可执行文件(要运行)或一个库(要在编写Haskell代码时使用)。通常将生成可执行文件和库的源文件放在单独的目录中。使用stack new时,会将库放入名为src的目录中。 Lib.hs是源堆栈生成的默认库,因此将其放在src目录中。
stack.yaml包含用于配置堆栈本身以及如何构建项目的内容。它包含诸如应该使用哪个版本的解析器版本,应该构建的软件包列表等信息。
在我们着手开始在堆栈中创建和构建项目之前,了解如何配置堆栈以及在何处可以找到各种配置文件非常重要。这些知识将有助于更好地了解在需要调整堆栈工作方式时要更改的配置以及在哪里可以找到它们。
stackwork的目的是一个项目,该项目是一个包含stack.yaml配置文件的目录。然后,一个stackproject包含一个或多个Haskell软件包,用一个或多个package.yaml(或相应的.cabal文件)的存在来描述。与托管在Hackage或Stackage上的软件包相比,这些软件包通常称为本地软件包。
堆栈既可以在项目环境中使用,也可以在项目外部使用。当在项目的上下文中使用时,堆栈将从包含stack.yaml的目录中执行。在项目外使用stackout意味着从没有stack.yaml`文件的位置执行stack。
因此,配置堆栈时应注意三个文件,其中包括:
< project_dir> /stack.yaml包含与本地程序包相关的配置,〜/ .stack / global-project / stack.yaml也包含与程序包相关的配置,但仅在堆栈在具有堆栈的目录外部执行的情况下才进行参考。 yaml文件。
〜/ .stack / config.yaml包含一个非项目配置,可用于更改stackdoes的工作方式。例如,应该使用哪个hpack二进制堆栈,或者将堆栈构建的可执行文件放在哪里。
换句话说,当从项目中执行堆栈时,将从< project_dir> /stack.yaml中选择配置以及〜/ .stack / config.yaml中指定的任何默认值。
当在项目外部执行堆栈时,堆栈仍然需要stack.yaml,因此〜/ .stack / global-project / stack.yaml中指定的堆栈与〜/ .stack / config.yaml中指定的默认值一起使用。
据说< project_dir> /stack.yaml和〜/ .stack / global-project / stack.yaml包含项目特定的配置选项,而〜/ .stack / config.yaml包含默认的非项目特定选项。
可能会问,在没有stack.yaml文件的目录之外可以执行哪些stackcommand?
一个很好的例子就是stack new,它创建了一个目录,将在其中创建stack.yaml文件。然后查阅〜/ .stack / global-project / stack.yaml中的配置(以及〜/ .stack / config.yaml中的默认配置)。
要查看项目特定配置选项的文档,请在此处查看。对于非项目配置,请在此处检查
为了开始使用堆栈,我们将看一下如何使用堆栈构建和运行可执行文件。为了使工作顺利进行,我们将编译并运行在运行stack new firstproject时创建的默认项目,因为它包含构建Haskell项目所需的所有必要文件。
$ stack build正在准备将GHC安装到一个隔离的位置,这不会干扰任何系统级别的安装.ghc-8.8.4下载。 ---已记录快照---
这将在项目中编译并生成可执行文件。为此,stack首先会下载该项目所需的GHC版本。这可以从初始日志片段中看到。这是堆栈的独特功能。 stack不仅有助于构建Haskell项目,而且还管理编译器版本,即将使用的GHC版本。
要运行上面生成的可执行文件,请在stack build命令完成后运行stack run
无需先运行堆栈构建即可执行堆栈运行命令。如果这样做,堆栈将确保首先构建以便生成将要运行的可执行文件。
到目前为止,我们已经有了一个包含可执行程序包的项目,我们可以对其进行构建和运行。接下来要看的是堆栈如何帮助依赖管理。
带有在Stackage上托管的精选软件包集的stackworks,因此,stack自然会咨询Stackage以解决外部第三方依赖性。由于Stackage是软件包的子集,因此可能会发生这样的情况,即所需的依赖关系不在Stackage上,而是在未整理的Hackage存储库中找到。对于这种情况,stackstill使得可以利用在Hackage上找到的软件包。在本节中,我们将了解如何包括来自Stackage和Hackage的依赖关系。
为了说明如何从Stackage中添加依赖关系到程序包,我们将修改在运行stack new时获得的程序包,并添加两个程序包。这些软件包是emojis,emoji实用程序和haskell-say,它使用Haskell徽标中的标注的ASCII艺术装饰打印到控制台的文本。
我们还添加了文本作为依赖项,以便能够从emojis包使用的Text转换为haskell-say使用的String。
现在,我们已经添加了这些依赖关系,我们可以使用它们了。更新Main.hs文件,如下所示:
{-#LANGUAGE OverloadedStrings#-}模块主要在其中导入Text.Emoji import HaskellSay(haskellSay)import Data.Text隐藏(map)main = mapM_(haskellSay。("您想要一些"<> )。解压缩)(emojiFromAlias" pizza")
现在尝试通过执行命令堆栈运行来构建和运行。这将失败,并显示类似以下错误消息:
$ stack runError:在构建构建计划时,遇到以下异常:在firstproject-0.1.0.0的依赖项中:haskell-say需要,但是堆栈配置没有指定的版本(最新匹配的版本是1.0.0.0),因为firstproject是构建目标。解决此问题的一些不同方法:*建议的操作:尝试将以下内容添加到/Users/schooloffp/delete/stack/firstproject/stack.yaml中的Extra-deps中:-haskell-say-1.0.0.0 @ sha256:654ed7ff571d62fb03dfda576aa0a89410fe403b0d84695b5151e3d026f33099,1330计划构建失败
问题是,Stack上没有haskell-say软件包。编辑package.yaml以包含表情符号和haskell-say时,stack能够解析所需的表情包,因为它包含在Stackage中,如此处所示。
这意味着即使通过在package.yaml中将haskell-say包指定为依赖项,堆栈这次仍将需要额外的帮助才能从Hackage检索haskell-say包,因为Stackage中没有该包。为此,使用stack.yaml中的extra-deps配置。
请注意,需要指定软件包的确切版本。也可以按照错误消息中的建议使用提交哈希来完成此操作。
现在将haskell-say-1.0.0.0作为额外的附加功能包括在内,再次执行堆栈运行应导致以下输出:
$ stack run构建`firstproject'的所有可执行文件一旦。在成功构建所有文件之后,将仅重建指定的可执行文件。配置(lib + exe)配置firstproject-0.1.0.0 ... firstproject>构建(lib + exe)firstproject-0.1.0.0的预处理库。.firstproject-0.1.0.0的构建库.. [1之2]编译Lib [2 of 2]编译Paths_firstprojectPreprocessing可执行文件' firstproject-exe&# 39; for firstproject-0.1.0.0 ..构建可执行文件' firstproject-exe' for firstproject-0.1.0.0 .. [1 of 2]编译Main [2 of 2]编译Paths_firstprojectLinking .stack-work / dist / x86_64-osx / Cabal-3.0.1.0 / build / firstproject-exe / firstproject-exe .. .firstproject>复制/注册/Users/schooloffp/delete/stack/firstproject/.stack-work/install/x86_64-osx/f7400eb5a06ef73a5cb417d56f25fb1b3c83d9c5e948a55a2c6f6c51f1bfd4fe/8.8.4/8.4/lib/x-x86_0.14中的安装库-Kbd7jwgcvpB4EvHQfLN8fa将可执行文件firstproject-exe安装在/Users/schooloffp/delete/stack/firstproject/.stack-work/install/x86_64-osx/f7400eb5a06ef73a5cb417d56f25fb1b3c83d9c5e948_6__1__________________________________________1。你要一些\ ____ _____________________________________________ / \ / \ / \ / _____ _____ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ----------- \ \ \ \ \ | \ \ \ \ \ --------- | / / / \ / / / \ \ ------- | /// ^ \ \ | // / / / \ \ \ ---- | / / / / \ \ / ____ / / ____ / \ ____ \
现在,我们的软件包只有一个可执行文件。 我们要做的下一件事是引入一个库。 为此,我们将把我们的项目变成一个,在执行时打印出用表情符号写的报价。 为此,我们进行了以下修改: 默认情况下,在执行stack new时添加source-dirs:src,它指向包含构成库的模块源代码的目录。 公开的模块:EmojiQuotes指示EmojiQuotes是一个库模块,应公开以在其他模块中使用。 下一步是在src目录中创建EmojiQuotes模块: 运行 ......