Haskell 我可以打部分记录吗?

Haskell 我可以打部分记录吗?,haskell,types,record,Haskell,Types,Record,我有一个带有姓名和id的Person记录,还有一个函数createPerson,它返回一个Person,不带id,将UUID的生成留给调用者: -- Person.hs import Data.UUID (UUID) data Person = Person { name :: String, id :: UUID } createPerson name = Person { name = name } 是否有一种方法可以在没有id的情况下键入人员,通知调用方id人员将引发异常?我考虑定义

我有一个带有姓名和id的
Person
记录,还有一个函数
createPerson
,它返回一个
Person
,不带
id
,将UUID的生成留给调用者:

-- Person.hs
import Data.UUID (UUID)

data Person = Person { name :: String, id :: UUID }

createPerson name = Person { name = name }
是否有一种方法可以在没有
id的情况下键入
人员
,通知调用方
id人员
将引发异常?
我考虑定义
PartialPerson
,如下所示:

data PartialPerson = { name :: String }

但当我想添加或更改字段时,这会很快变得很麻烦。

第一种方法:

您可以使用
可能的UUID
定义

data Person=Person{name::String,id::Maybe UUID}
现在,您可以定义一些有用的函数:

partialPerson::String->Person
partialPerson n=Person{name=n,id=Nothing}
getId::Person->UUID
getId(Person_uid)(只是uuid))=uuid
getId=错误“人员没有UUID”
但是
getId
是不安全的函数,所以在使用它时必须小心

第二种方式:

您可以定义新的数据类型:

data PartialPerson=PartialPerson{name::String}
。。。并定义用于在这些类型之间转换的函数:

withId::PartialPerson->UUID->Person
withId(PartialPerson n)uuid=个人n uuid
withoutId::Person->PartialPerson
无ID(人员n uu)=部分人员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
第三种选择的问题是,将功能同时用于两种不同的人会变得更加麻烦

另外,从JSON自动派生的
FromJSON
ToJSON
实例可能会有不需要的嵌套


而且,当有多个可选属性时,它的可扩展性不是很强。

Person
参数化以支持有id和无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的记录的简单类型是
UUID->Person
。问题是你不能打印它或检查它的其他字段。也许你可以考虑。我不知道为什么你会定义<代码> GETId <代码> > <代码> ID::人->也许uuid >(忽略代码< > ID <代码>可能不是一个好的域名。@chepner为什么说
id
不是一个好的字段名?因为
id::a->a
已经是一个函数了。相关谈话(2016)还有一个选项:
data Person f=Person{name::String,uuid::f uuid};键入PersonWithId=个人身份;键入PersonWithoutId=Person(Const())
。它看起来类似于您的第二个选项,但有一些。除了链接中提到的“rank2classes”库之外,另一个支持这种编程风格的库是“barbies”