9

我有以下实现 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

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

4

3 回答 3

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
于 2011-12-12T08:56:35.007 回答
4

你不是在重新发明 State(T) monad 和 lens 吗?

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

有 data-lens-template 代码为类型中以 _ 开头的每个“记录”生成镜头。

于 2011-12-12T16:03:55.560 回答
1

我认为f只是一个名字;你需要“取消引用”它,因为它来自环境,而不仅仅是引号内的东西[| |]。我在这里找到了一个示例 [链接,请参阅“10.1.1 从元组中选择”部分]。unquote uses $,所以我想你的最终代码看起来像,

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

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

于 2011-12-12T08:27:16.990 回答