purescript-0.15.7: src/Language/PureScript/Constants/TH.hs
{-# LANGUAGE TemplateHaskell #-}
-- | This module implements an eDSL for compactly declaring pattern synonyms
-- representing known PureScript modules and their members.
--
-- The following example assumes this module is imported qualified as TH and
-- the BlockArguments extension is used, both of which I recommend.
--
-- > $(TH.declare do
-- > TH.mod "Data.Foo" do
-- > TH.ty "SomeType"
-- > TH.asIdent do
-- > TH.var "someVariable"
-- > )
--
-- will become:
--
-- > pattern M_Data_Foo :: ModuleName
-- > pattern M_Data_Foo = ModuleName "Data.Foo"
-- >
-- > pattern SomeType :: Qualified (ProperName 'TypeName)
-- > pattern SomeType = Qualified (ByModuleName M_Data_Foo) (ProperName "SomeType")
-- >
-- > pattern I_someVariable :: Qualified Ident
-- > pattern I_someVariable = Qualified (ByModuleName M_Data_Foo) (Ident "someVariable")
--
-- All pattern synonyms must start with an uppercase letter. To prevent
-- namespace collisions, different types of pattern are distinguished by a sort
-- of Hungarian notation convention:
--
-- @
-- SomeType -- a type or class name
-- C_Ctor -- a constructor name
-- I_name -- a Qualified Ident
-- M_Data_Foo -- a module name
-- P_name -- a (module name, polymorphic string) pair
-- S_name -- a lone polymorphic string (this doesn't contain any module information)
-- @
--
-- I_, P_, and S_ patterns are all optional and have to be enabled with
-- `asIdent`, `asPair`, and `asString` modifiers respectively.
--
-- Finally, to disambiguate between identifiers with the same name (such as
-- Data.Function.apply and Data.Apply.apply), the `prefixWith` modifier will
-- modify the names of the patterns created within it.
--
-- > TH.mod "Data.Function" do
-- > TH.prefixWith "function" do
-- > TH.asIdent do
-- > TH.var "apply"
--
-- results in:
--
-- > pattern I_functionApply :: Qualified Ident
-- > pattern I_functionApply = Qualified (ByModuleName (M_Data_Function) (Ident "apply")
--
module Language.PureScript.Constants.TH
( declare
, mod
, cls, clss
, dty
, nty, ntys
, ty, tys
, var, vars
, prefixWith
, asIdent
, asPair
, asString
) where
import Protolude hiding (Type, mod)
import Control.Lens (over, _head)
import Control.Monad.Trans.RWS (RWS, execRWS)
import Control.Monad.Trans.Writer (Writer, execWriter)
import Control.Monad.Writer.Class (tell)
import Data.String (String)
import Language.Haskell.TH
import Language.PureScript.Names hiding (Name)
-- | Generate pattern synonyms corresponding to the provided PureScript
-- declarations.
declare :: Writer (Q [Dec]) () -> Q [Dec]
declare = execWriter
-- | Declare a module.
mod :: String -> ModDecs -> Writer (Q [Dec]) ()
mod mnStr inner = do
-- pattern M_Data_Foo :: ModuleName
-- pattern M_Data_Foo = ModuleName "Data.Foo"
let mn = mkModuleName mnStr
tell $ typedPatSyn mn [t| ModuleName |] [p| ModuleName $(litP $ stringL mnStr) |]
tell $ snd $ execRWS inner (mn, "", []) ()
-- | Declare a type class. The resulting pattern will use the name of the class
-- and have type `Qualified (ProperName 'ClassName)`.
cls :: String -> ModDecs
cls cn = ask >>= \(mn, prefix, _) -> tell $ mkPnPat [t| 'ClassName |] mn prefix cn
-- | Declare a list of type classes; shorthand for repeatedly calling `cls`.
clss :: [String] -> ModDecs
clss = traverse_ cls
-- | Declare a data type, given the name of the type and a list of constructor
-- names. A pattern will be created using the name of the type and have type
-- `Qualified (ProperName 'TypeName)`. A pattern will also be created for each
-- constructor prefixed with "C_", having type `Qualified (ProperName
-- 'ConstructorName)`.
dty :: String -> [String] -> ModDecs
dty dn ctors = ask >>= \(mn, prefix, _) -> do
tell $ mkPnPat [t| 'TypeName |] mn prefix dn
tell $ map fold $ traverse (mkPnPat [t| 'ConstructorName |] mn $ "C_" <> prefix) ctors
-- | Declare a data type with a singular constructor named the same as the
-- type, as is commonly the case with newtypes (but this does not require the
-- type to be a newtype in reality). Shorthand for calling `dty`.
nty :: String -> ModDecs
nty tn = dty tn [tn]
-- | Declare a list of data types with singular constructors; shorthand for
-- repeatedly calling `nty`, which itself is shorthand for `dty`.
ntys :: [String] -> ModDecs
ntys = traverse_ nty
-- | Declare a type. The resulting pattern will use the name of the type and have
-- type `Qualified (ProperName 'TypeName)`.
ty :: String -> ModDecs
ty tn = ask >>= \(mn, prefix, _) -> tell $ mkPnPat [t| 'TypeName |] mn prefix tn
-- | Declare a list of types; shorthand for repeatedly calling `ty`.
tys :: [String] -> ModDecs
tys = traverse_ ty
-- | Declare a variable, function, named instance, or generally a lower-case
-- value member of a module. The patterns created depend on which of `asPair`,
-- `asIdent`, or `asString` are used in the enclosing context.
var :: String -> ModDecs
var nm = ask >>= \(mn, prefix, vtds) -> tell $ foldMap (\f -> f mn prefix nm) vtds
-- | Declare a list of variables; shorthand for repeatedly calling `var`.
vars :: [String] -> ModDecs
vars = traverse_ var
-- | For every variable declared within, create a pattern synonym prefixed
-- with "P_" having type `forall a. (Eq a, IsString a) => (ModuleName, a)`.
asPair :: ModDecs -> ModDecs
asPair = local $ addToVars mkPairDec
-- | For every variable declared within, cerate a pattern synonym prefixed
-- with "I_" having type `Qualified Ident`.
asIdent :: ModDecs -> ModDecs
asIdent = local $ addToVars mkIdentDec
-- | For every variable declared within, cerate a pattern synonym prefixed
-- with "S_" having type `forall a. (Eq a, IsString a) => a`.
asString :: ModDecs -> ModDecs
asString = local $ addToVars mkStringDec
-- | Prefix the names of all enclosed declarations with the provided string, to
-- prevent collisions with other identifiers. For example,
-- `prefixWith "function"` would turn `I_apply` into `I_functionApply`, and
-- `C_Example` into `C_FunctionExample`.
prefixWith :: String -> ModDecs -> ModDecs
prefixWith = local . applyPrefix
-- Internals start here
type ModDecs = RWS (Name, String, [VarToDec]) (Q [Dec]) () ()
type VarToDec = Name -> String -> String -> Q [Dec]
addToVars :: VarToDec -> (a, b, [VarToDec]) -> (a, b, [VarToDec])
addToVars f (a, b, fs) = (a, b, f : fs)
applyPrefix :: String -> (a, String, c) -> (a, String, c)
applyPrefix prefix (a, prefix', c) = (a, camelAppend prefix' prefix, c)
cap :: String -> String
cap = over _head toUpper
camelAppend :: String -> String -> String
camelAppend l r = if null l then r else l <> cap r
-- "Data.Foo" -> M_Data_Foo
mkModuleName :: String -> Name
mkModuleName = mkName . ("M_" <>) . map (\case '.' -> '_'; other -> other)
-- "I_" -> "fn" -> "foo" -> I_fnFoo
-- "I_" -> "" -> "foo" -> I_foo
mkPrefixedName :: String -> String -> String -> Name
mkPrefixedName tag prefix = mkName . (tag <>) . camelAppend prefix
-- 'TypeName -> M_Data_Foo -> "Function" -> "Foo" ->
-- pattern FunctionFoo :: Qualified (ProperName 'TypeName)
-- pattern FunctionFoo = Qualified (ByModuleName M_Data_Foo) (ProperName "Foo")
mkPnPat :: Q Type -> VarToDec
mkPnPat pnType mn prefix str = typedPatSyn (mkName $ cap prefix <> str)
[t| Qualified (ProperName $pnType) |]
[p| Qualified (ByModuleName $(conP mn [])) (ProperName $(litP $ stringL str)) |]
-- M_Data_Foo -> "function" -> "foo" ->
-- pattern I_functionFoo :: Qualified Ident
-- pattern I_functionFoo = Qualified (ByModuleName M_Data_Foo) (Ident "foo")
mkIdentDec :: VarToDec
mkIdentDec mn prefix str = typedPatSyn (mkPrefixedName "I_" prefix str)
[t| Qualified Ident |]
[p| Qualified (ByModuleName $(conP mn [])) (Ident $(litP $ stringL str)) |]
-- M_Data_Foo -> "function" -> "foo" ->
-- pattern P_functionFoo :: forall a. (Eq a, IsString a) => (ModuleName, a)
-- pattern P_functionFoo = (M_Data_Foo, "foo")
mkPairDec :: VarToDec
mkPairDec mn prefix str = typedPatSyn (mkPrefixedName "P_" prefix str)
[t| forall a. (Eq a, IsString a) => (ModuleName, a) |]
[p| ($(conP mn []), $(litP $ stringL str)) |]
-- _ -> "function" -> "foo" ->
-- pattern S_functionFoo :: forall a. (Eq a, IsString a) => a
-- pattern S_functionFoo = "foo"
mkStringDec :: VarToDec
mkStringDec _ prefix str = typedPatSyn (mkPrefixedName "S_" prefix str)
[t| forall a. (Eq a, IsString a) => a |]
(litP $ stringL str)
typedPatSyn :: Name -> Q Type -> Q Pat -> Q [Dec]
typedPatSyn nm t p = sequence [patSynSigD nm t, patSynD nm (prefixPatSyn []) implBidir p]