packages feed

warlock-0.1.0.0: src/Warlock/HKD.hs

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

-- |
-- Module      : Warlock.HKD
-- Description : Higher-Kinded Data (HKD) type generation
-- License     : MIT
--
-- = Overview
--
-- Generate HKD versions of your types for use with @barbies@ and other
-- HKD libraries. Automatically creates 'Witch.From' instances for conversion
-- between regular types and their Identity-wrapped HKD equivalents.
--
-- = Quick Example
--
-- @
-- {-# LANGUAGE TemplateHaskell #-}
-- {-# LANGUAGE TypeFamilies #-}
-- import Warlock.HKD
-- import Data.Functor.Identity
-- import Witch (from)
--
-- data Person = Person
--   { name :: String
--   , age :: Int
--   }
--
-- deriveHKD' ''Person
--
-- -- Generates:
-- -- type instance HKD Person f = Person' f
-- -- data Person' f = Person' { name :: f String, age :: f Int }
-- -- instance From (HKD Person Identity) Person
-- -- instance From Person (HKD Person Identity)
--
-- -- Usage:
-- let person = Person "Alice" 30
-- let hkdPerson = from person :: HKD Person Identity
-- let unwrapped = from hkdPerson :: Person
--
-- -- Usage with barbies:
-- import qualified Barbies as B
-- validatePerson :: Person' Maybe -> Either [String] Person
-- validatePerson p = B.btraverse (maybe (Left ["missing"]) Right) p
-- @
--
-- = Use Cases
--
-- * **Validation**: Wrap fields in Maybe for partial construction
-- * **Form handling**: Build up data incrementally
-- * **Serialization**: Handle optional fields gracefully
-- * **Testing**: Generate test data with default values
-- * **Barbies integration**: Use with btraverse, bpure, etc.

module Warlock.HKD
  ( -- * Type Family
    HKD

    -- * Template Haskell Generation
  , deriveHKD
  , deriveHKD'

    -- * Configuration
  , HKDConfig(..)
  , defaultHKDConfig
  , withFieldTransform
  , withConstructorTransform
  , withFieldPrefix
  , withConstructorSuffix
  , withoutFromInstances
  ) where

import Language.Haskell.TH
import qualified Data.Kind as Kind
import Data.Functor.Identity (Identity(..))
import qualified Witch

--------------------------------------------------------------------------------
-- Type Family

-- | Higher-Kinded Data type family.
--
-- Transforms a regular type into its HKD equivalent where each field
-- is wrapped in a type constructor @f@.
--
-- @
-- type instance HKD Person f = Person' f
-- @
type HKD :: Kind.Type -> (Kind.Type -> Kind.Type) -> Kind.Type
type family HKD a f

--------------------------------------------------------------------------------
-- Configuration

-- | Configuration for HKD generation.
--
-- Controls how the HKD type and its components are named, and whether
-- to generate From instances.
data HKDConfig = HKDConfig
  { hkdFieldNameTransform :: String -> String
    -- ^ Transform field names. Default: id (keep original names)
  , hkdConstructorNameTransform :: String -> String
    -- ^ Transform constructor names. Default: (++ "'") (add prime suffix)
  , hkdGenerateFromInstances :: Bool
    -- ^ Auto-generate From instances. Default: True
  }

-- | Default HKD configuration.
--
-- - Field names: unchanged
-- - Constructor names: add prime suffix (@Person@ → @Person'@)
-- - Generate From instances: yes
defaultHKDConfig :: HKDConfig
defaultHKDConfig = HKDConfig
  { hkdFieldNameTransform = id
  , hkdConstructorNameTransform = (++ "'")
  , hkdGenerateFromInstances = True
  }

-- | Set custom field name transform function.
--
-- @
-- deriveHKD (defaultHKDConfig \`withFieldTransform\` (\"hkd\" ++)) ''Person
-- @
withFieldTransform :: (String -> String) -> HKDConfig -> HKDConfig
withFieldTransform f cfg = cfg { hkdFieldNameTransform = f }

-- | Set custom constructor name transform function.
--
-- @
-- deriveHKD (defaultHKDConfig \`withConstructorTransform\` (++ "HKD")) ''Person
-- @
withConstructorTransform :: (String -> String) -> HKDConfig -> HKDConfig
withConstructorTransform f cfg = cfg { hkdConstructorNameTransform = f }

-- | Add prefix to field names.
--
-- @
-- deriveHKD (defaultHKDConfig \`withFieldPrefix\` "hkd") ''Person
-- -- name → hkdname
-- @
withFieldPrefix :: HKDConfig -> String -> HKDConfig
withFieldPrefix cfg prefix = withFieldTransform (prefix ++) cfg

-- | Add suffix to constructor names.
--
-- @
-- deriveHKD (defaultHKDConfig \`withConstructorSuffix\` "HKD") ''Person
-- -- Person → PersonHKD
-- @
withConstructorSuffix :: HKDConfig -> String -> HKDConfig
withConstructorSuffix cfg suffix = withConstructorTransform (++ suffix) cfg

-- | Disable automatic From instance generation.
withoutFromInstances :: HKDConfig -> HKDConfig
withoutFromInstances cfg = cfg { hkdGenerateFromInstances = False }

--------------------------------------------------------------------------------
-- Template Haskell Generation

-- | Generate HKD type with custom configuration.
--
-- @
-- deriveHKD defaultHKDConfig ''Person
-- deriveHKD (defaultHKDConfig \`withFieldPrefix\` "hkd") ''Person
-- @
deriveHKD :: HKDConfig -> Name -> Q [Dec]
deriveHKD config typeName = do
  -- Extract type information
  info <- reify typeName
  (typeVars, constructors) <- extractTypeInfo info

  -- Generate HKD data type
  let hkdTypeName = mkName $ hkdConstructorNameTransform config (nameBase typeName)
  hkdDataDecl <- generateHKDDataType config hkdTypeName typeVars constructors

  -- Generate type family instance
  let fVar = mkName "f"
  let typeFamilyInst = generateTypeFamilyInstance typeName hkdTypeName typeVars fVar

  -- Generate From instances if enabled
  fromInstances <- if hkdGenerateFromInstances config
    then generateFromInstances config typeName hkdTypeName typeVars constructors
    else pure []

  pure $ hkdDataDecl : typeFamilyInst : fromInstances

-- | Generate HKD type with default configuration.
--
-- @
-- deriveHKD' ''Person
-- @
deriveHKD' :: Name -> Q [Dec]
deriveHKD' = deriveHKD defaultHKDConfig

--------------------------------------------------------------------------------
-- Implementation: Extract Type Info

extractTypeInfo :: Info -> Q ([TyVarBndr BndrVis], [Con])
extractTypeInfo info = case info of
  TyConI (DataD _ _ typeVars _ cons _) ->
    pure (typeVars, cons)
  TyConI (NewtypeD _ _ typeVars _ con _) ->
    pure (typeVars, [con])
  _ -> fail $ "Warlock.HKD: Cannot derive HKD for " ++ show info

--------------------------------------------------------------------------------
-- Implementation: Generate HKD Data Type

generateHKDDataType :: HKDConfig -> Name -> [TyVarBndr BndrVis] -> [Con] -> Q Dec
generateHKDDataType config hkdTypeName typeVars constructors = do
  let fVar = mkName "f"
  let fTyVar = PlainTV fVar BndrReq

  -- Add 'f' as the last type variable
  let allTyVars = typeVars ++ [fTyVar]

  -- Transform constructors
  hkdCons <- mapM (transformConstructor config fVar) constructors

  pure $ DataD [] hkdTypeName allTyVars Nothing hkdCons []

transformConstructor :: HKDConfig -> Name -> Con -> Q Con
transformConstructor config fVar con = case con of
  RecC conName fields -> do
    hkdFields <- mapM (transformField config fVar) fields
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    pure $ RecC hkdConName hkdFields

  NormalC conName fields -> do
    hkdFields <- mapM (transformBangType config fVar) fields
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    pure $ NormalC hkdConName hkdFields

  InfixC left conName right -> do
    hkdLeft <- transformBangType config fVar left
    hkdRight <- transformBangType config fVar right
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    pure $ InfixC hkdLeft hkdConName hkdRight

  _ -> fail "Warlock.HKD: GADT constructors not supported"

transformField :: HKDConfig -> Name -> VarBangType -> Q VarBangType
transformField config fVar (fieldName, bndr, fieldType) = do
  let hkdFieldName = mkName $ hkdFieldNameTransform config (nameBase fieldName)
  let hkdFieldType = AppT (VarT fVar) fieldType
  pure (hkdFieldName, bndr, hkdFieldType)

transformBangType :: HKDConfig -> Name -> BangType -> Q BangType
transformBangType _config fVar (bndr, fieldType) = do
  let hkdFieldType = AppT (VarT fVar) fieldType
  pure (bndr, hkdFieldType)

--------------------------------------------------------------------------------
-- Implementation: Generate Type Family Instance

generateTypeFamilyInstance :: Name -> Name -> [TyVarBndr BndrVis] -> Name -> Dec
generateTypeFamilyInstance origTypeName hkdTypeName typeVars fVar =
  let -- Build the original type applied to its type variables
      origTypeVarNames = map getTyVarName typeVars
      origType = foldl AppT (ConT origTypeName) (map VarT origTypeVarNames)

      -- Build the HKD type applied to type variables + f
      hkdType = foldl AppT (ConT hkdTypeName) (map VarT (origTypeVarNames ++ [fVar]))

      -- type instance HKD OrigType f = HKDType' f
      lhs = AppT (AppT (ConT ''HKD) origType) (VarT fVar)
  in TySynInstD (TySynEqn Nothing lhs hkdType)
  where
    getTyVarName :: TyVarBndr BndrVis -> Name
    getTyVarName (PlainTV n _) = n
    getTyVarName (KindedTV n _ _) = n

--------------------------------------------------------------------------------
-- Implementation: Generate From Instances

generateFromInstances :: HKDConfig -> Name -> Name -> [TyVarBndr BndrVis] -> [Con] -> Q [Dec]
generateFromInstances config origTypeName hkdTypeName typeVars constructors = do
  -- Generate: From (HKD OrigType Identity) OrigType
  unwrapInst <- generateUnwrapInstance config origTypeName hkdTypeName typeVars constructors

  -- Generate: From OrigType (HKD OrigType Identity)
  wrapInst <- generateWrapInstance config origTypeName hkdTypeName typeVars constructors

  pure [unwrapInst, wrapInst]

generateUnwrapInstance :: HKDConfig -> Name -> Name -> [TyVarBndr BndrVis] -> [Con] -> Q Dec
generateUnwrapInstance config origTypeName hkdTypeName typeVars constructors = do
  let typeVarNames = map getTyVarName typeVars
  let origType = foldl AppT (ConT origTypeName) (map VarT typeVarNames)
  -- Use concrete HKD type instead of type family for instance head
  -- Apply Identity as a type constructor, not a type variable
  let hkdType = foldl AppT (ConT hkdTypeName) (map VarT typeVarNames ++ [ConT ''Identity])

  -- Generate from clauses for each constructor
  clauses <- mapM (generateUnwrapClause config) constructors

  let fromMethod = FunD (mkName "from") clauses

  pure $ InstanceD Nothing [] (AppT (AppT (ConT ''Witch.From) hkdType) origType) [fromMethod]
  where
    getTyVarName (PlainTV n _) = n
    getTyVarName (KindedTV n _ _) = n

generateUnwrapClause :: HKDConfig -> Con -> Q Clause
generateUnwrapClause config con = case con of
  RecC conName fields -> do
    -- Generate pattern: HKDCon' (Identity f1) (Identity f2) ...
    fieldVars <- mapM (\_ -> newName "x") fields
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    -- Use transformed field names for pattern matching
    let transformedFields = [(mkName $ hkdFieldNameTransform config (nameBase fname), var) | ((fname, _, _), var) <- zip fields fieldVars]
    let hkdPattern = RecP hkdConName [(fname, ConP 'Identity [] [VarP var]) | (fname, var) <- transformedFields]

    -- Generate expression: OrigCon f1 f2 ...
    let origConExp = foldl AppE (ConE conName) (map VarE fieldVars)

    pure $ Clause [hkdPattern] (NormalB origConExp) []

  NormalC conName fields -> do
    fieldVars <- mapM (\_ -> newName "x") fields
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    let hkdPattern = ConP hkdConName [] [ConP 'Identity [] [VarP var] | var <- fieldVars]
    let origConExp = foldl AppE (ConE conName) (map VarE fieldVars)

    pure $ Clause [hkdPattern] (NormalB origConExp) []

  InfixC _ conName _ -> do
    leftVar <- newName "l"
    rightVar <- newName "r"
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    let hkdPattern = InfixP (ConP 'Identity [] [VarP leftVar]) hkdConName (ConP 'Identity [] [VarP rightVar])
    let origConExp = InfixE (Just (VarE leftVar)) (ConE conName) (Just (VarE rightVar))

    pure $ Clause [hkdPattern] (NormalB origConExp) []

  _ -> fail "Warlock.HKD: GADT constructors not supported in From instances"

generateWrapInstance :: HKDConfig -> Name -> Name -> [TyVarBndr BndrVis] -> [Con] -> Q Dec
generateWrapInstance config origTypeName hkdTypeName typeVars constructors = do
  let typeVarNames = map getTyVarName typeVars
  let origType = foldl AppT (ConT origTypeName) (map VarT typeVarNames)
  -- Use concrete HKD type instead of type family for instance head
  -- Apply Identity as a type constructor, not a type variable
  let hkdType = foldl AppT (ConT hkdTypeName) (map VarT typeVarNames ++ [ConT ''Identity])

  -- Generate from clauses for each constructor
  clauses <- mapM (generateWrapClause config) constructors

  let fromMethod = FunD (mkName "from") clauses

  pure $ InstanceD Nothing [] (AppT (AppT (ConT ''Witch.From) origType) hkdType) [fromMethod]
  where
    getTyVarName (PlainTV n _) = n
    getTyVarName (KindedTV n _ _) = n

generateWrapClause :: HKDConfig -> Con -> Q Clause
generateWrapClause config con = case con of
  RecC conName fields -> do
    -- Generate pattern: OrigCon f1 f2 ...
    fieldVars <- mapM (\_ -> newName "x") fields
    let origPattern = RecP conName [(fname, VarP var) | ((fname, _, _), var) <- zip fields fieldVars]

    -- Generate expression: HKDCon' (Identity f1) (Identity f2) ...
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    let transformedFields = [(mkName $ hkdFieldNameTransform config (nameBase fname), AppE (ConE 'Identity) (VarE var)) | ((fname, _, _), var) <- zip fields fieldVars]
    let hkdConExp = RecConE hkdConName transformedFields

    pure $ Clause [origPattern] (NormalB hkdConExp) []

  NormalC conName fields -> do
    fieldVars <- mapM (\_ -> newName "x") fields
    let origPattern = ConP conName [] [VarP var | var <- fieldVars]
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    let hkdConExp = foldl AppE (ConE hkdConName) [AppE (ConE 'Identity) (VarE var) | var <- fieldVars]

    pure $ Clause [origPattern] (NormalB hkdConExp) []

  InfixC _ conName _ -> do
    leftVar <- newName "l"
    rightVar <- newName "r"
    let origPattern = InfixP (VarP leftVar) conName (VarP rightVar)
    let hkdConName = mkName $ hkdConstructorNameTransform config (nameBase conName)
    let hkdConExp = InfixE
          (Just (AppE (ConE 'Identity) (VarE leftVar)))
          (ConE hkdConName)
          (Just (AppE (ConE 'Identity) (VarE rightVar)))

    pure $ Clause [origPattern] (NormalB hkdConExp) []

  _ -> fail "Warlock.HKD: GADT constructors not supported in From instances"