Add a more advanced haskell project
This commit is contained in:
parent
d53b887029
commit
72f23438b1
23
06-haskell-shellfor/README.md
Normal file
23
06-haskell-shellfor/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 06-haskell-shellfor
|
||||||
|
|
||||||
|
This example shows a Haskell dev shell built with `shellFor`.
|
||||||
|
|
||||||
|
It includes:
|
||||||
|
|
||||||
|
- a local package added to the Haskell package set,
|
||||||
|
- a dev shell derived from that package with `shellFor`, and
|
||||||
|
- a small test suite run by `nix flake check`.
|
||||||
|
|
||||||
|
Useful commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
cabal run
|
||||||
|
cabal test
|
||||||
|
|
||||||
|
nix build
|
||||||
|
./result/bin/mini-shellfor
|
||||||
|
|
||||||
|
nix run
|
||||||
|
nix flake check
|
||||||
|
```
|
||||||
25
07-haskell-deps/README.md
Normal file
25
07-haskell-deps/README.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 07-haskell-deps
|
||||||
|
|
||||||
|
This example shows a small Haskell project that uses external libraries from the package set.
|
||||||
|
|
||||||
|
It includes:
|
||||||
|
|
||||||
|
- a library that parses JSON with `aeson`,
|
||||||
|
- `text` and `bytestring` usage in the library and CLI,
|
||||||
|
- an executable under `app/`, and
|
||||||
|
- a test suite run by `nix flake check`.
|
||||||
|
|
||||||
|
Useful commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix develop
|
||||||
|
cabal run
|
||||||
|
cabal run -- '{"name":"flakes"}'
|
||||||
|
cabal test
|
||||||
|
|
||||||
|
nix build
|
||||||
|
./result/bin/mini-json '{"name":"flakes"}'
|
||||||
|
|
||||||
|
nix run . -- '{"name":"flakes"}'
|
||||||
|
nix flake check
|
||||||
|
```
|
||||||
18
07-haskell-deps/app/Main.hs
Normal file
18
07-haskell-deps/app/Main.hs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
module Main where
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Char8 as ByteString
|
||||||
|
import MiniJson.Greeting (greetFromJson)
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import System.Exit (die)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
let input =
|
||||||
|
case args of
|
||||||
|
[] -> "{\"name\":\"learner\"}"
|
||||||
|
firstArg : _ -> firstArg
|
||||||
|
|
||||||
|
case greetFromJson (ByteString.pack input) of
|
||||||
|
Left err -> die err
|
||||||
|
Right message -> putStrLn message
|
||||||
27
07-haskell-deps/flake.lock
generated
Normal file
27
07-haskell-deps/flake.lock
generated
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1776548001,
|
||||||
|
"narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
40
07-haskell-deps/flake.nix
Normal file
40
07-haskell-deps/flake.nix
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
# Builds a small Haskell program that uses external libraries from the
|
||||||
|
# package set, so the example shows how Cabal dependencies flow through Nix.
|
||||||
|
description = "A Haskell project with external dependencies";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ self, nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
inherit (pkgs) haskellPackages;
|
||||||
|
project = haskellPackages.callCabal2nix "mini-json" ./. { };
|
||||||
|
checkedProject = pkgs.haskell.lib.doCheck project;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
packages.${system}.default = project;
|
||||||
|
|
||||||
|
apps.${system}.default = {
|
||||||
|
type = "app";
|
||||||
|
program = "${self.packages.${system}.default}/bin/mini-json";
|
||||||
|
meta.description = "Run the Haskell JSON parsing example.";
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
haskellPackages.ghc
|
||||||
|
pkgs.cabal-install
|
||||||
|
pkgs.haskell-language-server
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
# `doCheck` runs the Cabal test suite, which exercises both valid and
|
||||||
|
# invalid JSON input through the library function.
|
||||||
|
checks.${system}.test-suite = checkedProject;
|
||||||
|
};
|
||||||
|
}
|
||||||
33
07-haskell-deps/mini-json.cabal
Normal file
33
07-haskell-deps/mini-json.cabal
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
cabal-version: 2.4
|
||||||
|
name: mini-json
|
||||||
|
version: 0.1.0.0
|
||||||
|
build-type: Simple
|
||||||
|
|
||||||
|
library
|
||||||
|
exposed-modules: MiniJson.Greeting
|
||||||
|
hs-source-dirs: src
|
||||||
|
build-depends:
|
||||||
|
aeson,
|
||||||
|
base >=4.14 && <5,
|
||||||
|
bytestring,
|
||||||
|
text
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
executable mini-json
|
||||||
|
main-is: Main.hs
|
||||||
|
hs-source-dirs: app
|
||||||
|
build-depends:
|
||||||
|
base >=4.14 && <5,
|
||||||
|
bytestring,
|
||||||
|
mini-json
|
||||||
|
default-language: Haskell2010
|
||||||
|
|
||||||
|
test-suite mini-json-test
|
||||||
|
type: exitcode-stdio-1.0
|
||||||
|
main-is: Main.hs
|
||||||
|
hs-source-dirs: test
|
||||||
|
build-depends:
|
||||||
|
base >=4.14 && <5,
|
||||||
|
bytestring,
|
||||||
|
mini-json
|
||||||
|
default-language: Haskell2010
|
||||||
20
07-haskell-deps/src/MiniJson/Greeting.hs
Normal file
20
07-haskell-deps/src/MiniJson/Greeting.hs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module MiniJson.Greeting where
|
||||||
|
|
||||||
|
import Data.Aeson ((.:), FromJSON (parseJSON), eitherDecodeStrict', withObject)
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as Text
|
||||||
|
|
||||||
|
data GreetingRequest = GreetingRequest
|
||||||
|
{ name :: Text
|
||||||
|
}
|
||||||
|
|
||||||
|
instance FromJSON GreetingRequest where
|
||||||
|
parseJSON = withObject "GreetingRequest" (\object -> GreetingRequest <$> object .: "name")
|
||||||
|
|
||||||
|
greetFromJson :: ByteString -> Either String String
|
||||||
|
greetFromJson input = do
|
||||||
|
request <- eitherDecodeStrict' input
|
||||||
|
pure ("hello, " ++ Text.unpack (name request) ++ ", from aeson")
|
||||||
14
07-haskell-deps/test/Main.hs
Normal file
14
07-haskell-deps/test/Main.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Main where
|
||||||
|
|
||||||
|
import qualified Data.ByteString.Char8 as ByteString
|
||||||
|
import MiniJson.Greeting (greetFromJson)
|
||||||
|
import System.Exit (die)
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main =
|
||||||
|
case
|
||||||
|
( greetFromJson (ByteString.pack "{\"name\":\"flakes\"}")
|
||||||
|
, greetFromJson (ByteString.pack "{\"missing\":\"name\"}")
|
||||||
|
) of
|
||||||
|
(Right "hello, flakes, from aeson", Left _) -> putStrLn "test passed"
|
||||||
|
_ -> die "unexpected parser result"
|
||||||
67
notes/009-haskell-dependencies.md
Normal file
67
notes/009-haskell-dependencies.md
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# Haskell Dependencies
|
||||||
|
|
||||||
|
This note covers `07-haskell-deps/`, which builds a small Haskell program that depends on external libraries from the Haskell package set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. What This Example Adds
|
||||||
|
|
||||||
|
The earlier Haskell examples stayed close to `base` and local modules.
|
||||||
|
|
||||||
|
This example adds three common Haskell libraries:
|
||||||
|
|
||||||
|
- `aeson` for JSON decoding,
|
||||||
|
- `text` for string data in the parsed value, and
|
||||||
|
- `bytestring` for the raw input passed into the decoder.
|
||||||
|
|
||||||
|
That makes the example useful for two reasons:
|
||||||
|
|
||||||
|
- it shows how Cabal `build-depends` entries become Nix build inputs automatically, and
|
||||||
|
- it demonstrates a more realistic program shape than a pure string concatenation example.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. The Important Point About Dependencies
|
||||||
|
|
||||||
|
The flake still uses the same Nix expression pattern as `05-haskell/`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
project = haskellPackages.callCabal2nix "mini-json" ./. { };
|
||||||
|
```
|
||||||
|
|
||||||
|
The external libraries are not listed again in `flake.nix`. They are declared in `mini-json.cabal`, and `callCabal2nix` translates that Cabal package description into a Nix derivation.
|
||||||
|
|
||||||
|
That is the main teaching point: Cabal remains the source of truth for Haskell package dependencies, while the flake decides how the package is exposed as a build, app, dev shell, and check.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. The Library Function
|
||||||
|
|
||||||
|
The library exposes one function:
|
||||||
|
|
||||||
|
```haskell
|
||||||
|
greetFromJson :: ByteString -> Either String String
|
||||||
|
```
|
||||||
|
|
||||||
|
It decodes a JSON object with a `name` field and returns a greeting string. The executable wraps that function in a small CLI, and the test suite checks both a valid input and an invalid one.
|
||||||
|
|
||||||
|
That keeps the example focused on dependency usage, not CLI design.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Commands to Try
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd 07-haskell-deps
|
||||||
|
|
||||||
|
nix develop
|
||||||
|
cabal run
|
||||||
|
cabal run -- '{"name":"flakes"}'
|
||||||
|
cabal test
|
||||||
|
|
||||||
|
nix build
|
||||||
|
./result/bin/mini-json '{"name":"flakes"}'
|
||||||
|
|
||||||
|
nix run . -- '{"name":"flakes"}'
|
||||||
|
nix flake check
|
||||||
|
```
|
||||||
Loading…
x
Reference in New Issue
Block a user