我可以输入部分记录吗?
Can I type a partial record?
我有一个带有名称和 ID 的 Person
记录,以及一个函数 createPerson
returns 一个没有 id
的 Person
,留下生成调用方的 UUID。:
-- Person.hs
import Data.UUID (UUID)
data Person = Person { name :: String, id :: UUID }
createPerson name = Person { name = name }
有没有办法在没有 id
的情况下键入 Person
来通知调用者 id Person
将抛出异常? 我考虑过如下定义 PartialPerson
:
data PartialPerson = { name :: String }
但是当我想添加或更改字段时,这很快就会变得很麻烦。
第一种方式:
您可以定义 Person
为 Maybe UUID
:
data Person = Person { name :: String, id :: Maybe UUID }
现在您可以定义一些有用的函数了:
partialPerson :: String -> Person
partialPerson n = Person { name = n, id = Nothing }
getId :: Person -> UUID
getId (Person _ (Just uuid)) = uuid
getId _ = error "person hasn't UUID"
但是getId
是一个不安全的函数,使用时一定要小心
第二种方式:
您可以定义新的数据类型:
data PartialPerson = PartialPerson { name :: String }
... 并定义此类函数以在这些类型之间进行转换:
withId :: PartialPerson -> UUID -> Person
withId (PartialPerson n) uuid = Person n uuid
withoutId :: Person -> PartialPerson
withoutId (Person n _) = PartialPerson n
没有身份证的人的一种可能类型是:
type PersonWithoutId = UUID -> Person
问题是我们无法打印这种类型的值,或检查它们的其他字段,因为函数是不透明的。
另一个选项是参数化类型:
data Person a = Person { name :: String, personId :: a }
type PersonWithId = Person UUID
type PersonWithoutId = Person ()
这样做的好处是您仍然可以轻松地派生出有用的类型类。
第三种选择是删除此人的 ID,只在需要时使用一对:
type PersonWithId = (UUID,Person)
或使用专用类型:
data WithId a = WithId { theId :: UUID, theValue :: a } deriving Functor
第三个选项的问题在于,拥有适用于两种人的功能变得更加麻烦。
此外,自动派生的 FromJSON
和 ToJSON
实例可能会有不需要的嵌套。
当有多个可选时,它的可扩展性不是很好属性。
使 Person
参数化以支持有和没有 id 的人的想法的变体,同时保持 id
字段 是关于 UUID,不仅仅是一些任意参数类型:
{-# LANGUAGE DataKinds, KindSignatures, GADTs #-}
data IdRequirement
= NoIdNeeded | IdOptional | IdRequired
data IdField (reqId :: IdRequirement) where
IdLess :: IdField 'NoIdNeeded
IdOmitted :: IdField 'IdOptional
IdProvided :: UUID -> IdField 'IdOptional
RequiredId :: UUID -> IdField 'IdRequired
data Person (reqId :: IdRequirement)
= Person { personName :: String
, personId :: IdField reqId }
我有一个带有名称和 ID 的 Person
记录,以及一个函数 createPerson
returns 一个没有 id
的 Person
,留下生成调用方的 UUID。:
-- Person.hs
import Data.UUID (UUID)
data Person = Person { name :: String, id :: UUID }
createPerson name = Person { name = name }
有没有办法在没有 id
的情况下键入 Person
来通知调用者 id Person
将抛出异常? 我考虑过如下定义 PartialPerson
:
data PartialPerson = { name :: String }
但是当我想添加或更改字段时,这很快就会变得很麻烦。
第一种方式:
您可以定义 Person
为 Maybe UUID
:
data Person = Person { name :: String, id :: Maybe UUID }
现在您可以定义一些有用的函数了:
partialPerson :: String -> Person
partialPerson n = Person { name = n, id = Nothing }
getId :: Person -> UUID
getId (Person _ (Just uuid)) = uuid
getId _ = error "person hasn't UUID"
但是getId
是一个不安全的函数,使用时一定要小心
第二种方式:
您可以定义新的数据类型:
data PartialPerson = PartialPerson { name :: String }
... 并定义此类函数以在这些类型之间进行转换:
withId :: PartialPerson -> UUID -> Person
withId (PartialPerson n) uuid = Person n uuid
withoutId :: Person -> PartialPerson
withoutId (Person n _) = PartialPerson n
没有身份证的人的一种可能类型是:
type PersonWithoutId = UUID -> Person
问题是我们无法打印这种类型的值,或检查它们的其他字段,因为函数是不透明的。
另一个选项是参数化类型:
data Person a = Person { name :: String, personId :: a }
type PersonWithId = Person UUID
type PersonWithoutId = Person ()
这样做的好处是您仍然可以轻松地派生出有用的类型类。
第三种选择是删除此人的 ID,只在需要时使用一对:
type PersonWithId = (UUID,Person)
或使用专用类型:
data WithId a = WithId { theId :: UUID, theValue :: a } deriving Functor
第三个选项的问题在于,拥有适用于两种人的功能变得更加麻烦。
此外,自动派生的 FromJSON
和 ToJSON
实例可能会有不需要的嵌套。
当有多个可选时,它的可扩展性不是很好属性。
使 Person
参数化以支持有和没有 id 的人的想法的变体,同时保持 id
字段 是关于 UUID,不仅仅是一些任意参数类型:
{-# LANGUAGE DataKinds, KindSignatures, GADTs #-}
data IdRequirement
= NoIdNeeded | IdOptional | IdRequired
data IdField (reqId :: IdRequirement) where
IdLess :: IdField 'NoIdNeeded
IdOmitted :: IdField 'IdOptional
IdProvided :: UUID -> IdField 'IdOptional
RequiredId :: UUID -> IdField 'IdRequired
data Person (reqId :: IdRequirement)
= Person { personName :: String
, personId :: IdField reqId }