Created
May 16, 2020 11:54
-
-
Save malte-v/18f47ce6836de7dfc40460b2ff9240ff to your computer and use it in GitHub Desktop.
Haskell record to map/map to record converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE LambdaCase #-} | |
{-# LANGUAGE ScopedTypeVariables #-} | |
{-# LANGUAGE TemplateHaskell #-} | |
module Routing.Duplex.TH where | |
import Control.Isomorphism.Partial (Iso(..)) | |
import Data.Dynamic (Dynamic, fromDynamic, toDyn) | |
import Data.List (foldl') | |
import Data.Map (Map, fromList, lookup) | |
import Data.Maybe (mapMaybe) | |
import Language.Haskell.TH | |
import Language.Haskell.TH.Syntax | |
import Prelude hiding (lookup) | |
defineRecordIsomorphisms :: Name -> Q [Dec] | |
defineRecordIsomorphisms name = defineRecordIsomorphisms' name ("rec" <>) | |
defineRecordIsomorphisms' :: Name -> (String -> String) -> Q [Dec] | |
defineRecordIsomorphisms' name isoRename = do | |
info <- reify name | |
let ctors = case info of | |
TyConI (DataD _ _ _ _ cs _) -> cs | |
TyConI (NewtypeD _ _ _ _ c _) -> [c] | |
_ -> error $ show name <> " neither denotes a data or newtype declaration. Found: " <> show info | |
let recCtors = | |
mapMaybe | |
( \case | |
RecC name fields -> Just (name, fields) | |
_ -> Nothing | |
) | |
ctors | |
traverse (defineRecordIsomorphism isoRename) recCtors | |
defineRecordIsomorphism :: (String -> String) -> (Name, [VarBangType]) -> Q Dec | |
defineRecordIsomorphism isoRename (name, fields) = do | |
let renamed = mkName $ isoRename $ nameBase name | |
body <- [|Iso $(mkToMapFn fields) $(mkFromMapFn (name, fields))|] | |
pure $ FunD renamed [Clause [] (NormalB body) []] | |
where | |
mkToMapFn :: [VarBangType] -> Q Exp | |
mkToMapFn fields = do | |
let recVarName = mkName "rec" | |
LamE [VarP recVarName] | |
<$> [| | |
Just $ | |
fromList $(ListE <$> traverse (mkFieldPair recVarName) fields) | |
|] | |
where | |
mkFieldPair :: Name -> VarBangType -> Q Exp | |
mkFieldPair recVarName (fieldName, _, _) = do | |
toDynFn <- [|toDyn|] | |
pure $ | |
TupE | |
[ LitE $ StringL $ nameBase fieldName, | |
AppE toDynFn (AppE (VarE fieldName) (VarE recVarName)) | |
] | |
mkFromMapFn :: (Name, [VarBangType]) -> Q Exp | |
mkFromMapFn (name, fields) = do | |
let mapVarName = mkName "map" | |
body <- | |
DoE | |
<$> sequence | |
( fmap (mkFieldExtractStmt mapVarName) fields | |
<> [ do | |
let resultExp = | |
foldl' AppE (ConE name) $ | |
fmap (\(fieldName, _, _) -> VarE (extractedFieldVarName fieldName)) fields | |
NoBindS <$> [|pure $(pure resultExp)|] | |
] | |
) | |
pure $ LamE [VarP mapVarName] body | |
where | |
mkFieldExtractStmt :: Name -> VarBangType -> Q Stmt | |
mkFieldExtractStmt mapVarName (fieldName, _, fieldType) = | |
BindS (SigP (VarP (extractedFieldVarName fieldName)) fieldType) | |
<$> [| | |
fromDynamic | |
=<< lookup | |
$(pure $ LitE $ StringL $ nameBase fieldName) | |
$(pure $ VarE mapVarName) | |
|] | |
extractedFieldVarName :: Name -> Name | |
extractedFieldVarName fieldName = mkName $ "extracted_" <> nameBase fieldName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment