Emakso: Haskela Programado
Oni ne povas nomi sin pacema krom se oni estas kapabla de granda perforto. Se oni ne estas kapabla por granda perforto, oni ne estas pacema, oni estas nenoca.
—Stef STARKGARYEN

Enhavotabelo
Enkonduko
Haskelo estas bela programlingvo. Mi simple diru tion. Ĝi estas unu el tiuj programlingvoj kiu estis desegnite eleganta kaj potenca. En la alia flanko de la ĉielarko estas Lispo kaj en la alia flanko estas Haskelo. Ne estas nur pro ĝi estas pure funkcia, statike tipigita, maldiligente taksita, potence realigas tipklasajn, riĉa per esplorado, kaj havas aktivan komunumon. Estas pro ĝi havas ĉion.
Sed per Haskelo, aferoj estas malsimilaj. Oni rigardas aferojn el malsama perspektivo. Oni iras ekster la komfortaj zonoj. Oni elektas Haskelon ĉar oni ne nur deziras fariĝi pli bona programisto, sed oni ankoraŭ deziras fariĝi pli bona pensanto.
En ĉi tiu artikolo, mi parolos kiel mi agordas mian propran Haskelan programadan medion. Kompreneble, Emakso estas nur unu parto da ĝi. Se mi metis ĉiom da partoj kiujn mi bezonas, do la titolo jam fariĝos tre longa. Mi uzas GHC, Stack, Doom-emakso, Nix, kaj Direnv.
Estu konscie, ke ĉi tiu gvidilo estas tre opinia. La komandoj kaj instrukcioj kiuj mi uzas estas fajne desegnita por mia uzado.
Doom
Dum la pasintaj ses jaroj, mi jam neŝanĝiĝeme uzis Doom-emakso kiel mia ĉefa redaktilo anstataŭ vanila emakso. Doom estas rapida, fidebla, kaj venas kun baterioj. Malgraŭ promociite kiel emakso por komencantoj, ĝi ankaŭ funkcias bonege por altgradaj uzantoj. Mi ankaŭ provis uzi je Spacemacs sed ĝi ne estis sufiĉe stabila.
Unue, ni devas havi subtenon por Haskelo en ~/.doom.d/init.el. Trovi la :lang-sekcion, kaj metu la jenan:
:lang
…
(haskell +lsp)
…
Mi ankaŭ ŝatas ebligi ligaturojn, por ke la signoj aspektu pli bene. Trovi la :ui-sekcion, kaj metu la jenan:
:ui
…
(ligatures +extra)
…
Tiam, ni devas aldoni subtenon por direnv, por ke malfermu .hs-dosierojn ankaŭ ŝargas direnv. Malfermu la dosieron ~/.doom.d/config.el kaj metu la jenan:
(use-package! direnv
:config
(direnv-mode)
(setq direnv-always-show-summary nil))
Tiam, ni devas diri al haskelo-reĝimo uzi GHCi kiu venas kun Stack. Malfermu la dosieron ~/.doom.d/config.el, denove, kaj metu la jenan:
(after! haskell
(setq haskell-process-type 'stack-ghci))
Fine, reŝargi la agordon ĉe la komandlinio per:
doom sync
aŭ ene Doom mem:
SPC h r r
Direnv
direnv estas eta plaĉa ilo kiu igas onin krei mediajn variablojn unikaj al dosierujoj. La kreado de ĉi tiu variabloj okazas kiam oni ŝanĝas la dosierujo en kiu direnv estas ŝaltita.
Oni povas instali direnv per aldoni ĝin al configuration.nix aŭ oni povas instali ĝin por la loka profilo. Por simpleco, ni faru la posta:
nix profile install nixpkgs#direnv nixpkgs#nix-direnv
Tiam, ni kreu la supran agorddosieron de direnv. Metu la jenan en la agordo ~/.direnvrc:
use_flake() {
watch_file flake.nix
watch_file flake.lock
eval "$(nix print-dev-env)"
}
source $HOME/.nix-profile/share/nix-direnv/direnvrc
Stack
Estas nun la tempo por krei novan projekton. Ni faru ĝin per Stack ĉar ĝi estas stabila, havas sanajn implicitajn agordojn, kaj integriĝas bone kun Hackage kaj Stackage.
Ni antaŭsupozu, ke ni estas en la hejma dosierujo de uzanto john—/home/john/:
nix run nixpkgs#stack new foo
Kiam la komando finas ruli, la dosierujo /home/john/foo/ enhavas la jenajn dosierojn:
foo
├── app
│ └── Main.hs
├── CHANGELOG.md
├── foo.cabal
├── LICENSE
├── package.yaml
├── README.md
├── Setup.hs
├── src
│ └── Lib.hs
├── stack.yaml
└── test
└── Spec.hs
Nun estas bona tempo por aldoni la jenan kodon al la Stack-agorddosiero, ~/.stack/config.yaml:
notify-if-nix-on-path: false
Nix
Krei reprodukteblan medion, ni nun plenigos la dosierujon foo per dosieroj kiuj integrigas ĉion kune. Kreu la jenajn dosierojn ene la dosierujo foo/ kiu estis tuj kreita. Pli grave, ni uzos nix-flokojn.
Unue estas flake.nix:
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs =
inputs@{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells = import ./shells.nix { inherit nixpkgs pkgs; };
}
);
}
Sekve, estas shells.nix:
{
nixpkgs,
pkgs,
...
}:
with pkgs;
let
comPkgs = [
which
rlwrap
];
addComPkgs = l: l ++ comPkgs;
inherits = {
inherit nixpkgs pkgs addComPkgs;
};
import' = m: import m inherits;
in
rec {
haskell = import' ./haskell.nix;
default = haskell;
}
Tiam, haskell.nix:
{
nixpkgs,
pkgs,
addComPkgs,
...
}:
with pkgs;
let
extraPkgs = [
];
haskellPkgs = haskell.packages."ghc9103";
stackWrapped = symlinkJoin {
name = "stack";
paths = [ stack ];
buildInputs = [ makeWrapper ];
postBuild = ''
wrapProgram $out/bin/stack \
--add-flags "\
--nix \
--system-ghc \
--no-install-ghc \
"
'';
};
mainPkgs = [
haskellPkgs.ghc
haskellPkgs.haskell-language-server
stackWrapped
fourmolu
];
in
mkShell {
buildInputs = addComPkgs mainPkgs ++ extraPkgs;
LD_LIBRARY_PATH = lib.makeLibraryPath mainPkgs;
}
Tiam, haskell.nix:
{
nixpkgs,
pkgs,
addComPkgs,
...
}:
with pkgs;
let
extraPkgs = [
];
haskellPkgs = haskell.packages."ghc9103";
stackWrapped = symlinkJoin {
name = "stack";
paths = [ stack ];
buildInputs = [ makeWrapper ];
postBuild = ''
wrapProgram $out/bin/stack \
--add-flags "\
--no-nix \
--system-ghc \
--no-install-ghc \
"
'';
};
mainPkgs = [
haskellPkgs.ghc
haskellPkgs.haskell-language-server
stackWrapped
fourmolu
];
in
mkShell {
buildInputs = addComPkgs mainPkgs ++ extraPkgs;
LD_LIBRARY_PATH = lib.makeLibraryPath mainPkgs;
}
Fine, kreu .envrc:
nix_direnv_manual_reload
use flake
if [[ -f .env ]]; then dotenv .env; fi
Diversaĵoj
Dum oni estas en la foo/ dosierujo, oni devas ebligi direnv:
direnv allow
kaj oni devas krei la ŝlosilan dosieron por Nix:
nix-direnv-reload
En VTI, ni ŝatas uzi la implicitajn valorojn de Ormolu de uzi du spacetojn por krommarĝenado. Ni uzas ĉi tiun fourmolu.yaml agordo por niaj bezonoj:
indentation: 2
column-limit: none
function-arrows: trailing
comma-style: leading
import-export-style: diff-friendly
import-grouping: legacy
indent-wheres: false
record-brace-space: false
newlines-between-decls: 1
haddock-style: multi-line
haddock-style-module: null
haddock-location-signature: auto
let-style: auto
in-style: right-align
if-style: indented
single-constraint-parens: always
single-deriving-parens: always
sort-constraints: false
sort-derived-classes: false
sort-deriving-clauses: false
trailing-section-operators: true
unicode: never
respectful: true
fixities: []
reexports: []
local-modules: []
Testado
En ĉi tiu punkto, la dosierujo foo/ aspektos kiel jene:
foo
├── app
│ └── Main.hs
├── CHANGELOG.md
├── flake.lock
├── flake.nix
├── foo.cabal
├── fourmolu.yaml
├── haskell.nix
├── LICENSE
├── package.yaml
├── README.md
├── Setup.hs
├── shells.nix
├── src
│ └── Lib.hs
├── stack.yaml
└── test
└── Spec.hs
Kiam oni ŝanĝiĝas al la dosierujo ~/foo/, one estas sciigita ke direnv ŝarĝiĝis.
cd ~/foo
Kiam oni malfermas la dosieron ~/foo/app/Main.hs per Doom:
SPC f f ~/foo/app/Main.hs RET
oni ankaŭ estas sciigita ke direnv ŝarĝiĝis.
Por kontroli ke la haskela programo ja muntas, rulu:
stack build
Tenu en la kalkulo, ke ĉi tiu stack duumdosiero estas tiu kiu loĝas ene la Nix-medio precizigita de flake.nix. Kiam la muntado finiĝas, rulu ĝin per:
stack exec foo-exe
Finrimarkoj
Se oni jam estas Nix-uzanto, oni trovas la instrukciojn relative facilaj por sekvi. Tamen, se oni ne ankoraŭ estas kutima al Nix, oni devas investi tempon por lerni ĝin, ĝia ekosistemo, kaj tio kio igas ĝin unika.
Mi ankoraŭ devas trovi pli bonan integritan programadan medion por Haskell pli ol tio kion mi priskribis ĉi-supre. La iniciala agordo povas iĝi senkuraĝiga, sed post kiam oni trairas la inicialan branĉbarileton, ĉio iĝas facila.