packages feed

fclabels-0.1: Data/Record/Label/TH.hs

module Data.Record.Label.TH (mkLabels) where

import Control.Monad (liftM)
import Data.Char (toLower, toUpper)
import Language.Haskell.TH ( Body (NormalB)
                           , Clause (Clause)
                           , Con (RecC)
                           , Dec (DataD, FunD)
                           , Exp (AppE, ConE, LamE, RecUpdE, VarE)
                           , Info (TyConI)
                           , Name
                           , Pat (VarP)
                           , Q
                           , mkName
                           , nameBase
                           , reify)
import Language.Haskell.TH.Syntax (VarStrictType)

mkLabels :: [Name] -> Q [Dec]
mkLabels = liftM concat . mapM mkLabels1

mkLabels1 :: Name -> Q [Dec]
mkLabels1 n = do
    i <- reify n
    let cs = case i of
                 TyConI (DataD _ _ _ cs _) -> cs -- only process data declarations
                 _ -> []
        ls = [ l | (RecC _ ls) <- cs, l <- ls ] -- we're only interested in labels of record constructors
    return $ map mkLabel ls

mkLabel :: VarStrictType -> Dec
mkLabel (name, _, ty) =
    -- Generate a name for the label:
    -- * If the original selector starts with an _, remove it and make
    --   the next character lowercase.
    -- * Otherwise, add 'l', and make the next character uppercase.
    let n = mkName $ case nameBase name of
                ('_' : c : rest) -> toLower c : rest
                (f : rest)   -> 'l' : toUpper f : rest
    in FunD n [Clause [] (NormalB (
           AppE (AppE (ConE (mkName "Label"))
                      (VarE name)) -- getter
                (LamE [VarP (mkName "b"), VarP (mkName "a")] -- setter
                      (RecUpdE (VarE (mkName "a")) [(name, VarE (mkName "b"))]))
                                   )) []]

isRec :: Con -> Bool
isRec (RecC _ _) = True
isRec _          = False