We can use more smart constructors for less monad gymnastics:
set :: [Name] -> Q Exp
set names = [| \ $(varP a) $(varP r) -> $(go names (varE r)) |]
where
a = mkName "a"
r = mkName "r"
(.=) = fmap . (,)
go ns acc = case ns of
[n] -> recUpdE acc [n .= varE a]
n : zs -> recUpdE acc [n .= go zs [| $(varE n) $acc |]]
_ -> error "no fields?"
In this form, it’s easy to see that we can also make go
total¹, which simplifies it:
go ns acc = case ns of
[] -> varE a
n : zs -> recUpdE acc [n .= go zs [| $(varE n) $acc |]]
This is, of course, a fold:
go = foldr f (const (varE a))
f n z acc = recUpdE acc [n .= z [| $(varE n) $acc |]]
Now notice that we never use a
and r
as names after they’re statically declared in the lambda, only as expressions. That means we can create them as part of the lambda quote and quote them into the fold (at this point it’s clearer I think to inline go
):
set :: [Name] -> Q Exp
set names = [| \a r -> $(foldr f (const [| a |]) names [| r |]) |]
where
(.=) = fmap . (,)
f n z acc = recUpdE acc [n .= z [| $(varE n) $acc |]]
I think that’s pretty good, and if the recUpdE
and .=
bits can be replaced with a quote in the future, it’s easy to see where that would go and how it would make things even simpler. If name splicing used ₦$
instead of $
, I’d expect this to do the trick:
set :: [Name] -> Q Exp
set names = [| \a r -> $(foldr f (const [| a |]) names [| r |]) |]
where
f n z acc -> [| $acc { ₦$n = $(z [| $(varE n) $acc |]) } |]
¹ If you truly desire set []
to be an error instead of the IMO quite justifiable const
, I would add a set [] = error "..."
clause at the top.