nw-fp-haskell
Haskell language-specific patterns, GADTs, type classes, and effect systems
它会碰到什么
逐条看命中(2 条严重或高危)
- 严重
SKILL.md:22exec-pipe-to-shellcurl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
- 严重
SKILL.md:209instruction-harmful-additivepriceOrder (ValidatedOrder d) = Right (PricedOrder (addPricing d))
这一栏是扫描器报的事实,不是结论。命中多不等于有毒(安全工具、规则库、示例脚本本来就会包含危险写法),命中少也不等于干净。它和你手上的凭据、文件、网络有什么关系,需要你自己看。
技能内容
FP in Haskell -- Functional Software Crafter Skill
Cross-references: [fp-principles](../nw-fp-principles/SKILL.md) | [fp-domain-modeling](../nw-fp-domain-modeling/SKILL.md) | [property-based-testing](../nw-property-based-testing/SKILL.md)
When to Choose Haskell
- Best for: correctness-critical systems | compiler-enforced purity | maximum type safety | financial systems
- Not ideal for: teams needing fast onboarding | rapid prototyping | .NET/JVM platform requirements
[STARTER] Quick Setup
# Install GHCup (manages GHC, cabal, stack, HLS)
curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
# Create project
mkdir order-service && cd order-service && cabal init --interactive
# Or: stack new order-service simple && stack build && stack test
Test runner: cabal test or stack test. Add hspec, QuickCheck, hedgehog to build-depends.
[STARTER] Type System for Domain Modeling
Choice Types (Sum Types)
data PaymentMethod
= CreditCard CardNumber ExpiryDate
| BankTransfer AccountNumber
| Cash
deriving (Eq, Show)
Record Types and Newtypes
data Customer = Customer
{ customerId :: CustomerId
, customerName :: CustomerName
, customerEmail :: EmailAddress
} deriving (Eq, Show)
newtype OrderId = OrderId Int deriving (Eq, Ord, Show)
newtype EmailAddress = EmailAddress Text deriving (Eq, Show)
newtype is erased at compile time -- zero runtime overhead, full type safety.
[STARTER] Validated Construction (Smart Constructors)
module Domain.Email (EmailAddress, mkEmailAddress, emailToText) where
import Data.Text (Text)
import qualified Data.Text as T
newtype EmailAddress = EmailAddress Text deriving (Eq, Show)
mkEmailAddress :: Text -> Either ValidationError EmailAddress
mkEmailAddress raw
| "@" `T.isInfixOf` raw = Right (EmailAddress raw)
| otherwise = Left (InvalidEmail raw)
Export the type but not the constructor. Only mkEmailAddress can create values.
[INTERMEDIATE] Composition Style
Function Composition (Right-to-Left)
-- (.) composes right-to-left
processOrder :: RawOrder -> Either OrderError Confirmation
processOrder = confirmOrder . priceOrder . validateOrder
Monadic Chaining with do-notation
placeOrder :: RawOrder -> Either OrderError Confirmation
placeOrder raw = do
validated <- validateOrder raw
priced <- priceOrder validated
confirmOrder priced
Applicative for Independent Validation
mkCustomer :: Text -> Text -> Either ValidationError Customer
mkCustomer rawName rawEmail =
Customer
<$> mkCustomerId 0
<*> mkCustomerName rawName
<*> mkEmailAddress rawEmail
Error-Accumulating Validation
import Data.Validation (Validation, failure, success)
mkCustomerV :: Text -> Text -> Validation [ValidationError] Customer
mkCustomerV rawName rawEmail =
Customer
<$> validateName rawName -- Validation [ValidationError] CustomerName
<*> validateEmail rawEmail -- all errors collected, not short-circuited
Unlike Either which stops at first error, Validation accumulates all failures via its Applicative instance.
[INTERMEDIATE] Effect Management
Haskell enforces purity at the compiler level. IO in return type means side effects.
calculateTotal :: Order -> Money -- Pure: compiler guarantees no side effects
calculateTotal order = sumOf (orderLines order)
saveOrder :: Order -> IO () -- Impure: IO in the type
saveOrder order = writeToDatabase order
-- calculateTotal CANNOT call saveOrder -- compiler error
[ADVANCED] Three Layers Pattern (Hexagonal Architecture)
-- Layer 1: Pure domain (no IO, no effects)
module Domain.Order (calculateDiscount, validateOrder) where
calculateDiscount :: Order -> Discount
calculateDiscount order
| totalLines order > 10 = Discount 0.1
| otherwise = Discount 0.0
-- Layer 2: Effect interfaces (type classes as ports)
class Monad m => OrderRepo m where
findOrder :: OrderId -> m (Maybe Order)
saveOrder :: Order -> m ()
-- Layer 3: IO implementations (adapters)
instance OrderRepo IO where
findOrder orderId = queryDatabase orderId
saveOrder order = insertDatabase order
Effect libraries: Effectful (recommended starting point, best performance) | mtl (existing codebases) | Polysemy (algebraic effect semantics).
[INTERMEDIATE] Testing
Frameworks: QuickCheck (original PBT) | Hedgehog (integrated shrinking) | Hspec (BDD) | tasty (composable test tree). See [property-based-testing](../nw-property-based-testing/SKILL.md) for detailed PBT patterns.
Property Test Example
import Test.Hspec
import Test.QuickCheck
spec :: Spec
spec = describe "validateOrder" $ do
it "round-trips through serialization" $
property $ \order ->
deserializeOrder (serializeOrder order) === Right order
it "validated orders always have positive totals" $
property $ \rawOrder ->
case validateOrder rawOrder of
Left _ -> discard
Right valid -> orderTotal valid > Money 0
Custom Generator
import Data.Text (pack)
import Test.QuickCheck
genValidEmail :: Gen EmailAddress
genValidEmail = do
user <- listOf1 (elements ['a'..'z'])
domain <- listOf1 (elements ['a'..'z'])
pure (EmailAddress (pack (user ++ "@" ++ domain ++ ".com")))
[ADVANCED] Idiomatic Patterns
GADTs for State Machines
{-# LANGUAGE GADTs, DataKinds #-}
data OrderState = Unvalidated | Validated | Priced
data Order (s :: OrderState) where
UnvalidatedOrder :: RawData -> Order 'Unvalidated
ValidatedOrder :: ValidData -> Order 'Validated
PricedOrder :: PricedData -> Order 'Priced
-- Type-safe transitions: only validated orders can be priced
priceOrder :: Order 'Validated -> Either PricingError (Order 'Priced)
priceOrder (ValidatedOrder d) = Right (PricedOrder (addPricing d))
Lazy Evaluation for Decoupled Pipelines
eligibleOrders :: [Order] -> [Order]
eligibleOrders = take 10 . filter isEligible . sortBy orderDate
Maturity and Adoption
- Steep learning curve: Monads, type classes, category-theory vocabulary create significant onboarding barrier. Budget extra ramp-up time.
- GHC extensions confusion: Over 100 language extensions; knowing which to enable requires experience. Start with
GHC2021defaults. - Space leaks from laziness: Default lazy evaluation causes subtle memory issues. Requires profiling discipline and strict annotations.
- Smaller talent pool: Hiring Haskell developers harder than mainstream languages. Consider team sustainability before committing.
Common Pitfalls
- Lazy space leaks: Use
foldl'(strict) instead offoldl. UseBangPatternsfor strict accumulators. - String vs Text: Never use
String([Char]) for real data. UseData.Text/Data.ByteString. - Orphan instances: Define instances in the module of the type or class. Use newtype wrappers otherwise.
- Over-abstracting with type-level programming: GADTs and type families increase compile times and error complexity. Use for genuine safety gains.
想直接用这个技能?
本站把开放许可(MIT / Apache 等)的技能按仓库打包整理到网盘,点一下转存到你自己的网盘,不用一个个从 GitHub 拉。许可未声明的技能只给原始仓库链接,不打包。
同名技能的其他版本
有 2 个不同仓库或目录里都有叫 nw-fp-haskell 的技能。它们内容并不相同,别混用:
- nWave-ai/nWave — Haskell language-specific patterns, GADTs, type classes, and effect systems