Haskell 使用记录语法中的大量构造函数替代代数数据类型

Haskell 使用记录语法中的大量构造函数替代代数数据类型,haskell,record,algebraic-data-types,Haskell,Record,Algebraic Data Types,我有一个自定义数据类型来包含景观元素(云、太阳、山等)。我需要它们的列表,所以我不能使用不同的类型和通用的类型类 它们共享构造函数中的大部分字段,但有些字段具有其他字段不具有的属性(例如,如果云层正在下雨或没有下雨) 到目前为止,我有一种具有不同构造函数的数据类型: data Element = Sun { elemColorStart :: Color, elemColorEnd :: Color, elemCoords :: Coords, elemPeriod :: Flo

我有一个自定义数据类型来包含景观元素(云、太阳、山等)。我需要它们的列表,所以我不能使用不同的类型和通用的类型类

它们共享构造函数中的大部分字段,但有些字段具有其他字段不具有的属性(例如,如果云层正在下雨或没有下雨)

到目前为止,我有一种具有不同构造函数的数据类型:

data Element = Sun {
  elemColorStart :: Color,
  elemColorEnd :: Color,
  elemCoords :: Coords,
  elemPeriod :: Float,
  elemSize :: Float,
  elemSteps :: Step,
  elemTime :: Float
}
| Cloud {
  elemKind :: CloudKind,
  elemColorMain :: Color,
  elemCoords :: Coords,
  elemRans :: [Float],
  elemSize' :: Size',
  elemSteps :: Step,
  elemTime :: Float
}
... etc
或者,我可以有一个具有所有可能属性的公共构造函数,如果不需要它们,就不初始化它们,尽管这看起来更糟


这看起来不是很“Haskell”,事实上,这种方法通常是面向对象的我做错了什么?欢迎任何其他可能的方法;这不是一个赋值,所以我没有任何约束。

将事物隔离到它们自己的数据类型中,并让
元素成为它们的总和:

data Sun = Sun {
  colorStart :: Color,
  colorEnd :: Color,
  coords :: Coords,
  period :: Float,
  size :: Float,
  steps :: Step,
  time :: Float
}

data Cloud = Cloud {
  kind :: CloudKind,
  colorMain :: Color,
  coords :: Coords,
  rans :: [Float],
  size :: Size',
  steps :: Step,
  time :: Float
}

data Element = SunElement Sun | CloudElement Cloud
现在,您可以为独立的事物提供专用的API,就像关注点分离带来的所有好处一样

哦,顺便说一句,我删除了字段名的前缀,因为现在我们有了
DuplicateRecordFields
扩展名


顺便说一句,你会发现它很有用。

Nikita的答案的一个转折点是使用一个名为
Shared
的ADT来保存在
Sun
Cloud
中找到的字段。如果您愿意,可以将其与OO的继承进行比较

data Shared = Shared {       
  coords :: Coords,     
  steps :: Step,
  time :: Float
}

data Sun = Sun {
  colorStart :: Color,
  colorEnd :: Color,
  period :: Float,
  size :: Float,
  shared :: Shared
}

data Cloud = Cloud {
  kind :: CloudKind,
  colorMain :: Color, 
  rans :: [Float],
  size :: Size',
  shared :: Shared
}

data Element = SunElement Sun | CloudElement Cloud

回答得好,我从没想过。但是前缀是存在的,因为我不想用
size
time
之类的东西污染全局名称空间,我可能需要这些东西来做其他事情。有什么简单的解决方案吗?@Lorenzo如果你不想污染名称空间,你要么需要前缀(就像你做的那样),要么在一个单独的模块中定义它们,然后
将合格的MyModule导入为M
,然后使用
M.field
。后者不是非常方便,我认为如果一个人有很多类型要添加到模块中就可以了。我会严格限制sum类型的构造函数。如果类型确实是性能关键型的,那么您可能希望使用
{-#UNPACK#-}
。通过
数据共享=共享{steps::Step,time::Float,coords::coords}
?@Lorenzo引入支持,使其更严格:
数据元素=SunElement!太阳|云元素!云
。这意味着要生成一个
元素
,您需要有一个
太阳
,而不仅仅是一个计算一个元素的thunk。语义与原始类型相同,而不是更懒惰,并且不太可能泄漏内存。解包:
data Element=SunElement{-#解包#-}!Sun | CloudElement{-#UNPACK}!云
。这将把记录解压到sum类型构造函数中,而不是有一个指向它的指针。这在操作上与您的原始版本完全相同。(续)