Search code examples
haskelltypesinstancetype-parameter

Why parameterised data type declaration in Haskell?


I went through a you-tube video and learnt that the person declared a new data type using parameterised data type as :

data Quad a = Quad a a a a
instance  (Show a) => Show (Quad a) where
show (Quad a b c d) = (show a) ++ " " ++  -- code continues for printing

My doubt is why do we need the first line of the code. I did something like this and it still works. My code :

data Q = Q Integer Integer Integer Integer 
instance  Show Q where
show (Q a b c d) = (show a)++" " ++ --code continues for printing

What is the use of the first line namely data Quad a = Quad a a a a , when instead we can do it by my method as shown just above ? Please Help. Thanks in advance !


Solution

  • I don't know how familiar you are with imperative programming languages like Java, but I will assume that you are familiar with generics. Now your defintion of Q would be - more or less - equivalent to something in Java like:

    public class Q {
    
        private Integer field1;
        private Integer field2;
        private Integer field3;
        private Integer field4;
    
        public Q (Integer field1, Integer field2, Integer field3, Integer field4) {
            this.field1 = field1;
            this.field2 = field2;
            this.field3 = field3;
            this.field4 = field4;
        }
    
    }
    

    (yeah I know Integer in Java is not equivalent to Integer in Haskell, but that's not an issue here).

    Now this thus limits us to working with ints. A problem might occur what to do in case we want a quaternion with doubles, or Cars. So that won't work very effectively.

    The original definition of Quad however would be something in Java like:

    public class Quad<A> {
    
        private A field1;
        private A field2;
        private A field3;
        private A field4;
    
        public Quad (A field1, A field2, A field3, A field4) {
            this.field1 = field1;
            this.field2 = field2;
            this.field3 = field3;
            this.field4 = field4;
        }
    
    }

    So you can work with a Quad<Integer> which is - more or less - equivalent to the above definition of Q. But I can easily work with Quad<Double> and Quad<Car> to construct quaternions of doubles, cars, horses,...

    Now you might think that a quaternion of horses does not make any sense. Quaternions are usually indeed specified over numbers. But there are several data structures to represent numbers: Integers can - as the name suggest - only represent integral values. Perhaps you want to allow Doubles as well. Furthermore Integer can represent all integral values (until the memory is exhausted). Although that's of course a nice feature, it comes with a cost: performance. It usually takes more time to do computations on Integers (even if they have the same value). So if you want to boost performance, you may look for Quad Int16 for instance.

    By using a type parameter you allow yourself a form of flexibility: you do not have to define a data type that looks almost the same as another one.

    Furthermore you can define - like the Youtube lessons shows - type instances where you put constraints on the a. So for all Quad as where a can be showed, then you can show the Quad a. You can also put constraints on functions. Say for instance you want to provide a way to calculate the sum of the "elements" of the quad. You can define:

    sumquad :: Num a => Quad a -> a
    sumquad (Q x y z t) = x + y + z + t

    So now you have defined a sumquad function for all Quad a types where a is an instance of the Num typeclass (and thus supports addition). If we would work with Q, there should be a sumquadQ for Q, a sumquadQDouble for QDouble (a hypothetical data QDouble = QDouble Double Double Double Double), etc. So it would require a large amount of work and would result in less elegant code.