scalahaskellfunctional-programmingfantasyland

Difference between Type Class and Algebraic data types


As I understand the Type Class is that it's not something concrete but just a construct for ad-hoc and parametric polymorphism. Eq and Semigroup are examples of type classes. On the other hand, there is Algebraic data type, which is a concrete composite type, for example, Either and Maybe. And they are also Functors.

So, there is a specification for Algebraic data types for JavaScript: https://github.com/fantasyland/fantasy-land. On this page, Setoid(Eq), Ord and Semigroup are also ADT. But is it correct? If so, they are composite of what types?

I also find out this article regarding type classes, and here Functors and Monads are type classes. https://typelevel.org/cats/typeclasses.html#type-classes-in-cats. And, is it means that Either and Maybe are type classes as well?

Is Eq a type class or algebraic data type? Or both? Same for Functor


Solution

  • We collect data (values, terms) into types (data types). We can collect types into type classes.

    1, "a", true, Some(1), None, ... are values. They belong to types, Int, String, Boolean, Option[Int], ... Types can belong to type classes, Eq, Ord, Functor, ...

    ADT (algebraic data type) is a data type constructed via sum and product

    // Scala
    sealed trait MyTrait
    case class MyClass1(i: Int, s: String) extends MyTrait
    case class MyClass2(b: Boolean) extends MyTrait
    
    -- Haskell
    data MyTrait = MyClass1 { i :: Int, s :: String } | MyClass2 { b :: Bool }
    

    Here MyTrait is a sum of MyClass1 and MyClass2. MyClass1 is a product of Int and String. MyClass2 is a product of single multiplier Boolean.

    There are also data types that are not ADT. E.g. function types A => B, union types A | B, intersection types A & B or A with B, existential types, etc. Scala ADT are automatically generalized ADT (GADT) in Haskell terminology. There are generalizations of ADT in dependently typed languages, Sigma- and Pi-types (i.e. dependent sum and product).

    Type classes is FP way to describe a behavior (via ad hoc polymorphism, early binding, static/compile-time dispatch). We can make a type an instance of a type class

    // type class
    trait MyTypeclass[A] {
      def foo(a: A): Unit
    }
    object MyTypeclass {
      // instances
      implicit val myTraitMyTypeclass: MyTypeclass[MyTrait] = new MyTypeclass[MyTrait] {
        override def foo(a: MyTrait): Unit = a match {
          case MyClass1(i, s) => println(s"MyTrait: MyClass1: i=$i, s=$s")
          case MyClass2(b)    => println(s"MyTrait: MyClass2: b=$b")
        }
      }
      implicit val myClass1MyTypeclass: MyTypeclass[MyClass1] = new MyTypeclass[MyClass1] {
        override def foo(a: MyClass1): Unit = println(s"MyClass1: i=${a.i}, s=${a.s}")
      }
      implicit val myClass2MyTypeclass: MyTypeclass[MyClass2] = new MyTypeclass[MyClass2] {
        override def foo(a: MyClass2): Unit = println(s"MyClass2: b=${a.b}")
      }
    }
    
    class MyTypeclass a where
      foo :: a -> IO ()
      
    instance MyTypeclass MyTrait where
      foo (MyClass1 i s) = print ("MyClass1: i=" ++ show i ++ ", s=" ++ show s)
      foo (MyClass2 b)   = print ("MyClass2: b=" ++ show b)
    

    Here MyClass1, MyClass2, MyTrait (in Haskell only MyTrait) are types, they are instances of the type class MyTypeclass.

    An alternative to typeclasses (another way to describe a behavior) is OOP inheritance (via subtyping polymorphism, late binding, dynamic/runtime dispatch)

    sealed trait MyTrait {
      def foo(): Unit
    }
    case class MyClass1(i: Int, s: String) extends MyTrait {
      override def foo(): Unit = println(s"MyClass1: i=$i, s=$s")
    }
    case class MyClass2(b: Boolean) extends MyTrait {
      override def foo(): Unit = println(s"MyClass2: b=$b")
    }
    

    We can lift an ADT into a type class. For example standard ADT

    sealed trait Option[+A]
    case class Some[+A](a: A) extends Option[A]
    case object None extends Option[Nothing]
    
    data Maybe a = Just a | Nothing
    

    can become a type class and its instances

    trait Option[A]
    
    case class Some[A](a: A)
    case object None
    type None = None.type
    
    implicit def someOption[A]: Option[Some[A]] = new Option[Some[A]] {}
    implicit val noneOption: Option[None] = new Option[None] {}
    
    class Maybe a
     
    newtype Just a = Just a
    data Nothing a = Nothing
     
    instance Maybe (Just a)
    instance Maybe (Nothing a)
    

    On this page, Setoid(Eq), Ord and Semigroup are also ADT. But is it correct? If so, they are composite of what types?

    Normally, Eq, Ord, Semigroup are not ADT. They are not constructed via sum and product. They are type classes. They describe behavior, namely how to compare elements for equality, how to compare them for order, how to add elements, what is unit element. They "consists" of all types declared as their instances, e.g. Int, String etc. (i.e. where the corresponding behavior is implemented in some specific way).

    is it means that Either and Maybe are type classes as well?

    Normally, Either and Maybe are not type classes, they are ADT. But if we really want we can lift ADT into a type class as I showed above.

    Is Eq a type class or algebraic data type? Or both? Same for Functor.

    Eq and Functor are type classes, not ADT.