haskell

TotalClaw 作者 totalclaw

专家 Haskell 开发技能。涵盖类型驱动设计、GHC 扩展、Cabal/Stack/Nix 构建、性能优化、测试和现代 Haskell 库生态系统。激活任何 Haskell 编程、调试或架构任务。

安装 / 下载方式

TotalClaw CLI推荐
totalclaw skills install shop:totalclaw:totalclaw~mightybyte-haskell
cURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Atotalclaw~mightybyte-haskell/file -o mightybyte-haskell.md
## 概述(中文)

专家 Haskell 开发技能。涵盖类型驱动设计、GHC 扩展、Cabal/Stack/Nix 构建、性能优化、测试和现代 Haskell 库生态系统。激活任何 Haskell 编程、调试或架构任务。

## 原文

# 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.
5. **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)

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

Minimal `my-project.cabal`:
```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)
```haskell
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
```haskell
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
```haskell
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
```haskell
-- 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
```haskell
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
```haskell
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

```haskell
-- 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
```haskell
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)
```haskell
-- 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)

-- Com