Generate a data declaration with TemplateHaskell - haskell

I wonder how to generate a bunch of constants based on a list of names.
I started with this working example:
ConstantCreation.hs
module ConstantCreation where
import Language.Haskell.TH
createConstant :: String -> Q [Dec]
createConstant constantName = do constantType <- newName constantName
constant <- newName constantName
return [ DataD []
constantType []
[NormalC constant []]
[] ]
MyConstants.hs
{-# LANGUAGE TemplateHaskell #-}
module MyConstants where
import ConstantCreation
$(do constantsDeclarations <- mapM createConstant
[ "MyFirstCustomConstant" ,
"MySecondCustomConstant" ]
return $ mconcat constantsDeclarations)
But things get tricky when I try to add a deriving Show.
I first tried changing the function createConstant like this:
createConstant constantName = do constantType <- newName constantName
constant <- newName constantName
return [ DataD []
constantType []
[NormalC constant []]
[GHC.Show.Show] ]
as suggested if I run the command runQ [d|data MyConstant = MyConstant deriving Show|] in GHCi, but it throws the error Not in scope: data constructor ‘GHC.Show.Show’
So I tried do define my function like this :
createConstant constantName = [d|data $(ConT $ newName constantName) = $(NormalC (newName constantName) []) deriving Show|]
but then I had the following error:
Cannot parse data constructor in a data/newtype declaration: $(NormalC
(newName constantName) [])
It would really be a pitty to have to define Show instances by hand, so I wonder what's going badly.
Thanks for any advice or explanation.

You can use ''Show to get the Type with the name that is in scope.
{-# LANGUAGE TemplateHaskell #-}
module Constant where
import Language.Haskell.TH
createConstant constantName = do
tname <- newName constantName
cname <- newName constantName
return [DataD [] tname [] [NormalC cname []] [''Show]]

Related

Assign boolean values to variables created from char list

I have two lists:
"ab" and [False,True]
Is it possible to create variables from first list and assign to them booleans from second list?
Something like this:
a = False
b = True
First the technically-yes-but-please-don't-do-this-it's-an-advanced-trick-that-should-only-be-used-if-you're-confident-this-is-really-what-you-want answer:
To generate variables guided by data you need Template Haskell.
{-# LANGUAGE TemplateHaskell #-}
module VariableGenerator where
import Language.Haskell.TH
import Language.Haskell.TH.Syntax
import Control.Monad
generateVariables :: Lift t => [Char] -> [t] -> DecsQ
generateVariables is qs = concat <$> zipWithM (\i q -> [d| $(varP $ mkName [i]) = q |]) is qs
This can then be used in another module like
{-# LANGUAGE TemplateHaskell #-}
import VariableGenerator
generateVariables "ab" [False, True]
main :: IO ()
main = do
print a
print b
Proper answer
I think what you want is simply a map.
Prelude> import qualified Data.Map as Map
Prelude Map> let m = Map.fromList $ zip "ab" [False, True]
Prelude Map> m Map.! 'a'
False
Prelude Map> m Map.! 'b'
True

Using a declaration quoter in a where statement

I am implementing a DSL that is based on using standard haskell functions/combinators to build database queries. From an implementation POV I decided to represent variables in the query like this:
newtype Variable = Var { fromVar :: Text }
this however forces the user to write Var "something" quite often, so I decided to
write a quasiquoter that does this automatically.
here is an example for the DSL:
{-# LANGUAGE OverloadedStrings #-}
maxQuery :: Query MAX
maxQuery = match
( sch `isa` "school"
$ forWhich "ranking" `labelMatches` ran $ε)
`get` [ran]
`max` [ran]
where
[sch,ran] = map Var ["sch","ran"]
what I would like it to be:
maxQuery :: Query MAX
maxQuery = match
( sch `isa` "school"
$ forWhich "ranking" `labelMatches` ran $ε)
`get` [ran]
`max` [ran]
where [defVars| sch ran |]
or something similar to this.
the quasiquoter i wrote is here:
{-# LANGUAGE TemplateHaskell #-}
module TypeDBTH where
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Quote
import Data.List.Split
import Data.Text (pack)
mkVars :: [String] -> Dec
mkVars vars = ValD
(ListP (map (VarP . mkName) vars))
(NormalB (ListE (map (\v -> AppE (ConE $ mkName "Var")
$ AppE (VarE $ mkName "pack")
(LitE $ StringL v))
vars)))
[]
defVars :: QuasiQuoter
defVars = QuasiQuoter { quoteDec = quoteVars }
--, quoteExp = expQuoteVars }
quoteVars :: String -> Q [Dec]
quoteVars = return . return . mkVars . filter (/= "") . splitOn " "
expQuoteVars :: String -> Q Exp
expQuoteVars s = return $ LetE [(mkVars . filter (/= "") . splitOn " " $ s)] (LitE $ StringL "x")
originally I only wrote quoteVars. for testing in ghci I added expQuoteVars.
However, removing the latter one now and trying to write
...
where [defVars| sch ran |]
leaves me with two errors:
lib/TypeDBQuery.hs:806:1: error:
parse error (possibly incorrect indentation or mismatched brackets)
because of the where [quasiquoter] with nothing after it
and
lib/TypeDBQuery.hs:807:5: error:
• Exception when trying to run compile-time code:
lib/TypeDBTH.hs:18:11-46: Missing field in record construction quoteExp
Code: Language.Haskell.TH.Quote.quoteExp defVars " sch ran "
• In the quasi-quotation: [defVars| sch ran |]
|
807 | x = [defVars| sch ran |]
| ^^^^^^^^^^^^^^^^^^^^
how can i use the quasiquoter for a quoteDec instead of quoteExp?
is this possible at all?
I would also be open to use it like this if this is easier then:
maxQuery :: Query MAX
maxQuery = let [defVars | sch ran |] in
$ match
( sch `isa` "school"
$ forWhich "ranking" `labelMatches` ran $ε)
`get` [ran]
`max` [ran]
i took a look at the "tutorials" and info sites of wiki.haskell.org and the TH modules but could not figure out how to do this...
https://wiki.haskell.org/Template_Haskell#What_to_do_when_you_can.27t_splice_that_there
https://wiki.haskell.org/Quasiquotation
https://wiki.haskell.org/A_practical_Template_Haskell_Tutorial
You can only use declaration quasi quotes in top-level declarations unfortunately. From the documentation:
A quasiquote may appear in place of
An expression
A pattern
A type
A top-level declaration
Instead of using TH, you could consider using OverloadedStrings:
instance IsString Variable where
fromString str = Var (pack str)
maxQuery :: Query MAX
maxQuery = match
( "sch" `isa` "school"
$ forWhich "ranking" `labelMatches` "ran" $ε)
`get` ["ran"]
`max` ["ran"]

Template Haskell on Aeson

I have a data type like this:
module My.Module
data A = A { aFoo :: Integer } deriving (Generic, Show)
And I have generic option for Aeson
import Data.Char ( toUpper, toLower )
genericOptions :: String -> Options
genericOptions prefix = defaultOptions
{ fieldLabelModifier = dropPrefix $ length prefix
, constructorTagModifier = addPrefix prefix
, omitNothingFields = True
}
where
dropPrefix l s = let remainder = drop l s
in (toLower . head) remainder : tail remainder
addPrefix p s = p ++ toUpper (head s) : tail s
So I can use it like this
instance A.FromJSON A where
parseJSON = A.genericParseJSON $ genericOptions "A"
instance A.ToJSON A where
toJSON = A.genericToJSON $ genericOptions "A"
But I realize I could use some template haskell
import Data.Aeson.TH ( deriveJSON )
import Language.Haskell.TH.Syntax ( Dec, Name, Q )
genericDeriveJSON :: Name -> Q [Dec]
genericDeriveJSON name =
deriveJSON (genericOptions (show name)) name
$(genericDeriveJSON ''A)
It throws an error:
Exception when trying to run
compile-time code:
Prelude.tail: empty list
Code: A.genericDeriveJSON ''A
It seems drop l s on dropPrefix returned an empty string meaning the value of show name is not string "A". Since I don't think I could inspect the value, anybody knows what is the value?
Try to use nameBase instead of show (which is meant for debugging and not core logic).
To see what show is doing you can look at the implementation of show which is defined as showName which is itself defined as showName' Alone, to see roughly that it constructs the fully qualified name of your type.

TemplateHaskell class name with conflict newName

I have a TemplateHaskell function creating a class name:
test :: Q [Dec]
test = do
clsname <- newName "A"
a <- newName "a"
return [
ClassD [] clsname [PlainTV a] [][]
]
The classname is generated with newName, so should be conflict free (the reason is I create the instances directly in TH and don't need it to be visible).
test
test
Schema.hs:27:1: error:
Multiple declarations of ‘A’
Declared at: Schema.hs:26:1
Schema.hs:27:1
However testing it with Debug.Trace, the name of A is indeed something like A_1627476119. This is the same in both GHC 7.10.3 and GHC8. Is this a bug or do I understand it wrong?
newName doesn't work the way you're imagining. It doesn't create a random unused symbol using the supplied string merely as a prefix, and -- as far as I can tell -- Template Haskell doesn't have a standard function to do that. However you can get the equivalent effect with:
gensym :: String -> Q Name
gensym pfx = mkName . show <$> newName pfx
which should work for your anonymous classes:
test :: Q [Dec]
test = do
clsname <- gensym "A" -- use gensym here
a <- newName "a" -- this is fine, though
return [
ClassD [] clsname [PlainTV a] [][]
]
If you're interested in the longer explanation, what newName does do is create a name that cannot be captured by "deeper" bindings, but it does this by attaching additional information to the created Name object, not by mangling the actual name. If such a Name is used to create a binding, the binding uses the original supplied name, not a mangled version.
To see this, note first that the Name created by mkName has more structure than its printable representation suggests:
GHCi> :m Language.Haskell.TH Language.Haskell.TH.Syntax
GHCi> nm <- runQ (newName "foo")
GHCi> nm
foo_16
GHCi> let Name occname nmtype = nm
GHCi> occname
OccName "foo"
GHCi> nmtype
NameU 16
GHCi>
Second, note that the quotation:
[d| one = 1 |]
is equivalent to the following do-block using newName:
do nm <- newName "one"
decl <- valD (varP nm) (normalB (litE (integerL 1))) []
return [decl]
so you can write the following:
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
$(do nm <- newName "one"
decl <- valD (varP nm) (normalB (litE (integerL 1))) []
return [decl])
main = print one
illustrating that the "one" name created by newName can be used to create a top-level binding that is referenced in the main function using its plain, unadorned, name: one. (If you created an extra copy of the splice here, you'd get the same "multiple declarations" error you got with your classes.)

split a word using HXT

I would like to know, how am i able to split a word with HXT ?
For example :
I have that ->
{-# LANGUAGE Arrows, NoMonomorphismRestriction #-}
import Text.XML.HXT.Core
import System.Environment --para uso do getArgs
data Class = Class { name ::String }
deriving (Show,Eq)
main = do
[src]<- getArgs
teams <- runX(readDocument [ withValidate no] src >>> getClass)
print teams
atTag tag = deep (isElem >>> hasName tag)
getClass = atTag "owl:Class" >>>
proc l -> do
className <- getAttrValue "rdf:about" -< l
returnA -< Class { name = className }
And i want to split the word ClassName !
Because the result of that programs (teams), gives me a set of hyperlink website (http:// ......) ! (Due to the XML file )
Can anyone give me some hints to solve it, please ?
Thank you !
You can use the function splitOn of the package split:
{-# LANGUAGE Arrows, NoMonomorphismRestriction #-}
import Text.XML.HXT.Core
import Data.List.Split (splitOn)
...
getClass = atTag "owl:Class" >>>
proc l -> do
className <- getAttrValue "rdf:about" -< l
returnA -< Class { name = splitOn "#" className !! 1 }
Example in ghci:
> import Data.List.Split
> let className = "http://www.xfront.com/owl/ontologies/camera/#Window"
> splitOn "#" className !! 1
Loading package split-0.2.2 ... linking ... done.
"Window"
The above code just works, if there is just one "#" in all of your URLs. If they are more complex, you shall have a look an the package Parsec.

Resources