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

时间:2011-12-12 01:37:31

标签: 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 }, ())

我会使用这样的代码:

myRecord = createRecord (changeAlpha 9)

此代码有效,但我想使用Template Haskell来简化changeAlpha函数。有这样的事情会很棒:

changeBeta x = $(makeChange beta) x

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

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

但是一旦我改成它:

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

我明白了:

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

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

3 个答案:

答案 0 :(得分:6)

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

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

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

changeBeta :: Double -> Record ()
changeBeta x = $(makeChange 'beta) x

答案 1 :(得分:4)

Aren你刚刚重塑国家(T)monad和镜头?

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

有数据镜头模板代码,可为每个"记录"生成镜头。以_开头的类型。

答案 2 :(得分:1)

我认为f只是一个名字;你需要“取消引用”它,因为它来自环境,而不仅仅是引号[| |]括号内的内容。我在这里找到了一个例子[link,参见“10.1.1从元组中选择”一节。 unquote使用$,所以我想你的最终代码看起来像是

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

不幸的是,我的ghc版本(7.0.3)似乎抱怨$。 (它给出了一个解析错误。)希望这个问题得到更多的关注,对我来说似乎很好(尽管可能会删除无关的monad部分)。