获取记录字段的文本表示?

3

有以下记录:

data Sleep = Sleep
   { _duration :: Maybe Int
   , _drunk :: Bool
   }

有没有一种方法可以做到以下事情:
deriveSomething ''Sleep

fieldName duration :: String -- "duration"

我需要这个功能来进行类型安全的数据库字段更新,例如:

setField connection key duration (Just 50)

需要注意的是,它必须与数据库无关(因此opaleye等工具不适用)。

如果可以使用像lens这样的标准包来实现这一点,那就更好了,但我没有找到合适的工具。


我认为 aeson 在将数据序列化为 JSON 时做了类似的事情,也许你会在其中找到一些有用的东西。 - epsilonhalbe
@epsilonhalbe:看看我下面的评论。 - Philip Kamenarsky
1个回答

2
您可以使用 Data.Data 来完成此操作:
{-# LANGUAGE DeriveDataTypeable #-}
import Data.Data

data Sleep = Sleep
    { _duration :: Maybe Int
    , _drunk :: Bool
    } deriving (Typeable, Data)

fieldNames :: Data a => a -> [String]
fieldNames = constrFields . toConstr

例子:

> fieldNames (Sleep undefined undefined)
["_duration", "_drunk"]

之后您需要决定如何将名称转换为数据库列,但这应该相当容易。

这需要使用特定构造函数创建的值,请记住数据类型可以有许多构造函数。这方面没有什么办法,但您可以像下面这样:

sleepFieldNames :: [String]
sleepFieldNames = fieldNames (Sleep undefined undefined)

这样你就不必一遍又一遍地重新计算它。


需要注意的是,你需要使用 {-# LANGUAGE DeriveDataTypeable #-} 来使它工作。 - epsilonhalbe
不幸的是,我需要每个特定字段的文本表示,而不是一次性获取所有字段,也就是说,我确实需要像fieldName :: Field -> String这样的东西,其中Field是从数据类型定义中派生出来的。 - Philip Kamenarsky
@PhilipKamenarsky 我不确定是否能在不编写一些TemplateHaskell的情况下实现。 GHC实际上只将您的访问器视为函数,您实际上得到的是语法糖data Sleep = Sleep (Maybe Int) Bool; _duration :: Sleep -> Maybe Int; _drunk :: Sleep -> Bool,而实现只是为您访问位置。 还有一些额外的魔法,但如果没有一些TH来生成像某种标记镜头之类的“Field”类型的“duration”,则无法实现。 - bheklilr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接