Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to build a DSL for looking up fields from a record in Haskell

Tags:

haskell

dsl

TL;DR: I need help figuring out how to generate code that will return one of a small number of data types (probably just Double and Bool) from various 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'm building a small DSL, and want the user to be write something like the following

circle.origin
square.side

and it will generate code similar to

origin . circle
side . square

In parsing this, I would have the strings "circle" and "origin" for example. I now need to turn those into function calls. I could obviously 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 lookup function per returned data type. Having one function per data type is practical from the DSL point of view in that it will only really deal with 2 or 3 data types. However, this hardly seems like a particularly effective way of doing things. Is there a better way (surely) of doing this? If not, is there a way that I can generate the code for the various lookup functions from the various records that I want to be able to lookup fields from?

Secondly, there's still the matter of the parsed "circle" or "square" needing to call the appropriate circle or square function. If I were to implement 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 that leaves me with having to figure out which type to enforce on the lookup function, and worse having to hand write instances for each (of many) records that I want to use this on.

Note: The fact that Circle and Square could be represented using a single ADT is incidental to my question in that this is a contrived example. The actual code will entail various very different records, of which the only thing they have in common is having fields of the same type.

like image 766
lukerandall Avatar asked Nov 02 '12 16:11

lukerandall


1 Answers

I tried using Template Haskell to provide a nice and type safe way to solve this problem. To do this, I constructed the expressions from a given string.

I suppose that Lens package can do that, but it may be a simpler and more flexible solution.

It can be used like this:

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

And is defined like this:

{-# 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 will be: x (origin circle)

Note: splitEvery is a function that splits a list into sublists taking out the given element. Example implementation:

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 heavyweight but type-safe way to create an embedded DSL with the given syntax.

like image 197
Boldizsár Németh Avatar answered Nov 18 '22 16:11

Boldizsár Németh