How to create a DSL to search for fields from a record in Haskell

TL; DR: I need help in determining how to generate code that will return one of a small number of data types (possibly only Double and Bool) from different fields on disparate records.

Long form: Assuming the following data types

data Circle = Circle { radius :: Integer, origin :: Point } data Square = Square { side :: Integer } 

and some boilerplate code

 circle = Circle 3 (Point 0 0) square = Square 5 

I am creating a small DSL and want the user to write something like the following

 circle.origin square.side 

and it will generate code similar to

 origin . circle side . square 

When analyzing this, for example, I would have the lines "circle" and "origin." Now I need to turn them into function calls. I could have something like this:

 data Expr a = IntegerE (a -> Integer) | PointE (a -> Point) lookupF2I "side" = Just $ IntegerE side lookupF2I "radius" = Just $ IntegerE radius lookupF2I _ = Nothing lookupF2P "origin" = Just $ PointE origin lookupF2P _ = Nothing 

and have one search function for the returned data type. Having one function for each data type is practical from the point of view of DSL, since it really will only deal with 2 or 3 data types. However, this hardly seems to be a particularly effective way of doing things. Is there a better way (of course) to do this? If not, is there a way that I can generate code for various search functions from various records that I want to be able to search for fields?

Secondly, there is still the question of parsing "circle" or "square" , which should call the corresponding function circle or square . If I implemented this using type classes, I could do something like:

 instance Lookup Circle where lookupF2I "radius" = Just $ IntegerE radius lookupF2I _ = Nothing lookupF2P "origin" = Just $ PointE origin lookupF2P _ = Nothing 

but then it leaves me the opportunity to figure out what type is forcibly applied to the search function, and the worse it is to be able to manually record instances for each (of many) records that I want to use.

Note. The fact that circle and square can be represented using a single ADT relates to my question in that this is a contrived example. The actual code will entail various very different entries, the only thing they have in common is fields of the same type.

+7
source share
1 answer

I tried using Template Haskell to provide a good and safe type of solution to this problem. To do this, I built the expressions from the given string.

I believe the Lens package can do this, but it can be a simpler and more flexible solution.

It can be used as follows:

 import THRecSyntax circleOrigin = compDSL "circle.origin.x" 

And is defined as follows:

 {-# LANGUAGE TemplateHaskell #-} import Language.Haskell.TH compDSL :: String -> Q Exp compDSL s = return $ foldr1 AppE $ map (VarE . mkName) (reverse $ splitEvery '.' s) 

So the result expression would be: x (origin circle)

Note: splitEvery is a function that splits the list into sub-lists, taking out this element. Implementation Example:

 splitEvery :: Eq a => a -> [a] -> [[a]] splitEvery elem s = splitter (s,[]) where splitter (rest, res) = case elemIndex elem rest of Just dotInd -> let (fst,rest') = splitAt dotInd rest in splitter (tail rest', fst : res) Nothing -> reverse (rest : res) 

This is a reliable but safe type of inline DSL creation with the given syntax.

+1
source

All Articles