模板Haskell与记录字段名称作为变量?

Ana*_*Ana 9 haskell template-haskell

我有以下代码实现monad.我正在尝试使用它来简化以后使用更复杂逻辑的字段设置.

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 }, ())
Run Code Online (Sandbox Code Playgroud)

我会使用这样的代码:

myRecord = createRecord (changeAlpha 9)
Run Code Online (Sandbox Code Playgroud)

这段代码有效,但我想使用Template Haskell来简化changeAlpha函数.有这样的东西会很棒:

changeBeta x = $(makeChange beta) x
Run Code Online (Sandbox Code Playgroud)

现在,我已经走了这么远:

changeBeta x = Record $ $([| \z -> \s -> (s { beta = z }, ()) |]) x
Run Code Online (Sandbox Code Playgroud)

但是一旦我改成它:

changeBeta f x = Record $ $([| \z -> \s -> (s { f = z }, ()) |]) x
Run Code Online (Sandbox Code Playgroud)

我明白了:

TestTH.hs:21:49: `f' is not a (visible) constructor field name
Run Code Online (Sandbox Code Playgroud)

没有变化的工作.这可能吗?

ham*_*mar 6

问题是您只能拼接类型,表达式或声明列表.记录字段标签既不是那些,所以你必须使用TH组合器来制作类型的表达式Q Exp然后拼接,尽管你仍然可以使用牛津括号来表示其余部分:

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

要使用它,您必须引用要更改的字段的名称:

changeBeta :: Double -> Record ()
changeBeta x = $(makeChange 'beta) x
Run Code Online (Sandbox Code Playgroud)