是否可以定义您自己的 Persistent / Esqueleto 镜头?

Is it possible to define your own Persistent / Esqueleto lens?

给定以下持久类型:

share
    [mkPersist sqlSettings, mkMigrate "migrateAll"]
    [persistLowerCase|
Account
    email Text
    passphrase Text
    firstName Text
    lastName Text
    deriving Eq Show Generic
|]

我认为生成的是一种镜头,即AccountEmailAccountPassphrase等。是否可以以这种方式组合这些,不一定是组合,而是说字符串连接,我经常发现自己编写了这些类型的函数:

accountFullName :: SqlExpr (Entity Account) -> SqlExpr Text
accountFullName acc = acc ^. AccountFirstName ++. val " " ++. acc ^. AccountLastName

如果我能以与Account* 类似的方式定义它就好了,这样我就可以使用^. 调用它们而不是使用原始函数,即acc ^. AccountFullName。这可能不是使用这些访问器的合适方式,但如果不是,我很想知道为什么,因为我觉得这可能有助于进一步理解 Persistent 库的这一部分,因为当我看的时候我感到很迷茫在 EntityField...

附近的代码处

这真的不可能。

(^.) :: (PersistEntity val, PersistField typ)
     => expr (Entity val)
     -> EntityField val typ
     -> expr (Value typ)

您会看到第二个参数是 EntityField val typ,它是 PersistEntity val class 中定义的类型族。这是为您的 table 预定义的,无法更改,因此此特定运算符不能按您希望的方式用于自定义访问器。


当您使用 persistLowerCase 和朋友时,它使用模板 Haskell 来解析您的定义并生成适当的数据定义。据我了解,生成了类似于以下内容的内容:

data Account = Account
  { accountEmail :: Text
  , accountPassphrase :: Text
  , accountFirstName :: Text
  , accountLastName :: Text
  }

data AccountField a where
  AccountId :: AccountField Key
  AccountEmail :: AccountField Text
  AccountPassphrase :: AccountField Text
  AccountFirstName :: AccountField Text
  AccountLastName :: AccountField Text

instance PersistEntity Account where
  data EntityField Account a = AccountField a
  ...

(这在语法上不准确,缺少很多细节,但我认为它为这种情况提供了足够的上下文。)

因此,您传递给 (^.) 的 "lens" 实际上只是与您的 table 类型 Account 关联的类型的构造函数。您不能创建新的构造函数或动态地重新关联类型族,因此您不能创建可以传递给 (^.) 的其他内容。这些访问器实际上是一成不变的。


我认为只使用原始函数最有意义。 accountFullName acc 写起来还不错,它清楚地表明您正在做的事情比仅仅提取字段值要复杂一些。