Home > OS >  Difference between Type Class and Algebraic data types
Difference between Type Class and Algebraic data types

Time:12-31

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

CodePudding user response:

The underlying root of your confusion is distinguishing concepts from implementations.
So let's try to make some of those clearer.

Typeclasses

Is a pattern to achieve polymorphism; punctually, ad-hoc polymorphism.

In some languages those are a feature provided by the language; e.g. Haskell or Rust.
Others, allow you to express them using other features; e.g. Scala (using implicits).
And finally, in theory, you may model them in any language, but you would need to pass the instances manually, this is what is usually called the strategy pattern; an example is Java's Comparator. Some folks argue that if the value has to be passed explicitly then is not a typeclass, I personally disagree but let's not open that pandora's box.

Algebraic Data Types (ADTs)

Is a pattern to model data based on simple constructors known as products and unions.

Again, some languages provide the building blocks for those out of the box, like Haskell. Some others emulate them upon classic subtyping and objects with some help from the compiler, like Scala.
And, once again, you may in theory model them in any language and replace pattern matching with a fold. But, in this case, I do agree that if the UX is not pleasant and you don't have pattern matching built-in in the language is hard to talk about ADTs.

Functor

Is an abstraction, a concept derived from category theory.
In essence, it is constituted of three things:

  • A type F[_] of kind * -> * (commonly know as type constructors); e.g. List
  • An implementation of map[A, B](fa: F[A])(f: A => B): F[B] for the given type F
  • A formal proof that such implementation satisfies the Functor laws.

I won't attempt to explain what a Functor is here...
But, a quick TL;DR; is that it represents the possibility of transforming some context by applying a function to the value(s) it will compute.

Since a Functor is an abstraction, we need some form of polymorphism to represent it. Experience has shown us that typeclasses are the most natural way to model Functor, Monad, Monoid, etc. But, you may try to use other approaches like structural types or subtyping... but really, you will hit a wall sooner than latter.

Extras

On top of all that, we also need to add other layers of indirection that each language may have.
For example, in Scala we use subtyping to model typeclasses hierarchies, so Monad[F] extends Functor[F], and the same keyword to model both subtyping abstractions and typeclass abstractions trait.

So again, it is important to always separate concepts / ideas from their underlying implementations; at the end of the day, everything will be just electrical pulses on a weird machine :p

CodePudding user response:

ADTs and typeclasses are both "things with multiple types", and I think that's what's confusing you. I think the reason we're having a hard time answering your question (or even figuring out precisely what it is) is because other than that they have almost nothing in common.

A typeclass could be thought of as a contract or specification. It exists independently of any concrete type and they are open: and any type you come up with could implement the necessary details to be a member of the set. We specify this in different ways in different languages that include this concept.

For instance Rust traits like Display:

use std::fmt;

struct Foo(i32);

impl fmt::Display for Foo {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

Or Haskell typeclases like Show:

data Foo = Int deriving Show

We have created in both cases a very basic type Foo based on the integer type that implements the "has a string representation" typeclass in the respective languages. As I said before the set of types that implement Show/Display is open: we can do this forever with any applicable type we come up with.

Contrast that with Maybe, which is a tagged union (which qualifies it as an ADT) Just a | Nothing for some type a. The set of types in the Maybe union is closed: you can use any type a you want but you can't add new things to the union.

Maybe is a type, not a typeclass. It's meaningless to say that Foo implements/derives Maybe. Maybe is not a set of rules for some other type to follow, anything could be a Maybe Whatever.

But Maybe as a type does implement some typeclasses, for instance the Monad typeclass (and by implication Functor, Applicative, etc.). It is not meaningless to say that Maybe implements the Monad specification.

  • Related