Lorenzo
Lorenzo

Reputation: 2210

Alternative to algebraic data type with lots of constructors in record syntax

I have a custom data type to contain elements of a landscape (cloud, sun, mountain, etc). I need to have a list of them, so I can't use different types and a common typeclass.

They share most of the fields in the constructor, however some have properties that other don't (for example, if the cloud is raining or not).

As of now, I have one data type with different constructors:

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

Alternatively I could have a common constructor with all the possible properties, and just not initialise them if theyre not needed, though this looks even worse.

This does not look very "Haskelly", and in fact this approach in general is quite object oriented. What am I doing wrong? Any other possible approach would be welcome; it is not an assignment so I don't really have any constraints.

Upvotes: 1

Views: 253

Answers (2)

Elmex80s
Elmex80s

Reputation: 3504

A twist on Nikita's answer is to use an ADT called Shared holding fields found in both Sun and Cloud. If you want you can compare this to inheritance from 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

Upvotes: 1

Nikita Volkov
Nikita Volkov

Reputation: 43309

Isolate the things into their own datatypes and have Element be a sum of them:

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

Now you can have dedicated APIs for things in isolation as much as all the good things that come with separation of concerns.

Oh and BTW, I've dropped the prefixes on field names, since now we have the DuplicateRecordFields extension.

BTW, you'll find this question useful.

Upvotes: 8

Related Questions