这个答案是对 errfrom 的原始答案的改编,其中包含更多细节。下面的函数还可以创建镜头,而不仅仅是设置器。
tfMakeLenses 生成 Lens' s a 类型的镜头,或者根据定义,(a -> f a) -> s -> f s 用于关联数据类型。
{-# TemplateHaskell #-}
import Control.Lens.TH
import Language.Haskell.TH.Syntax
tfMakeLenses typeFamilyName = do
fieldNames <- tfFieldNames typeFamilyName
let associatedFunNames = associateFunNames fieldNames
return $ map createLens associatedFunNames
where -- Creates a function of the form:
-- funName lensFun record = fmap (\newValue -> record {fieldName=newValue}) (lensFun (fieldName record))
createLens :: (Name, Name) -> Dec
createLens (funName, fieldName) =
let lensFun = mkName "lensFunction"
recordVar = mkName "record"
valVar = mkName "newValue"
setterFunction = LamE [VarP valVar] $ RecUpdE (VarE recordVar) [(fieldName, VarE valVar)]
getValue = AppE (VarE fieldName) (VarE recordVar)
body = NormalB (AppE (AppE (VarE 'fmap) setterFunction) (AppE (VarE lensFun) getValue))
in FunD funName [(Clause [VarP lensFun, VarP recordVar] body [])]
-- Maps [Module._field1, Module._field2] to [(field1, _field1), (field2, _field2)]
associateFunNames :: [Name] -> [(Name, Name)]
associateFunNames = map funNames
where funNames fieldName = ((mkName . tail . nameBase) fieldName, (mkName . nameBase) fieldName)
-- Retrieves fields of last instance declaration of type family "t"
tfFieldNames t = do
FamilyI _ ((DataInstD _ _ _ _ ((RecC _ fields):_) _):_) <- reify t
let fieldNames = flip map fields $ \(name, _, _) -> name
return fieldNames
用法:将类型姓氏传递给tfMakeLenses。将为调用之前的最后一个类型族实例创建镜头。
class SomeClass t where
data SomeData t :: * -> *
data MyData = MyData Int
instance SomeClass MyData where
data SomeData MyData a = SomeData {_a :: a, _b :: a
tfMakeLenses ''SomeData