Short answer
You can sort of do it with records but probably shouldn’t. Use either separate types or composition.
Long answer
You can sort of do this (records only) via extensible records. In practice these get painful to work with when used to model an inheritance hierarchy. I don’t recommend going down this approach.
I think it’s interesting to look at why you might want such a feature. I see two in your original post:
-
Polymorphism - you want to write functions that can accept both 2d and 3d points
-
DRY - you don’t want to repeat the definition of the
x
and y
components
Composition
Composition is a good mechanism for achieving both of those goals. For example:
type Point2dPlus extra
= Point2dPlus { x : Int, y : Int } extra
-- A 2d point composed with a z component is a 3d point
type alias Point3d = Point2dPlus { z : Int }
This allows us to define functions that only care about the 2d part like:
quadrant : Point2dPlus a -> Quadrant
quandrant (Point2dPlus { x, y } _) =
-- do calculations with x and y
-- we don't care what's in rest
We can require the rest
portion to have a specific shape too such as:
add3D : Point3d -> Point3d -> Point3d
add3D (Point2dPlus p1 rest1) (Point2dPlus p2 rest2 } =
Point2dPlus { x = p1.x + p2.x, y = p1.y + p2.y } { z = rest1.z + rest2.z }
This approach is super flexible because it allows you to combo any type for the “rest” value. You can write functions that require a particular kind of “rest” value or not care because they only use the 2d portion. The fancy name for this sort of polymorphism is parametric polymorphism.
Separate types
In your particular example, I’d be tempted to just have two standalone types.
From what I know of 2d and 3d coordinate math, I think you will mostly want functions that act on only 2d or only 3d points so I don’t think polymorphism will be helpful here.
When DRYing up code we usually look for repeated characters in our text editor and try to find a way to avoid repeating them. The danger is that pieces of code that are coincidentally similar get coupled in a bad abstraction in the name of DRY. I think that may be the case here