| name | neohaskell-implementer |
| description | NeoHaskell code implementation guide. Use when implementing features, writing tests, build/test loops, or any task requiring NeoHaskell code. Handles pipeline phases 7-9 (tests, implementation, build loop), 12-13 (fix reviews, final build), and 16 (fix bot comments). |
NeoHaskell Implementer
You are implementing code for the NeoHaskell project. Every line of code must follow NeoHaskell conventions exactly. Your code must be indistinguishable from what the project maintainer would write.
Operating Principles
- Follow the architecture document exactly — execute, not redesign
- Reuse what exists — before writing ANY utility, check if nhcore provides it
- Follow the style guide — the style guide is law
- Boy scout rule on touched files — when a file enters the diff, any pre-existing style debt in that file is cleaned up in the same edit (unqualified imports, point-free top-levels,
_ wildcard params, let..in/where, $, <>/++, raw String/IO/Either). Scoped strictly to files the change already opens; the rule never asks for a repo-wide reformat. Test bodies/assertions remain immutable when an immutability rule is in force; the rule applies to surrounding helpers, imports, and fixtures only.
- Minimal changes elsewhere — no bonus refactors in files the diff doesn't already touch, no "while I'm here" expansions of scope.
Reuse-First Rule (CRITICAL)
Before writing any helper function:
- Search nhcore — grep for existing functions
- Check the architecture document — it lists specific utilities to use
- Check
Core.hs re-exports
- Only if nothing exists — then write it, following existing patterns
NeoHaskell Code Style (MANDATORY)
Syntax Rules
| # | Rule | Correct | Wrong |
|---|
| 1 | Pipe operator | x |> foo |> bar | bar $ foo x |
| 2 | Do-blocks for bindings | do let y = expr | let y = expr in ... |
| 3 | Case expressions only | case x of { ... } | Pattern matching in function head |
| 4 | Descriptive type params | forall element result. | forall a b. |
| 5 | Qualified imports | import Module qualified | Unqualified imports |
| 6 | String interpolation | [fmt|Hello #{name}!|] | "Hello " <> name |
| 7 | Result, not Either | Result error value | Either error value |
| 8 | Task, not IO | Task err val | IO a |
| 9 | Task.yield | Task.yield value | pure / return |
| 10 | If-then-else for Bools | if cond then a else b | case cond of True -> ... |
Import Convention
-- Type unqualified, module qualified
import Array (Array)
import Array qualified
import Result (Result (..))
import Result qualified
-- GHC/base modules: qualified with full path
import Data.Text qualified
import Data.Aeson qualified as Json
Test Convention
spec :: Spec Unit
spec = do
describe "ModuleName" do
describe "functionName" do
it "describes expected behavior" \_ -> do
input |> ModuleName.functionName |> shouldBe expected
Performance Annotations
{-# INLINE fn #-} on small, hot-path functions
{-# UNPACK #-} on primitive fields in hot-path types
toEncoding over toJSON for Aeson on hot paths
Phase 7: Test Suite Writing
- Read test specification from Phase 6
- Create stub type definitions (enough for tests to compile)
- Translate each test case to Hspec code — follow spec EXACTLY
- Register tests in
nhcore.cabal
- Verify tests compile:
cabal build all
- Verify all tests fail (no implementation yet)
Phase 8: Implementation
- Read architecture document and test files
- Implement one module at a time
- After each module:
cabal build all
- Follow all style rules
- DO NOT modify test files
Common Pitfalls
- Orphan instances: Define typeclass instances with the type
- TH staging: Splices must come AFTER definitions they reference
- Missing cabal entries: New modules must be in
nhcore.cabal
- NoImplicitPrelude: Import everything from nhcore
Phase 9: Build & Test Loop
- Run
cabal build all
- If compilation fails — fix root cause, re-run
- If compilation succeeds — run
cabal test
- If tests fail — fix IMPLEMENTATION (never tests), re-run
- When all pass — run
hlint on changed files
- Fix any hlint warnings
Circuit Breaker
Maximum 10 iterations.
After 10 iterations with failures:
- STOP all edits
- Document what you've tried
- Report specific errors with file:line references
- Do NOT keep trying random fixes
Self-Review Checklist
Before reporting completion:
Phase 12: Fix Review Notes
- Read security review — address every Critical/High finding
- Read performance review — add INLINE, strict fields, fix allocations
- For each finding: understand root cause, implement minimal fix
- Run
cabal build all && cabal test after all fixes
- If fix touches security/hot-path code, note for re-review
Phase 16: Fix Bot Comments
- Read bot comments:
gh api repos/neohaskell/NeoHaskell/pulls/{PR}/comments
- For each comment: understand concern, implement fix
- Run
cabal build all && cabal test
- Commit and push
- Maximum 5 fix-and-push cycles — if CI still fails, escalate
Event-Sourcing Patterns
New Command
data MyCommand = MyCommand
{ entityId :: Uuid
, someField :: Text
}
deriving (Eq, Show, Generic)
instance Json.ToJSON MyCommand
instance Json.FromJSON MyCommand
-- TH macro (define endpoint handlers BEFORE this)
command "MyCommand" ''MyCommand ''MyEntity
[ 'someEndpoint
]
decide :: MyCommand -> Maybe MyEntity -> RequestContext -> Decision MyEvent
decide cmd entity _ctx =
case entity of
Just _ -> Decider.reject "Entity already exists"
Nothing -> Decider.acceptNew [MyEventCreated {entityId = cmd.entityId}]
New Entity
data MyEntity = MyEntity
{ entityId :: !Uuid
, someField :: !Text
}
deriving (Eq, Show, Generic)
data MyEvent
= MyEventCreated { entityId :: Uuid }
| MyEventUpdated { someField :: Text }
deriving (Eq, Show, Generic)
-- Type families
type instance EntityOf MyCommand = MyEntity
type instance EventOf MyEntity = MyEvent
Red Lines (NEVER Do These)
- NEVER modify test expectations
- NEVER use
$ — use |>
- NEVER use
let..in or where — use do
- NEVER use
Either — use Result
- NEVER use
pure or return
- NEVER use single-letter type parameters
- NEVER import from
Prelude directly
- NEVER create orphan instances
- NEVER continue after 10 failed iterations — report and stop
- NEVER refactor while fixing — minimal changes only