在 Haskell 中定义新类型与持久类型的惯用方法

问题描述 投票:0回答:1

我有一个代表持久记录的类型。我想要一个非常相似的类型来表示应该发布以创建新记录的数据。

这是完整的类型:

data Record = Reading
  { id: UUID
  , value: String
  ...
  }

“新”类型与减去“id”相同,该“id”将由数据库自动生成。我如何定义这个类型?我正在使用servant来定义API。

我当前的策略是在类型和所有字段前加上“new”前缀,这有效,但对于多字段模型来说是多余的。我还看到了嵌套策略,其中我有一个共同的共享类型。我也考虑过将 id 设为可选,但我真的不想发布它。

postgresql rest haskell servant
1个回答
0
投票

您可以使用类似“更高种类的数据”的方法来实现此目的。 首先,一些进口:

{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE UndecidableInstances #-} module Example where import Data.Functor.Identity import Data.Proxy import Data.UUID import Data.Aeson import GHC.Generics

然后,定义具有更高种类类型参数的记录:

data Record f = Record { recordId :: f UUID, recordValue :: String } deriving (Generic)

Identity Functor

 为您提供此记录的一个变体,它
始终具有一个 Id。 type RecordWithId = Record Identity

使用 
Maybe

为您提供一个带有

可选
id 的变体。 type RecordWithOptionalId = Record Maybe

Proxy

 可以用作具有单个无趣“单位”值的函子。 (并且没有包装类型的值)。这让我们可以为没有 ID 的 
Record 创建一个类型。
type RecordWithoutId = Record (Proxy)

我们可以为我们的 
Show

导出

Record
deriving instance (Show (f UUID)) => Show (Record f)

需要在 

omitNothingFields = True

 实例中传递 
allowOmitedFields = True
Aeson 才能按照您的预期解析
RecordWithoutId
。这确实需要 Aeson >= 2.2.0.0 的版本(在撰写本文时该版本比最新的 Stackage Snapshot 更新)。如果此版本绑定不适合您,您可以手动实现 Aeson 实例。
instance (ToJSON (f UUID)) => ToJSON (Record f) where
    toJSON = genericToJSON defaultOptions { omitNothingFields = True, allowOmitedFields = True }

instance (FromJSON (f UUID)) => FromJSON (Record f) where
    parseJSON = genericParseJSON defaultOptions { omitNothingFields = True, allowOmitedFields = True }

用 ID 对值进行编码:
ghci> BL.putStrLn $ encode (Record {recordId = Identity nil, recordValue = "value" })
{"recordId":"00000000-0000-0000-0000-000000000000","recordValue":"value"}

对没有 ID 的值进行编码:
ghci> BL.putStrLn $ encode (Record {recordId = Proxy, recordValue = "value" })
{"recordValue":"value"}

解码没有 ID 的值
ghci> decode "{\"recordValue\":\"value\"}" :: Maybe RecordWithoutId
Just (Record {recordId = Proxy, recordValue = "value"})


© www.soinside.com 2019 - 2024. All rights reserved.