Add a more advanced haskell project

This commit is contained in:
Hassan Abedi 2026-04-21 14:19:00 +02:00
parent d53b887029
commit 72f23438b1
9 changed files with 267 additions and 0 deletions

View 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
View 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
```

View 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
View 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
View 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;
};
}

View 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

View 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")

View 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"

View 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
```