Code Style and Standards
cabal repl <package> # Interactive development
cabal build <package> # Build specific package
cabal test <package> # Run tests for package
GHCi Capture Tool
Use the custom ghci-capture tool instead of raw tmux capture:
# Use relative path to the tool
./ghci-capture session-name
# Shows intelligent context - if last prompt is empty, shows previous command output
# Much better than raw tmux capture for GHCi sessions
Haskell Conventions (enforced by .hlint.yaml)
Core Philosophy: Optimize for clarity and safety over conciseness. Code should be explicit, readable, and resistant to runtime errors.
Safety First: Ban Partial Functions
Never use these dangerous functions - they crash on edge cases:
-- BAD: Partial functions that crash
head [1,2,3] -- crashes on []
tail [1,2,3] -- crashes on []
maximum [1,2,3] -- crashes on []
minimum [1,2,3] -- crashes on []
fromJust (Just 42) -- crashes on Nothing
fromRight (Left err) -- crashes on Left
-- GOOD: Explicit pattern matching
case xs of
[] -> handleEmpty
(x:rest) -> useHead x
case myMaybe of
Nothing -> handleNothing
Just value -> useValue value
Use safe alternatives:
maximum1/minimum1for non-empty lists- Pattern matching instead of
fromJust/fromRight - Explicit error handling with
annotate "message" & unwrapError
Modern Haskell: Preferred Functions
Use modern equivalents:
-- OLD -> NEW
return x -> pure x
mapM f xs -> traverse f xs
mapM_ f xs -> traverse_ f xs
forM xs f -> for xs f
forM_ xs f -> for_ xs f
undefined -> todo -- shows warnings until removed
Explicit Over Clever
Prefer explicit code over point-free style:
-- BAD: Point-free style (hard to read)
f = map (+1) . filter even . concat
-- GOOD: Explicit pipeline
f xs = xs
& concat
& filter even
& map (+1)
-- BAD: const obscures intent
const True
-- GOOD: Explicit lambda with type information
\(_ :: Request) -> True
Avoid helper functions that hide logic:
-- BAD: fromMaybe hides the Nothing case
fromMaybe defaultValue myMaybe
-- GOOD: Explicit pattern matching
case myMaybe of
Nothing -> defaultValue
Just value -> value
Type-Safe Length and Containers
Use specific length functions:
-- BAD: Generic length is dangerous
length (3,4) -- equals 1 (surprising!)
length (Just 42) -- equals 1 (surprising!)
-- GOOD: Type-specific length
List.length [1,2,3]
Map.length myMap
Set.length mySet
Use strict containers:
-- BAD: Lazy containers cause space leaks
import Data.Map
-- GOOD: Strict containers
import Data.Map.Strict as Map
import Data.HashMap.Strict as HashMap
-- BAD: O(n²) nub
nub [1,2,1,3,2]
-- GOOD: O(n log n) nubOrd
nubOrd [1,2,1,3,2]
Text and ByteString Handling
Use my-prelude conversion functions:
-- BAD: Raw encoding functions
Data.Text.Encoding.encodeUtf8
Data.Text.Encoding.decodeUtf8
-- GOOD: Explicit error handling variants
textToBytesUtf8 -- safe conversion
bytesToTextUtf8 -- safe with Either result
bytesToTextUtf8Lenient -- replacement chars for invalid UTF-8
bytesToTextUtf8Unsafe -- crashes on invalid UTF-8 (discouraged)
-- Lazy/strict conversions
toStrict -- Text.Lazy -> Text
toLazy -- Text -> Text.Lazy
toStrictBytes -- ByteString.Lazy -> ByteString
toLazyBytes -- ByteString -> ByteString.Lazy
Left-to-Right Pipeline Style
Prefer data flow left-to-right:
-- BAD: Backward composition (right-to-left reading)
result = f . g . h $ input
-- GOOD: Forward pipeline (left-to-right reading)
result = input
& h
& g
& f
-- GOOD: Bind chain for monadic operations
result <- input
>>= h
>>= g
>>= f
Error Handling Patterns
Use Data.Error.Tree for structured errors:
-- GOOD: Structured error collection
parseUser :: Json.Value -> Either ErrorTree User
parseUser = Json.parse $ do
name <- Json.key "name" Json.asText
age <- Json.key "age" Json.asInt
pure User{..}
-- GOOD: Error annotation
result <- computation
& annotate "Failed to process user data"
& orAppThrow span
Module Import Guidelines
Use qualified imports for common modules:
import Data.Map.Strict as Map
import Data.HashMap.Strict as HashMap
import Data.Set as Set
import Data.Text as Text
import Data.Aeson as Json
import Data.ByteString.Char8 as Char8
Testing Framework
- Haskell: hspec with
Test.hsmodule for colorful diffs - Property testing: hedgehog for generators
- Database testing: tmp-postgres for isolated tests
Documentation Research with Hoogle
For comprehensive Haskell library documentation research:
Core Research Tools
# Function signatures and documentation
hoogle search --json "functionName"
# Module-level architecture and guidance
hoogle search --json "Module.Name"
# Type definitions and constructors
hoogle search --json "TypeName"
Research Workflow Pattern
-
Discover functions: Use
grepto find what exists in documentation filesgrep -r "functionPattern" /nix/store/*-doc/share/doc/*/html/ -
Get precise details: Use
hoogle search --jsonfor exact signatures and comprehensive documentation -
Cross-reference: Validate implementation approaches against library design patterns
Key Benefits
- Exact signatures: No guessing function types or parameters
- Module context: Understanding architectural patterns and intended usage
- Cross-package visibility: See how types/functions work across Haskell ecosystem
- Implementation validation: Confirm code patterns match library design intent
Documentation Sources
- Nix store:
/nix/store/*-doc/share/doc/*/html/contains complete HTML documentation - Hoogle JSON: Structured function/module/type information with links
- HTML files: Rich examples and cross-references (often large, use strategic reading)
Alternative: Local Hoogle Server
For unrestricted documentation access with rich HTML content:
Setup:
# Start documentation server in tmux
tmux new-session -d -s hoogle-docs
tmux send-keys -t hoogle-docs "hoogle server --local --port 9090" Enter
# Verify server is running
sleep 3 && curl -s http://localhost:9090 | head -10
Research Commands:
# Search for modules - comprehensive architectural documentation
curl -s "http://localhost:9090/?hoogle=Network.HTTP.Client" | head -100
# Search for functions - exact signatures and usage examples
curl -s "http://localhost:9090/?hoogle=setQueryString" | grep -A 20 -B 5 "setQueryString"
# Search for types - constructors and cross-package usage
curl -s "http://localhost:9090/?hoogle=defaultRequest" | grep -A 15 -B 5 "defaultRequest"
# Use byte ranges for large responses
curl -s -r 0-2047 "http://localhost:9090/?hoogle=searchTerm"
Key Benefits:
- No tool approval issues - Direct bash commands bypass permission prompts
- Rich HTML documentation - Complete examples, links, and architectural guidance
- Controllable output - Use head/tail/grep to manage response sizes
- Fast access - Immediate response times with comprehensive results
- Cross-package visibility - See functions across entire Haskell ecosystem
Proven Workflow: Start server ā curl search ā grep filter ā validate with hoogle search --json
Research methodology: Always verify library functions via Hoogle before implementation to ensure accurate, idiomatic Haskell code.