Template Haskell with record field name as variable?
-
13-03-2021 - |
Question
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?
Solution
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
OTHER TIPS
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.
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).