Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template Haskell with record field name as variable?

I've got the following piece of code that implements a monad. I'm trying to use it to simplify the setting of fields with more complex logic later on.

data Rec = Rec {
    alpha :: Int,
    beta  :: Double,
} deriving (Show)
defaultRec = Rec 0 0 0

data Record r = Record { runRecord :: Rec -> (Rec, r) }
instance Monad Record where
    return r = Record $ \s -> (s, r)
    a >>= b  = Record $ \s -> let (q, r) = runRecord a s in runRecord (b r) q

createRecord f = fst $ runRecord f defaultRec

changeAlpha x  = Record $ \s -> (s { alpha = x }, ())

I would use the code like this:

myRecord = createRecord (changeAlpha 9)

This code works, but I'd like to use Template Haskell to simplify the changeAlpha function. It would be great to have something like this:

changeBeta x = $(makeChange beta) x

Now, I've gone as far as this:

changeBeta x = Record $ $([| \z -> \s -> (s { beta = z }, ()) |]) x

But as soon as I change it to this:

changeBeta f x = Record $ $([| \z -> \s -> (s { f = z }, ()) |]) x

I get this:

TestTH.hs:21:49: `f' is not a (visible) constructor field name

No variations work. Is this possible?

like image 646
Ana Avatar asked Dec 12 '11 01:12

Ana


3 Answers

The problem is that you can only splice types, expressions or lists of declarations. A record field label is neither of those, so you'll have to use TH combinators to make an expression of type Q Exp and then splice that, though you can still use Oxford brackets for the remaining parts:

makeChange :: Name -> Q Exp
makeChange x = [|
    \z -> Record $ \s -> ( $(recUpdE [| s |] [fieldExp x [| z |]]), () ) |]

To use it, you'll have to quote the name of the field you want to change:

changeBeta :: Double -> Record ()
changeBeta x = $(makeChange 'beta) x
like image 74
hammar Avatar answered Nov 19 '22 12:11

hammar


Aren't you just reinventing State(T) monad and lenses?

http://hackage.haskell.org/packages/archive/data-lens/2.0.2/doc/html/Data-Lens-Strict.html

There's data-lens-template code which generates lenses for each "record" starting with _ in a type.

like image 38
Marek Sieradzki Avatar answered Nov 19 '22 11:11

Marek Sieradzki


I think f is just a name; you need to "unquote" it, since it's from the environment, not just stuff inside the quotation [| |] brackets. I found an example of this here [ link, see section "10.1.1 Select from a tuple" ]. The unquote uses $, so I imagine your final code would look something like,

changeField f = [| \z s -> s { $(varE f) = z } |]

Unfortunately, my version of ghc (7.0.3) seems to complain about the $. (It gives a parse error.) Hope this question gets some more attention, it seems good to me (though perhaps remove the unrelated monad part).

like image 43
gatoatigrado Avatar answered Nov 19 '22 13:11

gatoatigrado