In Haskell, if you want to create a hierarchy of types (and there will be needs at times), you use an encoding like below:
data CustomerCreatedAction = CustomerCreatedAction { ...stuff... }
data JourneyRunnerAction = JourneyRunnerAction { ...stuff... }
data Action = AC CustomerCreatedAction
| AJ JourneyRunnerAction
You could do the same in Scala.
But you could also make use of the fact that data constructors also yield their own types in Scala, like shown below:
sealed trait Action
final case class CustomerCreatedAction(...stuff...) extends Action
final case class JourneyRunnerAction(...stuff...) extends Action
Let's say in your program, you start from a point where you know the thing is an Action
but not of which kind. You use pattern matching to narrow down to the exact kind of action. In the Haskell approach shown above, the way you pass down the evidence of this narrowed down view is by unwrapping the data. In the Scala approach, the evidence is implied by the subtyping relationship.
doCustomeryThings :: CustomerCreatedAction -> Int
doCustomeryThings = ...stuff...
doJourneyeyThings :: JourneyRunnerAction -> Int
doJourneyeyThings = ...stuff...
act :: Action
act = ...stuff...
case act of
AC customerCreatedAction -> doCustomeryThings customerCreatedAction
AJ journeyRunnerAction -> doJourneyeyThings journeyRunnerAction
def doCustomeryThings(action: CustomerCreatedAction): Int = ...stuff...
def doJourneyeyThings(action: JourneyRunnerAction): Int = ...stuff...
val action: Action = ...stuff...
action match {
case c: CustomerCreatedAction => doCustomeryThings(c)
case j: JourneyRunnerAction => doJourneyeyThings(j)
}
Can you give examples of cases where the approach used in Haskell code above gives advanatages over the one used in Scala code?