---
name: Haskell Ecosystem
description: This skill should be used when working with Haskell projects, "cabal.project", "stack.yaml", "ghc", "cabal build/test/run", "stack build/test/run", or Haskell language patterns. Provides comprehensive Haskell ecosystem patterns and best practices.
version: 0.1.0
---
Provide comprehensive patterns for Haskell language, GHC toolchain, Cabal/Stack build systems, and type-level programming.
Read - Analyze cabal files, stack.yaml, and Haskell source filesEdit - Modify Haskell code and build configurationBash - Run cabal build, stack build, ghci commandsmcp__context7__get-library-docs - Fetch latest Haskell documentationFunctions have no side effects; same input always produces same outputExpressions evaluated only when needed; enables infinite data structuresHindley-Milner type system; explicit signatures recommended for top-level definitionsCompose computations with effects; IO, Maybe, Either, State, Reader, WriterAd-hoc polymorphism; define operations for types (Eq, Ord, Show, Functor, Applicative, Monad)Sum types (Either, Maybe) and product types (tuples, records)
data Maybe a = Nothing | Just a
data Either a b = Left a | Right b
data Person = Person { name :: String, age :: Int }
Define behavior for types; similar to interfaces but more powerfulEquality comparison (==, /=)Ordering comparison (compare, <, >, <=, >=)String representation (show)Parse from string (read)Mappable containers (fmap, <$>)Apply functions in context (<*>, pure)Sequence computations (>>=, return, do-notation)Reducible structures (foldr, foldl, toList)Traverse with effects (traverse, sequenceA)Associative binary operation (<>)Semigroup with identity (mempty, mappend)
class Eq a where
(==) :: a -> a -> Bool
(/=) :: a -> a -> Bool
instance Eq Person where
p1 == p2 = name p1 == name p2 && age p1 == age p2
Advanced type system features for compile-time guaranteesGeneralized Algebraic Data Types - refine types in pattern matchingGADTs
{-# LANGUAGE GADTs #-}
data Expr a where
LitInt :: Int -> Expr Int
LitBool :: Bool -> Expr Bool
Add :: Expr Int -> Expr Int -> Expr Int
If :: Expr Bool -> Expr a -> Expr a -> Expr a
eval :: Expr a -> a
eval (LitInt n) = n
eval (LitBool b) = b
eval (Add e1 e2) = eval e1 + eval e2
eval (If c t e) = if eval c then eval t else eval e
Type-level functions; compute types from typesTypeFamilies
{-# LANGUAGE TypeFamilies #-}
type family Element c where
Element [a] = a
Element (Set a) = a
Element Text = Char
class Container c where
type Elem c
empty :: c
insert :: Elem c -> c -> c
Promote data types to kinds for type-level programmingDataKinds
{-# LANGUAGE DataKinds, KindSignatures #-}
data Nat = Zero | Succ Nat
data Vec (n :: Nat) a where
VNil :: Vec 'Zero a
VCons :: a -> Vec n a -> Vec ('Succ n) a
Type parameters that don't appear in value constructors
newtype Tagged tag a = Tagged { unTagged :: a }
data Validated
data Unvalidated
validateEmail :: Tagged Unvalidated String -> Maybe (Tagged Validated String)
Type constructors as parameters; enables Functor, Monad abstractions
class Functor f where
fmap :: (a -> b) -> f a -> f b
-- f is a type constructor (* -> *)
-- Functor operates on type constructors, not concrete types
Compose monadic effects using transformers from mtl/transformersBuild custom monad stacks with transformers
import Control.Monad.Trans.Reader
import Control.Monad.Trans.State
import Control.Monad.Trans.Except
type App a = ReaderT Config (StateT AppState (ExceptT AppError IO)) a
runApp :: Config -> AppState -> App a -> IO (Either AppError (a, AppState))
runApp cfg st app = runExceptT (runStateT (runReaderT app cfg) st)
Use mtl type classes for polymorphic effect handling
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.Except
doSomething :: (MonadReader Config m, MonadState AppState m, MonadError AppError m) => m Result
doSomething = do
cfg <- ask
st <- get
when (invalid cfg) $ throwError InvalidConfig
pure (compute cfg st)
Read-only environment; ask, localMutable state; get, put, modifyError handling; throwError, catchErrorAccumulate output; tell, listenShort-circuit on NothingNon-determinism (use list-t or logict for correct semantics)Composable getters, setters, and traversals using lens libraryFocus on parts of data structures
import Control.Lens
data Person = Person { _name :: String, _age :: Int }
makeLenses ''Person
-- name :: Lens' Person String
-- age :: Lens' Person Int
getName :: Person -> String
getName p = p ^. name
setName :: String -> Person -> Person
setName n p = p & name .~ n
modifyAge :: (Int -> Int) -> Person -> Person
modifyAge f p = p & age %~ f
Different optic types for different access patternsGet and set exactly one valueFocus on one branch of a sum typeFocus on zero or more valuesBidirectional transformationRead-only accessRead-only traversalCommon lens operatorsView through lens (view)Set value (set)Modify value (over)Apply function (flip ($))View through prism (preview)View all through traversal (toListOf)Alternative to lens with better type errors and compositionopticsConsidered more modern; lens has larger ecosystemOptional values; prefer over null
findUser :: UserId -> Maybe User
findUser uid = lookup uid users
-- Safe chaining with Monad
getUserEmail :: UserId -> Maybe Email
getUserEmail uid = do
user <- findUser uid
pure (userEmail user)
Computations that may fail with error information
parseConfig :: Text -> Either ParseError Config
parseConfig input = do
json <- parseJSON input
validateConfig json
Error handling in monadic contexts
import Control.Monad.Except
data AppError = NotFound | InvalidInput String | IOError IOException
loadUser :: MonadError AppError m => UserId -> m User
loadUser uid = do
mUser <- findUser uid
case mUser of
Nothing -> throwError NotFound
Just u -> pure u
Zero-cost wrapper for type safety
newtype UserId = UserId { unUserId :: Int }
deriving (Eq, Ord, Show)
newtype Email = Email { unEmail :: Text }
deriving (Eq, Show)
Validate data at construction time
module Email (Email, mkEmail, unEmail) where
newtype Email = Email Text
mkEmail :: Text -> Maybe Email
mkEmail t
| isValidEmail t = Just (Email t)
| otherwise = Nothing
Named fields with accessor functions
{-# LANGUAGE RecordWildCards #-}
data Config = Config
{ configHost :: String
, configPort :: Int
, configTimeout :: Int
}
-- Using RecordWildCards
mkConnection :: Config -> IO Connection
mkConnection Config{..} = connect configHost configPort
Functions that crash on some inputs (head, tail, fromJust, read)Use safe alternatives (headMay, listToMaybe) or pattern matchingUsing String ([Char]) for text processingUse Text or ByteString for performanceUsing lazy IO (readFile, getContents) in productionUse strict IO or streaming (conduit, pipes, streaming)Defining type class instances outside the module of the type or classUse newtype wrappers or define instances in appropriate modules
.
├── my-project.cabal
├── cabal.project # Multi-package configuration
├── cabal.project.local # Local overrides (gitignored)
├── src/
│ └── MyProject.hs
├── app/
│ └── Main.hs
├── test/
│ └── Spec.hs
└── bench/
└── Bench.hs
cabal-version: 3.0
name: my-project
version: 0.1.0.0
synopsis: Short description
license: MIT
author: Your Name
maintainer: your@email.com
common warnings
ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates
-Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints
library
import: warnings
exposed-modules: MyProject
build-depends: base ^>=4.18,
text ^>=2.0,
containers ^>=0.6
hs-source-dirs: src
default-language: GHC2021
executable my-project
import: warnings
main-is: Main.hs
build-depends: base ^>=4.18,
my-project
hs-source-dirs: app
default-language: GHC2021
test-suite my-project-test
import: warnings
type: exitcode-stdio-1.0
main-is: Spec.hs
build-depends: base ^>=4.18,
my-project,
hspec ^>=2.11,
QuickCheck ^>=2.14
hs-source-dirs: test
default-language: GHC2021
-- Caret (^>=): major version compatible
base ^>=4.18 -- 4.18.x.x
-- Range
text >=2.0 && <2.2
-- Any version (avoid in published packages)
containers
default-extensions:
OverloadedStrings
LambdaCase
RecordWildCards
DerivingStrategies
GeneralizedNewtypeDeriving
TypeApplications
default-language: GHC2021 -- Recommended for new projects
Multi-package project configuration
packages: .
./subpackage
-- Use local package
optional-packages: ../local-dependency
-- Optimization
optimization: 2
-- Documentation
documentation: True
-- Test options
tests: True
-- Allow newer dependencies
allow-newer: base
Compile the projectBuild all targetsBuild and run executableRun test suitesStart GHCi with project loadedGenerate documentationUpdate package indexLock dependency versionsGenerate version boundsCheck for outdated dependencies
.
├── stack.yaml # Stack project configuration
├── stack.yaml.lock # Locked dependency versions
├── package.yaml # hpack format (generates .cabal)
└── ... (same as cabal)
resolver: lts-22.0 # Stackage LTS snapshot
packages:
- .
- ./subpackage
extra-deps:
- some-package-1.0.0
- github: owner/repo
commit: abc123
ghc-options:
"$locals": -Wall -Werror
hpack format; generates .cabal file
name: my-project
version: 0.1.0.0
dependencies:
- base >= 4.18 && < 5
- text
- containers
ghc-options:
- -Wall
- -Wcompat
default-extensions:
- OverloadedStrings
- LambdaCase
library:
source-dirs: src
executables:
my-project:
main: Main.hs
source-dirs: app
dependencies:
- my-project
tests:
my-project-test:
main: Spec.hs
source-dirs: test
dependencies:
- my-project
- hspec
- QuickCheck
Compile the projectRun testsBuild and run executableStart GHCi with project loadedGenerate documentationClean build artifactsUpgrade Stack itselfWhich build tool should I use?Stack with LTS resolverCabal (native format)Cabal with cabal.projectStack (simpler getting started)Cabal with haskell.nix or nixpkgsGlasgow Haskell CompilerGHC 9.12+ (2025-2026)Previous standardDefault edition; enables common extensionsCurrent recommended for new code; extends GHC2021Haskell Language Server - IDE support via LSPCode completionType information on hoverGo to definitionFind referencesCode actions (import, qualify)Diagnostics (errors, warnings, hlint)Code formatting (fourmolu, ormolu, stylish-haskell)hls.yaml or hie.yaml
cradle:
cabal:
- path: "src"
component: "lib:my-project"
- path: "app"
component: "exe:my-project"
- path: "test"
component: "test:my-project-test"
Opinionated formatter; ormolu fork with more optionsfourmolu -i src/**/*.hsMinimal configuration formatterormolu -i src/**/*.hsConfigurable import organization and formattingstylish-haskell -i src/**/*.hsSuggest idiomatic Haskell improvementshlint src/.hlint.yaml
- ignore: {name: "Use camelCase"}
- warn: {name: "Use head"}
- error: {name: "Use String"}
Static analysis for HaskellstanDetect dead codeweederBDD-style testing framework
import Test.Hspec
main :: IO ()
main = hspec $ do
describe "Calculator" $ do
it "adds two numbers" $ do
add 1 2 `shouldBe` 3
it "handles negative numbers" $ do
add (-1) 1 `shouldBe` 0
describe "Parser" $ do
context "when input is valid" $ do
it "parses successfully" $ do
parse "valid" `shouldSatisfy` isRight
context "when input is invalid" $ do
it "returns error" $ do
parse "invalid" `shouldSatisfy` isLeft
Equality checkPredicate checkIO action resultException checkList containmentProperty-based testing; generate random test cases
import Test.QuickCheck
prop_reverseReverse :: [Int] -> Bool
prop_reverseReverse xs = reverse (reverse xs) == xs
prop_sortIdempotent :: [Int] -> Bool
prop_sortIdempotent xs = sort (sort xs) == sort xs
-- With preconditions
prop_headLast :: NonEmptyList Int -> Bool
prop_headLast (NonEmpty xs) = head xs == head xs
-- Custom generators
newtype PositiveInt = PositiveInt Int deriving Show
instance Arbitrary PositiveInt where
arbitrary = PositiveInt . abs <$> arbitrary
-- With HSpec
describe "reverse" $ do
it "is its own inverse" $ property $
\xs -> reverse (reverse xs) == (xs :: [Int])
Modern property-based testing with integrated shrinking
import Hedgehog
import qualified Hedgehog.Gen as Gen
import qualified Hedgehog.Range as Range
prop_reverse :: Property
prop_reverse = property $ do
xs <- forAll $ Gen.list (Range.linear 0 100) Gen.alpha
reverse (reverse xs) === xs
Use QuickCheck for properties; HSpec for examplesTest edge cases: empty lists, zero, negative numbersUse type-driven development; write types firstUse doctest for documentation examplesUse Context7 MCP for up-to-date Haskell documentationresolve-library-id libraryName="optics haskell"get-library-docs context7CompatibleLibraryID="/websites/hackage_haskell_package_optics-0_4_2_1" topic="lenses"get-library-docs context7CompatibleLibraryID="/websites/hackage_haskell_package_hspec-2_11_12" topic="expectations"get-library-docs context7CompatibleLibraryID="/haskell-servant/servant" topic="server"Understand Haskell code requirements1. Check cabal file or package.yaml for project configuration2. Review existing types and type classes3. Identify monad transformer requirements4. Check for type-level programming needsWrite pure, type-safe Haskell code1. Design with types first; let types guide implementation2. Use appropriate abstractions (Functor, Applicative, Monad)3. Handle errors with Maybe/Either/ExceptT4. Write property-based tests for core logicVerify Haskell code correctness1. Run cabal build or stack build2. Run hlint for suggestions3. Run cabal test or stack test4. Check formatting with fourmolu/ormoluLet types guide design; use the type system to prevent errorsAvoid partial functions; use safe alternativesRun hlint and fix suggestions before committingUse Text/ByteString instead of String for performancePrefer mtl-style type class constraints over concrete monad stacksWrite property-based tests for core logicDocument exported functions with Haddock commentsUse newtypes for type safetyEnable GHC2021 or explicit commonly-used extensionsRun hlint before committing; address all suggestionsAvoid partial functions (head, tail, fromJust); use safe alternativesUse types to encode invariants; make illegal states unrepresentableUse fourmolu or ormolu for consistent formattingPrefer Text over String for text processingWrite HSpec tests for behavior; QuickCheck for propertiesUse explicit type signatures for top-level definitionsType system design, monad transformer architecture, and effect modelingHaskell implementation with proper abstractions and error handlingRun hlint, check formatting, and enforce Haskell idiomsHLint suggestion about styleApply suggestion, maintain idiomatic codeType error or missing instanceReview types, add instance or adjust designBreaking change in public APIStop, present migration options to userPartial function usage or unsafe code in libraryBlock operation, require safe alternativesNavigate type class hierarchies and module structureFetch Haskell documentation and library referencesDebug type errors, missing instances, and performance issueshaskell.nix integration and nixpkgs Haskell infrastructureUse types to encode invariantsAvoid partial functions in library codeFollow Haskell style conventionsUsing String for text processingLazy IO in production codeOrphan instances