Is it possible to expand data types with new values?
E.g.:
The following compiles:
data Axes2D = X | Y
data Axes3D = Axes2D | Z
But, the following:
data Axes2D = X | Y deriving (Show, Eq)
data Axes3D = Axes2D | Z deriving (Show, Eq)
type Point2D = (Int, Int)
type Point3D = (Int, Int, Int)
move_along_axis_2D :: Point2D -> Axes2D -> Int -> Point2D
move_along_axis_2D (x, y) axis move | axis == X = (x + move, y)
| otherwise = (x, y + move)
move_along_axis_3D :: Point3D -> Axes3D -> Int -> Point3D
move_along_axis_3D (x, y, z) axis move | axis == X = (x + move, y, z)
| axis == y = (x, y + move, z)
| otherwise = (x, y, z + move)
gives the following compiling error (move_along_axis_3D commented out doesn’t give errors):
Prelude> :l expandTypes_test.hs
[1 of 1] Compiling Main ( expandTypes_test.hs, interpreted )
expandTypes_test.hs:12:50:
Couldn't match expected type `Axes3D' with actual type `Axes2D'
In the second argument of `(==)', namely `X'
In the expression: axis == X
In a stmt of a pattern guard for
an equation for `move_along_axis_3D':
axis == X
Failed, modules loaded: none.
So is it possible to make X and Y of type Axes2D as well of type Axes3D?
If it is possible: what am I doing wrong? Else: why is it not possible?
Along with what Daniel Fischer said, to expand on why this is not possible: the problems with the kind of subtyping you want run deeper than just naming ambiguity; they make type inference a lot more difficult in general. I think Scala’s type inference is a lot more restricted and local than Haskell’s for this reason.
However, you can model this kind of thing with the type-class system:
You can then use guards to “pattern-match” on these values:
This has many of the same drawbacks as subtyping would have: uses of
axisX,axisYandaxisZwill have a tendency to become ambiguous, requiring type annotations that defeat the point of the exercise. It’s also a fair bit uglier to write type signatures with these type-class constraints, compared to using concrete types.There’s another downside: with the concrete types, when you write a function taking an
Axes2D, once you handleXandYyou know that you’ve covered all possible values. With the type-class solution, there’s nothing stopping you from passingZto a function expecting an instance ofHasAxes2D. What you really want is for the relation to go the other way around, so that you could passXandYto functions expecting a 3D axis, but couldn’t passZto functions expecting a 2D axis. I don’t think there’s any way to model that correctly with Haskell’s type-class system.This technique is occasionally useful — for instance, binding an OOP library like a GUI toolkit to Haskell — but generally, it’s more natural to use concrete types and explicitly favour what in OOP terms would be called composition over inheritance, i.e. explicitly wrapping “subtypes” in a constructor. It’s not generally much of a bother to handle the constructor wrapping/unwrapping, and it’s more flexible besides.