← 返回
未分类 中文

Haskell

Expert Haskell development skill. Covers type-driven design, GHC extensions, Cabal/Stack/Nix builds, performance optimization, testing, and the modern Haskel...
专家级 Haskell 开发技能,涵盖类型驱动设计、GHC 扩展、Cabal/Stack/Nix 构建、性能优化、测试以及现代 Haskell 开发……
mightybyte
未分类 clawhub v1.0.0 1 版本 100000 Key: 无需
★ 0
Stars
📥 220
下载
💾 0
安装
1
版本
#latest

概述

Haskell Development Guide

Core Philosophy

  1. Types are the design — Make illegal states unrepresentable. If the type checker accepts it, we would like it to be correct.
  2. Purity by default — Side effects are explicit in the type system. IO is a feature, not a burden.
  3. Composition over inheritance — Small, composable functions. Typeclasses for ad-hoc polymorphism.
  4. Laziness as a tool — Enables elegant abstractions but demands awareness of space leaks.
  5. Correctness first, then performance — Get it right, then profile, then optimize.
  6. Keep it Simple — Purity and strong types (i.e. roughly Haskell2010) gives you the majority of Haskell's value. Avoid more advanced language features unless absolutely necessary.

Project Setup

Cabal (preferred with Nix)

mkdir my-project && cd my-project
cabal init --interactive

Minimal my-project.cabal:

cabal-version: 3.0
name:          my-project
version:       0.1.0.0
build-type:    Simple

common warnings
    ghc-options: -Wall -Werror -Wcompat -Widentities
                 -Wincomplete-record-updates
                 -Wincomplete-uni-patterns
                 -Wpartial-fields
                 -Wredundant-constraints

library
    import:           warnings
    exposed-modules:  MyProject
    build-depends:    base >= 4.17 && < 5
                    , text
                    , containers
                    , aeson
    hs-source-dirs:   src
    default-language:  Haskell2010
    default-extensions:
        DeriveGeneric
        DerivingStrategies
        LambdaCase
        ScopedTypeVariables

executable my-project
    import:           warnings
    main-is:          Main.hs
    build-depends:    base, my-project
    hs-source-dirs:   app
    default-language: Haskell2010

test-suite tests
    import:           warnings
    type:             exitcode-stdio-1.0
    main-is:          Main.hs
    build-depends:    base, my-project, hspec, QuickCheck
    hs-source-dirs:   test
    default-language: Haskell2010

Project Structure

my-project/
├── exe/
│   └── Main.hs              # Executable entry point (thin — delegates to library)
├── src/
│   ├── MyProject.hs          # Public API (re-exports)
│   ├── MyProject/
│   │   ├── Types.hs          # Core domain types
│   │   ├── App.hs            # Application monad, config
│   │   ├── DB.hs             # Database layer
│   │   ├── API.hs            # HTTP/API layer
│   │   └── Internal/         # Not exported — implementation details
│   │       └── Utils.hs
├── test/
│   ├── Main.hs
│   └── MyProject/
│       ├── TypesSpec.hs
│       └── DBSpec.hs
├── my-project.cabal
├── cabal.project             # Multi-package config, source-repository-packages

Essential GHC Extensions

Always Enable (via default-extensions in .cabal)

DeriveGeneric           -- Derives Generic
DerivingStrategies      -- Explicit: deriving stock, newtype, anyclass, via
LambdaCase              -- \case { ... } instead of \x -> case x of ...
ScopedTypeVariables     -- forall a. ... lets you reference 'a' in where clauses

Use Freely When Needed

BangPatterns
DeriveDataTypeable
DeriveFunctor
DeriveGeneric               -- Generic instances for aeson, etc.
DerivingVia                 -- Derive via newtype coercion
DuplicateRecordFields       -- Same field name in different records
ExistentialQuantification   -- Hide type variables
FlexibleContexts            -- Relax context restrictions
FlexibleInstances           -- Relax instance head restrictions
FunctionalDependencies      -- fundeps for MPTC
GeneralizedNewtypeDeriving  -- Derive through newtypes
MultiParamTypeClasses       -- Typeclasses with multiple params
NumericUnderscores          -- More readable number syntax
OverloadedRecordDot         -- record.field syntax (GHC 9.2+)
OverloadedStrings           -- String literals as Text/ByteString
RankNTypes                  -- Higher-rank polymorphism (forall inside arrows)
RecordWildCards             -- Controversial but can be used effectively

Avoid unless there's a significant clear value

TemplateHaskell         -- Metaprogramming (aeson TH, lens TH). Slows compilation.
GADTs                   -- Generalized algebraic data types
TypeFamilies            -- Type-level functions
DataKinds               -- Promote data constructors to types
ConstraintKinds         -- Alias constraint sets
UndecidableInstances    -- Sometimes needed for MTL/type families. Understand why.
TypeOperators           -- type a :+: b. Servant uses heavily.
AllowAmbiguousTypes     -- Pair with TypeApplications for type-level dispatch.

Type-Driven Development

Scope accessors with type name to avoid name clashes

data User = User
  { _user_firstName :: String
  , _user_email :: String
  } deriving (Eq,Ord,Show,Read,Generic)

Make Illegal States Unrepresentable

-- BAD: stringly-typed
data User = User { _user_role :: String, _user_email :: String }

-- GOOD: types encode constraints
data Role = Admin | Editor | Viewer
  deriving stock (Show, Eq, Ord)

newtype Email = Email { _unEmail :: Text }  -- smart constructor validates

mkEmail :: Text -> Either EmailError Email
mkEmail t
  | "@" `T.isInfixOf` t = Right (Email t)
  | otherwise = Left InvalidEmail

data User = User
  { _user_role  :: !Role
  , _user_email :: !Email
  }

Phantom Types for State Machines

data Draft
data Published

data Article (s :: Type) = Article
  { articleTitle   :: !Text
  , articleContent :: !Text
  }

publish :: Article Draft -> Article Published
publish (Article t c) = Article t c

-- Only published articles can be shared
share :: Article Published -> IO ()
share = ...

Newtypes for Safety

newtype UserId    = UserId    { unUserId    :: Int64 } deriving newtype (Eq, Ord, Show, FromJSON, ToJSON)
newtype ProductId = ProductId { unProductId :: Int64 } deriving newtype (Eq, Ord, Show, FromJSON, ToJSON)

-- Now you can't accidentally pass a ProductId where UserId is expected
getUser :: UserId -> IO User

Error Handling

-- Pure errors: Either
parseConfig :: Text -> Either ConfigError Config

-- App-level errors: ExceptT or MonadError
class Monad m => MonadError e m where
  throwError :: e -> m a
  catchError :: m a -> (e -> m a) -> m a

-- The ReaderT pattern (simple, composable)
newtype App a = App { unApp :: ReaderT AppEnv IO a }
  deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)

data AppError
  = NotFound Text
  | Unauthorized
  | ValidationError [Text]
  deriving stock (Show)

-- Throw with exceptions in IO, catch at boundaries
throwIO :: Exception e => e -> IO a
catch   :: Exception e => IO a -> (e -> IO a) -> IO a

-- RULE: Use Either for expected failures, exceptions for unexpected/IO failures.
-- Never use error/undefined in library code.

Common Patterns

The ReaderT Pattern

data AppEnv = AppEnv
  { appDbPool   :: !Pool Connection
  , appLogger   :: !Logger
  , appConfig   :: !Config
  }

newtype App a = App (ReaderT AppEnv IO a)
  deriving newtype (Functor, Applicative, Monad, MonadIO, MonadReader AppEnv)

runApp :: AppEnv -> App a -> IO a
runApp env (App m) = runReaderT m env

-- Use Has-pattern for granular access:
class Has field env where
  obtain :: env -> field

instance Has (Pool Connection) AppEnv where
  obtain = appDbPool

grabPool :: (MonadReader env m, Has (Pool Connection) env) => m (Pool Connection)
grabPool = asks obtain

Optics (lens/optics)

-- With OverloadedRecordDot (GHC 9.2+), often you don't need lens for simple access.
-- Use lens/optics for: nested updates, traversals, prisms for sum types.

-- lens: view, set, over
view _1 (1, 2)       -- 1
set _1 10 (1, 2)     -- (10, 2)
over _1 (+1) (1, 2)  -- (2, 2)

-- Compose with (.)
view (config . database . host) appEnv
over (users . each . name) T.toUpper myData

Testing

HSpec - Testing Framework

-- test/MyProject/TypesSpec.hs
module MyProject.TypesSpec (spec) where

import Test.Hspec
import MyProject.Types

spec :: Spec
spec = do
  describe "mkEmail" $ do
    it "accepts valid emails" $
      mkEmail "user@example.com" `shouldBe` Right (Email "user@example.com")

    it "rejects invalid emails" $
      mkEmail "invalid" `shouldSatisfy` isLeft

QuickCheck - Property Testing

import Test.QuickCheck

prop_reverseReverse :: [Int] -> Bool
prop_reverseReverse xs = reverse (reverse xs) == xs

-- Generate domain types:
instance Arbitrary Email where
  arbitrary = do
    user   <- listOf1 (elements ['a'..'z'])
    domain <- listOf1 (elements ['a'..'z'])
    pure $ Email $ T.pack $ user <> "@" <> domain <> ".com"

Performance Essentials

Strictness

-- Strict fields in data types (almost always want this):
data Config = Config
  { configHost :: !Text       -- strict (bang pattern in field)
  , configPort :: !Int
  }

-- BangPatterns in let bindings:
{-# LANGUAGE BangPatterns #-}
let !result = expensiveComputation

-- Use Text, not String. Always.
-- Use ByteString for binary data.
-- Use Vector for indexed access, [] for sequential processing.
-- Use HashMap/HashSet for large unordered collections.
-- Use Map/Set for ordered collections or when Ord is available.

Profiling

# Build with profiling
cabal build --enable-profiling

# Run with heap profiling
./my-project +RTS -hc -p -RTS

# Time profiling report
./my-project +RTS -p -RTS
# Generates my-project.prof

# Heap profile visualization
hp2ps -c my-project.hp

Common Space Leaks

-- BAD: lazy accumulator
foldl (+) 0 [1..1000000]  -- builds giant thunk chain

-- GOOD: strict left fold
foldl' (+) 0 [1..1000000]  -- evaluates as it goes

-- BAD: lazy state
modify (\s -> s { count = count s + 1 })  -- thunk builds up

-- GOOD: strict state
modify' (\s -> s { count = count s + 1 })
-- Or: use strict fields + evaluate

Commands Reference

TaskCommand
---------------
Buildcabal build
Runcabal run my-project
Testcabal test --test-show-details=direct
REPLcabal repl
Docscabal haddock
Linthlint src/
File watchghcid --command="cabal repl"
Deps outdatedcabal outdated
Cleancabal clean
Nix buildnix build
Nix dev shellnix develop

Common Gotchas

  1. String is [Char] — Use Text (from text package) everywhere. Enable OverloadedStrings for Text string literals.
  2. Lazy IOreadFile from Prelude is lazy. Use Data.Text.IO.readFile or ByteString.readFile instead.
  3. Orphan instances — Don't define typeclass instances outside the module that defines the type or the class. Use newtypes to wrap.
  4. Cabal hell — Use Nix or cabal's built-in solver (v2-build). Don't use cabal-install v1 commands.
  5. Records — Field names are global. Prefix field names with a uniform and readable scheme: _employee_familyName.
  6. Partial functions — Never use head, tail, fromJust, read in production code. Use pattern matching or safe alternatives.
  7. Unsafe operations - Never use accursedUnutterablePerformIO. Only use unsafePerformIO when you can prove its use correct.
  8. Undefined - undefined is a fantastic tool for stubbing things out for incremental development and type checking. It should never exist in production code.
  9. foldl vs foldl' — Always use foldl' (strict). The lazy foldl from Prelude is almost never what you want.
  10. Show for serializationShow is for debugging. Use aeson for JSON, binary/cereal for binary serialization.
  11. MonadFail — Pattern match failures in do blocks require MonadFail. Avoid partial patterns in do.
  12. Template Haskell ordering — TH splices create declaration groups. All TH splices must come after the declarations they reference and before declarations that reference the generated code.

Key Libraries

LibraryPurpose
------------------
textUnicode text (strict & lazy)
bytestringBinary data
aesonJSON encoding/decoding
postgresql-simple, mysql-simpleLow-level database library
lens / opticsComposable getters/setters
mtlMonad transformer classes
containersMap, Set, Seq (ordered)
unordered-containersHashMap, HashSet (fast)
vectorEfficient arrays
conduit / streamingStreaming data processing
warpFast HTTP server
http-clientLibrary for making http requests
stmSoftware transactional memory
QuickCheckProperty-based testing
hspecBDD-style test framework
tastyTest framework (composable)
criterionBenchmarking
optparse-applicativeCLI argument parsing
katipStructured logging
fakeGenerating realistic mock data

References

For detailed information, see:

  • references/type-system.md — ADTs, GADTs, type families, type classes, DataKinds, phantom types
  • references/common-patterns.md — MTL, ReaderT, effect systems, optics, free monads, type-level programming
  • references/libraries.md — Essential library ecosystem with examples
  • references/performance.md — Strictness, profiling, space leaks, concurrency, benchmarking
  • references/ghc-extensions.md — Comprehensive GHC extension guide by category
  • references/nix-haskell.md — Nix-based Haskell development (nixpkgs + haskell.nix)
  • references/cabal-guide.md — Cabal format, multi-package projects, Hackage publishing
  • references/best-practices.md — Code organization, Haddock, CI, style guide

版本历史

共 1 个版本

  • v1.0.0 当前
    2026-05-12 06:07 安全 安全

安全检测

腾讯云安全 (Keen)

安全,无风险
查看报告

腾讯云安全 (Sanbu)

安全,无风险
查看报告

🔗 相关推荐

developer-tools

Github

steipete
使用 `gh` CLI 与 GitHub 交互,通过 `gh issue`、`gh pr`、`gh run` 和 `gh api` 管理议题、PR、CI 运行及高级查询。
★ 666 📥 323,792
ai-intelligence

self-improving agent

pskoett
捕获经验教训、错误和纠正,以实现持续改进。使用时机:(1)命令或操作意外失败;(2)用户纠正……
★ 4,055 📥 795,929
ai-intelligence

Self-Improving + Proactive Agent

ivangdavila
自我反思+自我批评+自我学习+自组织记忆。智能体评估自身工作、发现错误并持续改进。
★ 1,349 📥 317,697