我有一段实现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
没有变化的工作。这可能吗?
答案 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部分)。