Algebraic or existential type


Identity: AlgebraicOrExistential
Category: Data MultiModule
Classifiers: data abstraction
Internal cross references:
External cross references:

Language: This has been described in terms of Haskell, but will apply to any language with abstract data types.

Description: A data type, which may be recursive, is transformed into an existential type, resembling a traditional object-oriented data representation. This transformation founders with binary functions over the data type.

data Shape                          
  = Circle Float |                    
    Rect Float Float  
                                    
area :: Shape -> Float          
area (Circle f) = pi*r^2         
area (Rect h w) = h*w
                                   
perim :: Shape -> Float 
perim (Circle f) = 2*pi*r           
perim (Rect h w) = 2*(h+w)           


data Shape
   = forall a. Sh a => Shape a

class Sh a where
   area :: a -> Float
   perim :: a -> Float

data Circle = Circle Float

instance Sh Circle
  area (Circle f)  = pi*r^2
  perim (Circle f) = 2*pi*r 

data Rect = Rect Float Float

instance Sh Rect
  area (Rect h w) = h*w
  perim (Rect h w) = 2*(h+w)

General comment:

It is possible to see this refactoring first being used left to right, to allow the addition of constructors in a localised way, and then from right to left, to permit the introduction of binary functions over the data type.

Left to right comment:

The code on the LHS collects the constructors of the type in a single place, namely the definition of Shape. Each function definition over Shape will have one case per constructor, and so if a constructor is added then each definition must be modified.

On the other hand, in the RHS code, the changes required for a new constructor are localised to one place, namely the instance declaration for the new constructor.

Right to left comment:

The RHS gives an object-oriented formulation of the data type, but is limited in not being able to represent binary operations over the Shape type in this existential format.

If a new function is to be added to the code, then each instance declaration needs to be modified, breaking locality once more.

Left to right conditions:

The functions over the data type (here the type Shape) must only take a single arguement of type Shape.

Right to left conditions:

The form of the existential type must be as if it had arisen by the left to right transformation.