packages feed

warlock-0.1.0.0: src/Warlock/Tutorial.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedLabels #-}

-- |
-- Module      : Warlock.Tutorial
-- Description : Comprehensive tutorial for the Warlock library
-- License     : MIT
-- Maintainer  : ian@iankduncan.com
-- Stability   : experimental
--
-- = Welcome to Warlock!
--
-- Warlock is a Template Haskell library for automatic type-safe mapping between
-- Haskell data types. It builds on the @witch@ library to provide compile-time
-- verified conversions with flexible configuration options.
--
-- This tutorial will guide you from basic usage to advanced techniques, showing
-- you how to leverage Warlock's features for real-world scenarios.
--
-- == Table of Contents
--
-- 1. "Warlock.Tutorial#g:intro" - Introduction & Core Concepts
-- 2. "Warlock.Tutorial#g:basics" - Getting Started (Basic Examples)
-- 3. "Warlock.Tutorial#g:naming" - Naming Conventions
-- 4. "Warlock.Tutorial#g:fields" - Advanced Field Manipulation
-- 5. "Warlock.Tutorial#g:constructors" - Constructor Mapping
-- 6. "Warlock.Tutorial#g:tweak" - Type Generation with Warlock.Tweak
-- 7. "Warlock.Tutorial#g:hkd" - Higher-Kinded Data with Warlock.HKD
-- 8. "Warlock.Tutorial#g:realworld" - Real-World Patterns
-- 9. "Warlock.Tutorial#g:advanced" - Advanced Techniques
-- 10. "Warlock.Tutorial#g:troubleshooting" - Troubleshooting Guide
--
-- = Introduction & Core Concepts #intro#
--
-- == What is Warlock?
--
-- Warlock generates 'Witch.From' and 'Witch.TryFrom' instances between data types,
-- eliminating boilerplate conversion code while maintaining type safety. Unlike
-- manual instances or generic deriving, Warlock gives you:
--
-- * __Compile-time field name checking__: Typos caught at compile time
-- * __Flexible mapping strategies__: Structural or semantic matching
-- * __Powerful field rules__: Computed fields, virtual fields, defaults
-- * __Type generation__: Create DTOs and HKD types automatically
--
-- == When to Use Warlock
--
-- Warlock excels at:
--
-- * API versioning (V1 → V2 → V3 migrations)
-- * Database to domain model conversions
-- * Creating DTOs for web APIs
-- * Form validation with HKD patterns
-- * Protocol buffer conversions
-- * Multi-layer architecture with distinct types per layer
--
-- == The witch Library
--
-- Warlock builds on @witch@, which provides two type classes:
--
-- * 'Witch.From' - Total conversions that always succeed
-- * 'Witch.TryFrom' - Partial conversions that may fail
--
-- Once Warlock generates instances, you use @witch@'s conversion functions:
--
-- @
-- -- Total conversion
-- employee = 'Witch.from' person :: Employee
--
-- -- Fallible conversion
-- result = 'Witch.tryFrom' text :: Either (TryFromException Text Int) Int
-- @
--
-- == ByPosition vs ByName: Decision Tree
--
-- Warlock offers two matching strategies:
--
-- +------------------+------------------------------------------------+
-- | ByPosition       | Structural matching by declaration order       |
-- +------------------+------------------------------------------------+
-- | Use when:        | - Types have same shape, different names       |
-- |                  | - Converting between library types (Either,    |
-- |                  |   Maybe, tuples)                               |
-- |                  | - No field naming conventions                  |
-- +------------------+------------------------------------------------+
-- | ByName Config    | Semantic matching with configurable rules      |
-- +------------------+------------------------------------------------+
-- | Use when:        | - Field names follow conventions               |
-- |                  | - Need field evolution (add/remove fields)     |
-- |                  | - Require computed or virtual fields           |
-- |                  | - Multi-constructor ADTs with naming patterns  |
-- +------------------+------------------------------------------------+
--
-- __Rule of thumb:__ Start with 'ByName' for record types, use 'ByPosition'
-- for positional constructors or when converting standard library types.
--
-- = Getting Started (Basic Examples) #basics#
--
-- == Simple Record Mapping
--
-- The most common case: two records with the same field names.
--
-- @
-- {-\# LANGUAGE TemplateHaskell \#-}
-- {-\# LANGUAGE MultiParamTypeClasses \#-}
--
-- import Warlock
-- import qualified Witch as W
--
-- data Person = Person { name :: String, age :: Int }
-- data Employee = Employee { name :: String, age :: Int }
--
-- -- Generate From instance: Person → Employee
-- 'deriveAutomap' ('ByName' 'defaultConfig') ''Person ''Employee
--
-- example :: Employee
-- example = W.from (Person \"Alice\" 30)
-- @
--
-- The generated instance matches fields by name (exact match required).
--
-- == Positional Constructor Mapping
--
-- For types without field names, use 'ByPosition':
--
-- @
-- data Point2D = Point2D Int Int
-- data Vector2D = Vector2D Int Int
--
-- -- Match by position: 1st → 1st, 2nd → 2nd
-- 'deriveAutomap' 'ByPosition' ''Point2D ''Vector2D
--
-- example :: Vector2D
-- example = W.from (Point2D 3 4)  -- Vector2D 3 4
-- @
--
-- == Adding Default Values
--
-- When the destination type has fields not present in the source:
--
-- @
-- data PersonV1 = PersonV1 { personName :: String }
-- data PersonV2 = PersonV2
--   { personName :: String
--   , personAge :: Int      -- New field!
--   , personRegion :: String -- Another new field!
--   }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'datatypePrefixConfig'
--     \`'withDefaults'\`
--       [ ('personAge, [| 0 :: Int |])
--       , ('personRegion, [| \"US\" :: String |])
--       ])
--   ''PersonV1
--   ''PersonV2
--
-- example :: PersonV2
-- example = W.from (PersonV1 \"Bob\")
-- -- PersonV2 { personName = \"Bob\", personAge = 0, personRegion = \"US\" }
-- @
--
-- == Basic Field Renaming
--
-- When field names differ between types:
--
-- @
-- data Source = Source
--   { oldName :: String
--   , oldBalance :: Double
--   }
--
-- data Dest = Dest
--   { newName :: String
--   , newBalance :: Double
--   }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withRenames'\`
--       [ ('newName, 'oldName)
--       , ('newBalance, 'oldBalance)
--       ])
--   ''Source
--   ''Dest
-- @
--
-- = Naming Conventions #naming#
--
-- Haskell code often follows naming conventions. Warlock provides pre-built
-- configurations for common patterns.
--
-- == Datatype Prefix Convention
--
-- The most common pattern: fields prefixed with the type name.
--
-- @
-- data Person = Person
--   { personName :: String
--   , personAge :: Int
--   }
--
-- data Employee = Employee
--   { employeeName :: String
--   , employeeAge :: Int
--   }
--
-- -- Automatically strips \"person\" and adds \"employee\"
-- 'deriveAutomap' ('ByName' 'datatypePrefixConfig') ''Person ''Employee
-- @
--
-- How it works:
--
-- 1. Strip source type prefix: @personName@ → @Name@
-- 2. Add destination type prefix: @Name@ → @employeeName@
-- 3. Case handled automatically: @Person@ → @person@, @Employee@ → @employee@
--
-- == Constructor Prefix Convention
--
-- Less common but still used: fields prefixed with constructor name.
--
-- @
-- data Result
--   = Success { successValue :: String, successTime :: Int }
--   | Failure { failureError :: String, failureTime :: Int }
--
-- data Response
--   = Ok { okValue :: String, okTime :: Int }
--   | Err { errError :: String, errTime :: Int }
--
-- 'deriveAutomap'
--   ('ByName' $ 'constructorPrefixConfig' \`'withConstructorMap'\`
--     [ ('Success, 'Ok)
--     , ('Failure, 'Err)
--     ])
--   ''Result
--   ''Response
-- @
--
-- == Snake Case ↔ Camel Case
--
-- For interfacing with databases or JSON APIs:
--
-- @
-- data DbUser = DbUser
--   { user_name :: String
--   , user_email :: String
--   , created_at :: String
--   }
--
-- data User = User
--   { userName :: String
--   , userEmail :: String
--   , createdAt :: String
--   }
--
-- 'deriveAutomap' ('ByName' 'snakeToCamelConfig') ''DbUser ''User
-- @
--
-- == Custom Normalization
--
-- Build your own normalization function:
--
-- @
-- import Data.Char (toUpper)
--
-- -- Remove \"internal\" prefix and uppercase first letter
-- customNormalize :: String -> String
-- customNormalize s = case 'Warlock.stripPrefix' \"internal\" s of
--   Just rest -> case rest of
--     (c:cs) -> toUpper c : cs
--     [] -> []
--   Nothing -> s
--
-- data Internal = Internal { internalData :: String }
-- data Public = Public { data_ :: String }
--
-- 'deriveAutomap'
--   ('ByName' $ 'defaultConfig'
--     \`'withNormalize'\` customNormalize
--     \`'withRenames'\` [('data_, 'internalData)])
--   ''Internal
--   ''Public
-- @
--
-- = Advanced Field Manipulation #fields#
--
-- Warlock provides three powerful mechanisms for complex field transformations:
--
-- * __Virtual fields__: Runtime computation via 'GHC.Records.HasField'
-- * __Computed fields__: Compile-time combination of multiple sources
-- * __Disassembled fields__: Splitting one source into multiple destinations
--
-- == Virtual Fields with HasField
--
-- Virtual fields use GHC's @HasField@ mechanism to access computed properties
-- that don't exist as actual fields.
--
-- @
-- {-\# LANGUAGE DataKinds \#-}
-- {-\# LANGUAGE OverloadedLabels \#-}
--
-- import GHC.Records (HasField(..))
--
-- data Person = Person
--   { firstName :: String
--   , lastName :: String
--   , age :: Int
--   }
--
-- -- Define virtual field at runtime
-- instance HasField \"fullName\" Person String where
--   getField (Person first last _) = first ++ \" \" ++ last
--
-- instance HasField \"ageGroup\" Person String where
--   getField (Person _ _ a) = if a < 18 then \"minor\" else \"adult\"
--
-- data Employee = Employee
--   { empName :: String
--   , empAge :: Int
--   , category :: String
--   }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withRules'\`
--       [ 'virtualField' 'empName #fullName  -- OverloadedLabels syntax
--       , 'rename' 'empAge 'age
--       , 'virtualField' 'category \"ageGroup\"  -- String syntax
--       ])
--   ''Person
--   ''Employee
-- @
--
-- __When to use virtual fields:__
--
-- * Source field is computed at runtime (not a real field)
-- * Single source, single destination
-- * Logic lives in @HasField@ instance
--
-- == Computed Fields (Combining Multiple Fields)
--
-- Combine multiple source fields at compile time using Template Haskell:
--
-- @
-- data Address = Address
--   { street :: String
--   , city :: String
--   , country :: String
--   }
--
-- data ShortAddress = ShortAddress
--   { location :: String
--   }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withRules'\`
--       [ 'combineFields' 'location $ do
--           street <- 'Warlock.get' 'street
--           city <- 'Warlock.get' 'city
--           country <- 'Warlock.get' 'country
--           pure [| \$street ++ \", \" ++ \$city ++ \", \" ++ \$country |]
--       ])
--   ''Address
--   ''ShortAddress
-- @
--
-- The 'Warlock.FieldGetter' monad provides:
--
-- * 'Warlock.get' - Extract field as Template Haskell expression
-- * 'Warlock.getName' - Extract field as 'Language.Haskell.TH.Name' (advanced)
-- * Compile-time checking: references to missing fields are caught
--
-- __When to use computed fields:__
--
-- * Multiple sources, single destination
-- * Compile-time transformation
-- * Need to combine or calculate from multiple fields
--
-- == Disassembled Fields (Splitting One Into Many)
--
-- Split one source field into multiple destination fields:
--
-- @
-- data FullName = FullName { fullName :: String }
-- data SplitName = SplitName
--   { firstName :: String
--   , lastName :: String
--   }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withRules'\`
--       ('disassembleFields' 'fullName
--         [ 'firstName 'Warlock..=' do
--             src <- 'Warlock.getSource'
--             pure [| case words \$src of (f:_) -> f; _ -> \"\" |]
--         , 'lastName 'Warlock..=' do
--             src <- 'Warlock.getSource'
--             pure [| case words \$src of (_:l:_) -> l; _ -> \"\" |]
--         ]))
--   ''FullName
--   ''SplitName
-- @
--
-- The 'Warlock.DisassembleGetter' monad provides:
--
-- * 'Warlock.getSource' - Get source field as TH expression
-- * 'Warlock.getSourceName' - Get source field as 'Language.Haskell.TH.Name'
--
-- __When to use disassembled fields:__
--
-- * Single source, multiple destinations
-- * Parsing or splitting structured data
-- * Extracting components from composite values
--
-- == Field Rule Composition
--
-- Rules can be composed using the builder pattern:
--
-- @
-- import Data.Function ((&))
--
-- -- Explicit builder style
-- rule1 = 'Warlock.field' 'balance & 'Warlock.fromField' 'balance_cents
-- rule2 = 'Warlock.field' 'region & 'Warlock.withDefault' [| \"US\" |]
--
-- -- Pre-composed helpers (more common)
-- rule3 = 'rename' 'newName 'oldName
-- rule4 = 'defaultTo' 'region [| \"US\" |]
-- @
--
-- = Constructor Mapping #constructors#
--
-- For multi-constructor ADTs (sum types), Warlock can map constructor names
-- with compile-time safety.
--
-- == Direct Constructor Mapping
--
-- Explicit constructor name mappings with compile-time checking:
--
-- @
-- data ShapeV1
--   = CircleV1 { radius :: Double }
--   | RectangleV1 { width :: Double, height :: Double }
--
-- data ShapeV2
--   = Circle { radius :: Double }
--   | Rectangle { width :: Double, height :: Double }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withConstructorMap'\`
--       [ ('CircleV1, 'Circle)
--       , ('RectangleV1, 'Rectangle)
--       ])
--   ''ShapeV1
--   ''ShapeV2
-- @
--
-- The Template Haskell names @'CircleV1@ and @'Circle@ are checked at compile
-- time, so typos are caught immediately.
--
-- == Suffix Transformations
--
-- Common pattern: add or remove version suffixes.
--
-- @
-- data PaymentV1
--   = CardPaymentV1 { cardNumber :: String }
--   | CashPaymentV1 { amount :: Double }
--
-- data PaymentV2
--   = CardPaymentV2 { cardNumber :: String, cvv :: String }
--   | CashPaymentV2 { amount :: Double, currency :: String }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withConstructorMap'\` 'replaceSuffix' \"V1\" \"V2\"
--     \`'withDefaults'\`
--       [ ('cvv, [| \"000\" |])
--       , ('currency, [| \"USD\" |])
--       ])
--   ''PaymentV1
--   ''PaymentV2
-- @
--
-- Available helpers:
--
-- * 'stripSuffix' @\"V1\"@ - Remove suffix: @FooV1@ → @Foo@
-- * 'addSuffix' @\"V2\"@ - Add suffix: @Foo@ → @FooV2@
-- * 'replaceSuffix' @\"V1\" \"V2\"@ - Replace: @FooV1@ → @FooV2@
--
-- == Custom Constructor Transformations
--
-- For complex patterns, provide a transformation function:
--
-- @
-- import Data.Char (toUpper)
--
-- uppercaseFirst :: String -> String
-- uppercaseFirst [] = []
-- uppercaseFirst (c:cs) = toUpper c : cs
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withConstructorMap'\` 'Warlock.Transform' uppercaseFirst)
--   ''Source
--   ''Dest
-- @
--
-- Or use a lambda directly (wrapped in 'Warlock.Transform'):
--
-- @
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withConstructorMap'\` 'Warlock.Transform' (++ \"_New\"))
--   ''Old
--   ''New
-- @
--
-- == Multi-Constructor ADTs with Field Evolution
--
-- Combining constructor mapping with field defaults:
--
-- @
-- data EventV1
--   = UserLoginV1 { userId :: Int, loginTime :: String }
--   | UserLogoutV1 { userId :: Int }
--   | ErrorOccurredV1 { errorMsg :: String }
--
-- data EventV2
--   = UserLoginV2 { userId :: Int, loginTime :: String, ipAddress :: String }
--   | UserLogoutV2 { userId :: Int, sessionDuration :: Int }
--   | ErrorOccurredV2 { errorMsg :: String, errorCode :: Int }
--
-- 'deriveAutomap'
--   ('ByName' $
--     'defaultConfig'
--     \`'withConstructorMap'\` 'replaceSuffix' \"V1\" \"V2\"
--     \`'withDefaults'\`
--       [ ('ipAddress, [| \"unknown\" |])
--       , ('sessionDuration, [| 0 |])
--       , ('errorCode, [| 500 |])
--       ])
--   ''EventV1
--   ''EventV2
-- @
--
-- = Type Generation with Warlock.Tweak #tweak#
--
-- @Warlock.Tweak@ generates new data types from existing ones, inspired by
-- TypeScript's utility types like @Pick\<T, K\>@ and @Omit\<T, K\>@.
--
-- == Pick: Selecting Specific Fields
--
-- Create a type with only selected fields:
--
-- @
-- import qualified Warlock.Tweak as Tweak
-- import Warlock.Tweak (pick)
--
-- data User = User
--   { userId :: Int
--   , userName :: String
--   , userEmail :: String
--   , userPassword :: String
--   , userCreatedAt :: String
--   }
--
-- -- Pick only public fields (TypeScript: Pick\<User, \"id\" | \"name\"\>)
-- pick ''User \"UserSummary\" ['userId, 'userName]
--
-- -- Generates:
-- -- data UserSummary = UserSummary
-- --   { userId :: Int
-- --   , userName :: String
-- --   }
-- -- instance From User UserSummary
-- @
--
-- == Omit: Excluding Specific Fields
--
-- Create a type excluding sensitive fields:
--
-- @
-- import Warlock.Tweak (omit)
--
-- -- Omit sensitive fields (TypeScript: Omit\<User, \"password\"\>)
-- omit ''User \"UserResponse\" ['userPassword]
--
-- -- Generates:
-- -- data UserResponse = UserResponse
-- --   { userId :: Int
-- --   , userName :: String
-- --   , userEmail :: String
-- --   , userCreatedAt :: String
-- --   }
-- -- instance From User UserResponse
-- @
--
-- == Prefix Operations with Tweak
--
-- Combine field selection with prefix transformations:
--
-- @
-- -- Strip \"user\" prefix from field names
-- pick ''User \"UserClean\" ['userId, 'userName, 'userEmail]
--   \`Tweak.stripPrefix\` \"user\"
--
-- -- Generates:
-- -- data UserClean = UserClean
-- --   { id :: Int
-- --   , name :: String
-- --   , email :: String
-- --   }
-- @
--
-- Available prefix operations:
--
-- * @\`Tweak.stripPrefix\` \"user\"@ - Remove prefix
-- * @\`Tweak.addPrefix\` \"dto\"@ - Add prefix
-- * @\`Tweak.replacePrefix\` (\"user\", \"api\")@ - Replace prefix
--
-- == Combining with Field Rules
--
-- @Warlock.Tweak@ supports all Warlock field rules:
--
-- @
-- import Warlock.Tweak (withRules)
--
-- pick ''Person \"PersonDTO\" ['fullName, 'age]
--   \`withRules\`
--   [ combineFields 'fullName $ do
--       first <- Warlock.get 'firstName
--       last <- Warlock.get 'lastName
--       pure [| \$first ++ \" \" ++ \$last |]
--   , rename 'age 'personAge
--   ]
-- @
--
-- == API Response Types
--
-- Common pattern: create response DTOs for web APIs:
--
-- @
-- data DbUser = DbUser
--   { dbUserId :: Int
--   , dbUserName :: String
--   , dbUserEmail :: String
--   , dbUserPasswordHash :: ByteString
--   , dbUserSalt :: ByteString
--   , dbUserCreatedAt :: UTCTime
--   , dbUserUpdatedAt :: UTCTime
--   }
--
-- -- Public API response (omit internal fields, strip prefix)
-- omit ''DbUser \"UserResponse\"
--   [ 'dbUserPasswordHash
--   , 'dbUserSalt
--   ]
--   \`Tweak.stripPrefix\` \"dbUser\"
--
-- -- Generates:
-- -- data UserResponse = UserResponse
-- --   { id :: Int
-- --   , name :: String
-- --   , email :: String
-- --   , createdAt :: UTCTime
-- --   , updatedAt :: UTCTime
-- --   }
-- @
--
-- = Higher-Kinded Data with Warlock.HKD #hkd#
--
-- @Warlock.HKD@ generates Higher-Kinded Data types where each field is wrapped
-- in a type constructor @f@. This enables powerful patterns for validation,
-- partial construction, and generic programming.
--
-- == Basic HKD Generation
--
-- @
-- {-\# LANGUAGE TemplateHaskell \#-}
-- {-\# LANGUAGE TypeFamilies \#-}
--
-- import Warlock.HKD
-- import Data.Functor.Identity
-- import Witch (from)
--
-- data Person = Person
--   { name :: String
--   , age :: Int
--   , email :: String
--   }
--
-- 'Warlock.HKD.deriveHKD'' ''Person
--
-- -- Generates:
-- -- data Person' f = Person'
-- --   { name :: f String
-- --   , age :: f Int
-- --   , email :: f String
-- --   }
-- -- type instance HKD Person f = Person' f
-- -- instance From (Person' Identity) Person
-- -- instance From Person (Person' Identity)
-- @
--
-- == Identity Wrapper Conversion
--
-- The primary use case: wrap and unwrap values in 'Data.Functor.Identity':
--
-- @
-- -- Wrap in Identity
-- let person = Person \"Alice\" 30 \"alice\@example.com\"
-- let hkdPerson = from person :: HKD Person Identity
--
-- -- Unwrap from Identity
-- let unwrapped = from hkdPerson :: Person
-- @
--
-- == Form Validation with Maybe
--
-- Build up values incrementally with 'Maybe', then validate:
--
-- @
-- -- Form data with optional fields
-- let formData = Person' (Just \"Bob\") Nothing (Just \"bob\@example.com\")
--              :: Person' Maybe
--
-- -- Validate: all fields must be present
-- validatePerson :: Person' Maybe -> Maybe Person
-- validatePerson (Person' (Just n) (Just a) (Just e)) = Just (Person n a e)
-- validatePerson _ = Nothing
--
-- case validatePerson formData of
--   Just person -> putStrLn \"Valid!\"
--   Nothing -> putStrLn \"Missing fields\"
-- @
--
-- == Integration with Barbies
--
-- Warlock.HKD generates types compatible with the @barbies@ library for
-- generic traversals:
--
-- @
-- {-\# LANGUAGE DeriveGeneric \#-}
-- {-\# LANGUAGE StandaloneDeriving \#-}
--
-- import qualified Barbies as B
-- import GHC.Generics (Generic)
--
-- data Person = Person
--   { name :: String
--   , age :: Int
--   }
--
-- deriveHKD' ''Person
--
-- deriving instance Generic (Person' f)
-- instance B.FunctorB Person'
-- instance B.TraversableB Person'
-- instance B.ApplicativeB Person'
--
-- -- Now use barbies operations
-- validateForm :: Person' Maybe -> Either [String] Person
-- validateForm p = case B.btraverse (maybe (Left [\"missing\"]) Right) p of
--   Left errs -> Left errs
--   Right identityPerson -> Right (from identityPerson)
-- @
--
-- == Custom HKD Configuration
--
-- Customize field and constructor naming:
--
-- @
-- -- Add \"hkd\" prefix to fields
-- deriveHKD (defaultHKDConfig \`withFieldPrefix\` \"hkd\") ''User
--
-- -- Use \"HKD\" suffix for constructor
-- deriveHKD (defaultHKDConfig \`withConstructorSuffix\` \"HKD\") ''Person
--
-- -- Generates: PersonHKD instead of Person'
-- @
--
-- = Real-World Patterns #realworld#
--
-- == API Versioning (V1 → V2 → V3)
--
-- Gracefully evolve your API over multiple versions:
--
-- @
-- -- Version 1: Initial release
-- data UserV1 = UserV1
--   { userV1Name :: String
--   , userV1Email :: String
--   }
--
-- -- Version 2: Added age field
-- data UserV2 = UserV2
--   { userV2Name :: String
--   , userV2Email :: String
--   , userV2Age :: Int
--   }
--
-- -- Version 3: Split name into first/last
-- data UserV3 = UserV3
--   { userV3FirstName :: String
--   , userV3LastName :: String
--   , userV3Email :: String
--   , userV3Age :: Int
--   }
--
-- -- V1 → V2: Add default age
-- deriveAutomap
--   (ByName $ datatypePrefixConfig
--     \`withDefaults\` [('userV2Age, [| 0 |])])
--   ''UserV1 ''UserV2
--
-- -- V2 → V3: Split name, keep other fields
-- deriveAutomap
--   (ByName $ datatypePrefixConfig
--     \`withRules\`
--       (disassembleFields 'userV2Name
--         [ 'userV3FirstName .= do
--             src <- getSource
--             pure [| case words \$src of (f:_) -> f; _ -> \"\" |]
--         , 'userV3LastName .= do
--             src <- getSource
--             pure [| case words \$src of (_:l:_) -> l; _ -> \"\" |]
--         ]))
--   ''UserV2 ''UserV3
--
-- -- Now you can chain conversions
-- upgradeToLatest :: UserV1 -> UserV3
-- upgradeToLatest v1 = from (from v1 :: UserV2)
-- @
--
-- == Database to Domain Model Conversion
--
-- Separate persistence layer from domain logic:
--
-- @
-- -- Persistent-generated database entity
-- data DbProduct = DbProduct
--   { dbProductId :: Int
--   , dbProductName :: String
--   , dbProductPriceCents :: Int
--   , dbProductCreatedAt :: UTCTime
--   }
--
-- -- Clean domain model
-- data Product = Product
--   { productId :: Int
--   , productName :: String
--   , productPrice :: Double  -- Different type!
--   }
--
-- deriveAutomap
--   (ByName $ customPrefixRules \"product\" \"dbProduct\"
--     \`withRules\`
--       [ mapField 'productPrice $ \\src _dst ->
--           [| fromIntegral (dbProductPriceCents \$(varE src)) / 100.0 |]
--       ])
--   ''DbProduct ''Product
-- @
--
-- == Request/Response DTOs
--
-- Create separate types for API requests and responses:
--
-- @
-- -- Internal domain model
-- data User = User
--   { userId :: Int
--   , userName :: String
--   , userEmail :: String
--   , userPasswordHash :: ByteString
--   , userCreatedAt :: UTCTime
--   }
--
-- -- API request (no ID, no timestamps)
-- pick ''User \"CreateUserRequest\" ['userName, 'userEmail, 'userPasswordHash]
--   \`Tweak.stripPrefix\` \"user\"
--
-- -- API response (no password)
-- omit ''User \"UserResponse\" ['userPasswordHash]
--   \`Tweak.stripPrefix\` \"user\"
--
-- -- Generates:
-- -- data CreateUserRequest = CreateUserRequest
-- --   { name :: String
-- --   , email :: String
-- --   , passwordHash :: ByteString
-- --   }
-- --
-- -- data UserResponse = UserResponse
-- --   { id :: Int
-- --   , name :: String
-- --   , email :: String
-- --   , createdAt :: UTCTime
-- --   }
-- @
--
-- == Nested Type Conversions
--
-- Warlock automatically handles nested conversions using @witch@'s @into@:
--
-- @
-- data PersonV1 = PersonV1 { age :: Int }
-- data PersonV2 = PersonV2 { age :: Integer }  -- Different type
--
-- deriveAutomap (ByName defaultConfig) ''PersonV1 ''PersonV2
--
-- -- Requires: instance From Int Integer (provided by witch)
--
-- -- For lists and other containers:
-- data TeamV1 = TeamV1 { members :: [PersonV1] }
-- data TeamV2 = TeamV2 { members :: [PersonV2] }
--
-- deriveAutomap (ByName defaultConfig) ''TeamV1 ''TeamV2
--
-- -- Requires: instance From PersonV1 PersonV2 (derived above)
-- -- Warlock uses witch's into to convert the list elements
-- @
--
-- == Multi-Step Transformations
--
-- Chain multiple transformations for complex scenarios:
--
-- @
-- -- Step 1: Generate DTO with Tweak
-- omit ''User \"UserDTO\" ['userPassword]
--   \`Tweak.stripPrefix\` \"user\"
--
-- -- Step 2: Generate HKD for validation
-- deriveHKD' ''UserDTO
--
-- -- Step 3: Use in form validation
-- validateUserForm :: UserDTO' Maybe -> Either [String] User
-- validateUserForm formData = do
--   -- Validate with barbies
--   dto <- btraverse (maybe (Left [\"missing\"]) Right) formData
--   -- Add back password (from separate secure input)
--   password <- getPasswordSecurely
--   pure $ User
--     { userId = id (from dto)
--     , userName = name (from dto)
--     , userEmail = email (from dto)
--     , userPassword = password
--     , userCreatedAt = now
--     }
-- @
--
-- = Advanced Techniques #advanced#
--
-- == Combining Tweak + HKD
--
-- Generate DTOs and their HKD versions for form handling:
--
-- @
-- data User = User
--   { userId :: Int
--   , userName :: String
--   , userEmail :: String
--   , userPassword :: String
--   }
--
-- -- Step 1: Create DTO (omit ID)
-- omit ''User \"UserForm\" ['userId]
--   \`Tweak.stripPrefix\` \"user\"
--
-- -- Step 2: Generate HKD for partial form data
-- deriveHKD' ''UserForm
--
-- -- Usage:
-- emptyForm :: UserForm' Maybe
-- emptyForm = UserForm' Nothing Nothing Nothing
--
-- fillName :: String -> UserForm' Maybe -> UserForm' Maybe
-- fillName n form = form { name = Just n }
-- @
--
-- == Custom Rule Generators
--
-- Create reusable field rule generators for your domain:
--
-- @
-- -- Rule generator for monetary fields (cents to dollars)
-- moneyFieldRules :: RuleGenContext -> String -> Maybe FieldRule
-- moneyFieldRules _ctx dstField
--   | \"price\" \`isPrefixOf\` dstField = Just $ mapField (mkName dstField) $
--       \\src _dst -> [| fromIntegral \$(varE src) / 100.0 |]
--   | \"cost\" \`isPrefixOf\` dstField = Just $ mapField (mkName dstField) $
--       \\src _dst -> [| fromIntegral \$(varE src) / 100.0 |]
--   | otherwise = Nothing
--
-- deriveAutomap
--   (ByName $ defaultConfig
--     \`withRuleGens\` [datatypePrefixRules, moneyFieldRules])
--   ''DbProduct ''Product
-- @
--
-- == Parameterized Types
--
-- Handle types with type parameters:
--
-- @
-- data Container a = Container { value :: a }
-- data Box a = Box { contents :: a }
--
-- -- Use deriveAutomapWith with concrete types
-- deriveAutomapWith (ByName defaultConfig)
--   [t| Container Int |]
--   [t| Box Int |]
--
-- -- Or derive for polymorphic case (if structures match)
-- deriveAutomap (ByName defaultConfig)
--   ''Container ''Box
-- @
--
-- == Performance Considerations
--
-- Warlock generates code at compile time, so there's zero runtime overhead
-- beyond the conversion logic itself. However, keep these in mind:
--
-- * __Compile times__: Large types or many derivations increase TH compile time
-- * __Code size__: Each derivation generates instance code (negligible impact)
-- * __Field rules__: Computed fields add expression complexity
-- * __Nested conversions__: Deep nesting may impact conversion performance
--
-- Tips for large codebases:
--
-- * Group related derivations in separate modules
-- * Use @ByPosition@ for simple positional types (simpler generated code)
-- * Profile if performance is a concern (usually not an issue)
--
-- = Troubleshooting Guide #troubleshooting#
--
-- == Common Compile Errors
--
-- === \"No instance for From ...\"
--
-- __Problem:__ Warlock uses @witch@'s @into@ for nested conversions.
--
-- @
-- -- Error: No instance for From Int String
-- data A = A { field :: Int }
-- data B = B { field :: String }
-- deriveAutomap (ByName defaultConfig) ''A ''B
-- @
--
-- __Solution:__ Provide the missing From instance or use a field rule:
--
-- @
-- deriveAutomap
--   (ByName $ defaultConfig
--     \`withRules\`
--       [ mapField 'field $ \\src _dst -> [| show \$(varE src) |]
--       ])
--   ''A ''B
-- @
--
-- === \"Field not found in source type\"
--
-- __Problem:__ Destination field has no corresponding source field.
--
-- __Solution:__ Add a default value or compute it:
--
-- @
-- deriveAutomap
--   (ByName $ defaultConfig \`withDefaults\` [('newField, [| defaultValue |])])
--   ''Source ''Dest
-- @
--
-- === \"Constructor count mismatch\"
--
-- __Problem:__ ByPosition requires same number of constructors.
--
-- __Solution:__ Use ByName instead, or ensure constructor counts match.
--
-- === \"Cannot mix record and positional constructors\"
--
-- __Problem:__ One type uses records, the other uses positional constructors.
--
-- __Solution:__ Both types must use the same constructor style (or use ByPosition
-- for both if they're positional).
--
-- == Debugging Generated Code
--
-- To see what code Warlock generates, use GHC's @-ddump-splices@ flag:
--
-- @
-- stack build --ghc-options=-ddump-splices
-- cabal build --ghc-options=-ddump-splices
-- @
--
-- This shows the full generated instance code, helpful for understanding
-- compilation errors.
--
-- == Type Inference Issues
--
-- Sometimes GHC needs type annotations when using @from@:
--
-- @
-- -- Ambiguous:
-- let x = from myValue
--
-- -- Fixed with type annotation:
-- let x = from myValue :: TargetType
--
-- -- Or using TypeApplications:
-- let x = from \@SourceType \@TargetType myValue
-- @
--
-- == When to Use Qualified Imports
--
-- Some Warlock names might conflict with your code:
--
-- @
-- -- Recommended for Tweak (many common names)
-- import qualified Warlock.Tweak as Tweak
--
-- -- Use: Tweak.pick, Tweak.omit, Tweak.stripPrefix
--
-- -- Main Warlock module usually doesn't need qualification
-- import Warlock
-- @
--
-- = Conclusion
--
-- You now have a comprehensive understanding of Warlock's capabilities!
--
-- __Key Takeaways:__
--
-- * Use 'ByName' for records, 'ByPosition' for positional types
-- * Leverage pre-built configs: 'datatypePrefixConfig', 'snakeToCamelConfig'
-- * Virtual fields for runtime computation, computed fields for compile-time
-- * @Warlock.Tweak@ for generating DTOs (@pick@/@omit@)
-- * @Warlock.HKD@ for validation and partial construction
-- * Combine features for powerful real-world patterns
--
-- __Next Steps:__
--
-- * Explore the main "Warlock" module documentation
-- * Check out "Warlock.Tweak" for DTO generation
-- * Learn about "Warlock.HKD" for HKD patterns
-- * Read the README for quick reference
--
-- Happy mapping! 🧙

module Warlock.Tutorial () where

-- This module is documentation-only. All exports are from Warlock, Warlock.Tweak, and Warlock.HKD.