In order to grasp better typeclasses (starting pretty much form scratch) I had a go at modelling 2-D shapes with area calculations, like this:
module TwoDShapes where
class TwoDShape s where
area :: s -> Float
data Circle = Circle Float deriving Show
aCircle radius | radius < 0 = error "circle radius must be non-negative"
| otherwise = Circle radius
instance TwoDShape Circle where
area (Circle radius) = pi * radius * radius
data Ellipse = Ellipse Float Float deriving Show
anEllipse axis_a axis_b | axis_a < 0 || axis_b < 0 = error "ellipse axis length must be non-negative"
| otherwise = Ellipse axis_a axis_b
instance TwoDShape Ellipse where
area (Ellipse axis_a axis_b) = pi * axis_a * axis_b
And so on for other kinds of shape.
This is fine but it occurred to me to try this:
module TwoDShapes where
class TwoDShape s where
area :: s -> Float
data TwoDShapeParams = TwoDShapeParams Float Float Float deriving Show
instance TwoDShape TwoDShapeParams where
area (TwoDShapeParams length_a length_b constant) = foldl (*) 1 [length_a, length_b, constant]
aCircle radius | radius < 0 = error "circle radius must be non-negative"
| otherwise = TwoDShapeParams radius radius pi
anEllipse axis_a axis_b | axis_a < 0 || axis_b < 0 = error "ellipse axis length must be non-negative"
| otherwise = TwoDShapeParams axis_a axis_b pi
etc. which is also fine. With the goal of information hiding I change the module declaration to look like this:
module TwoDShapes (TwoDShape, area, aCircle, anEllipse, aRectangle, aTriangle)
and slightly to my surprise this 1) works and 2) in ghci aCircle evaluates to TwoDShapeParams 1.0 1.0 3.1415927 which is true but I don’t understand how the type TwoDShapeParams is visible outside the module. I’m not sure what I was expecting, but not this.
What I’d really like is for the typeclass, it’s method and the “smart constructors” to be visible outside the module and nothing else. Can that be done?
Although the representation of
TwoDShapesis hidden, you have derived aShowinstance for it, which allows an arbitrary value of typeTwoDShapesto be converted to aString, so this is the source of the information leakage. A truly abstract type should not define aShowinstance, or indeed aDatainstance which similarly exposes information about the representation. It’s fine to have a way to convert your type to aString, as long as theStringis independent of the representation (see theShowinstances ofData.Map.MapandData.Array.Arrayfor good examples of this).Note that the module system is doing its job: you still can’t refer to the
TwoDShapesconstructor outside of the module that defines it.