Martin Sulzmann
We show how to model OO in Haskell by making using of a combination of
type classes, and
existential data types.
As a running example we consider geometric objects.
We also draw a comparison among Go and Haskell.
Recall
class Shape a where
area :: a -> Int
data Square = Square Int
data Rectangle = Rec { len :: Int, width :: Int }
instance Shape Square where
area (Square x) = x * x
instance Shape Rectangle where
area r = len r * width r
class (Show a, Shape a) => ShapeExt a where
scale :: a -> Int -> a
instance ShapeExt Square where
scale (Square x) s = Square (x * s)
instance ShapeExt Rectangle where
scale r s = Rec { len = len r * s, width = width r * s }
instance Show Square where
show (Square x) = "square(" ++ show x ++ ")"
instance Show Rectangle where
show (Rec {len = l, width = w }) = "rectangle(" ++ show l ++ "," ++ show w ++ ")"Can we store two geometric objects in a list?
The following will not type check.
Here’s the type error message.
• Couldn't match expected type ‘Rectangle’
with actual type ‘Square’
• In the expression: s
In the expression: [r, s]
In an equation for ‘ls’: ls = [r, s]
The above data type declaration makes use of existential data
types in combination with a type class. Why existential? There
seems to be a universal (for all) quantor in the above definition? Yes,
we universally quantify over a but this type is not part of
the return type. Hence, the following logical equivalence
forall a. (I implies J) = (exists a. I) implies J
where J does not refer to a
which justifies the name existential data type.
In more detail, the constructor MkGeoShape has the
following type.
The argument of the constructor is any value of type a
that is an instance of the ShapeExt type class.
The following type checks.
By embedding square and rectangle into the new type
GeoShape, we can store both in a list.
What can do with elements of this list? We can apply all the
operations that are available for geometric objects by lifting
them to GeoShape.
instance Show GeoShape where
show (MkGeoShape x) = show x
instance Shape GeoShape where
area (MkGeoShape x) = area x
instance ShapeExt GeoShape where
scale (MkGeoShape x) s = MkGeoShape (scale x s)Here is an example.
The Go interface
can be represented in Haskell as follows
The Go example
var r rectangle = rectangle{1, 2}
var s square = square{3}
x := sumArea(r, s) // applied on (rectangle, square)
fmt.Printf("%d \n", x)represented in Haskell
-- Square <= GShape
sqToGShape :: Square -> GShape
sqToGShape s = MkShape s
-- Rectangle <= GShape
recToGShape :: Rectangle -> GShape
recToGShape r = MkShape r
sumArea :: GShape -> GShape -> Int
sumArea x y = area x + area y
sumEx = let r = Rec 1 2
s = Square 3
in sumArea (recToGShape r) (sqToGShape s)The only difference is that coercions such as
recToGShape need to be inserted explicitly in Haskell.
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE GADTs #-}
class Shape a where
area :: a -> Int
data Square = Square Int
data Rectangle = Rec { len :: Int, width :: Int }
instance Shape Square where
area (Square x) = x * x
instance Shape Rectangle where
area r = len r * width r
class (Show a, Shape a) => ShapeExt a where
scale :: a -> Int -> a
instance ShapeExt Square where
scale (Square x) s = Square (x * s)
instance ShapeExt Rectangle where
scale r s = Rec { len = len r * s, width = width r * s }
instance Show Square where
show (Square x) = "square(" ++ show x ++ ")"
instance Show Rectangle where
show (Rec {len = l, width = w }) = "rectangle(" ++ show l ++ "," ++ show w ++ ")"
data GeoShape = forall a. ShapeExt a => MkGeoShape a
-- Equivalent to the above using GADT notation
data GeoShape2 where
MkGeoShape2 :: ShapeExt a => a -> GeoShape2
instance Show GeoShape where
show (MkGeoShape x) = show x
instance Shape GeoShape where
area (MkGeoShape x) = area x
instance ShapeExt GeoShape where
scale (MkGeoShape x) s = MkGeoShape (scale x s)
r = Rec { width = 2, len = 3 }
s = Square 4
-- yields a type error
-- ls = [r, s]
r2 = scale r 3
s1 :: GeoShape
s1 = MkGeoShape r
s2 :: GeoShape
s2 = MkGeoShape s
shapes = [s1, s2, MkGeoShape r2]
ex2 = putStrLn (unlines (["Sum of the area of"] ++
map show shapes ++
[show (sum (map area shapes))]))
-- Go versus Haskell
data GShape = forall a. Shape a => MkShape a
instance Shape GShape where
area (MkShape x) = area x
-- Square <= GShape
sqToGShape :: Square -> GShape
sqToGShape s = MkShape s
-- Rectangle <= GShape
recToGShape :: Rectangle -> GShape
recToGShape r = MkShape r
sumArea :: GShape -> GShape -> Int
sumArea x y = area x + area y
sumEx = let r = Rec 1 2
s = Square 3
in sumArea (recToGShape r) (sqToGShape s)