DEV Community


Posted on • Updated on

Type Thinking 🤔 P2 - Typeclass

In this post I shortly compare:

  • Haskell Typeclass
  • Rust Trait
  • Typescript type constraints

Demystifying some big words (wikipedia definitions):

  • Polymorphism - (many forms) "Polymorphism is the provision of a single interface to entities of different types"

I remember facing "polymorphism" when I just started to learn Java as a first OOP lang (~8 years ago).
The teacher taught us that one of the basics principles of an OOP lang is "polymorphism".
Whenever I hear of it I think of the famous animal example. having "Animal" class which the "Dog" and "Cat" inherit from and I can iterate a collection of Animals and invoke the "makeSound()" function they all implement cause they all of the same type (object) - "Animal".

But If being honest I don't remember the last time I used inheritance (even the prototype kind..) I usually define a structure type by an interface and defines another "behavioural" interface containing the methods I need to implement. (a matter of taste? Or a better segregation...).

  • Ad hoc polymorphism or just overloading: "Defines a common interface for an arbitrary set of individually specified types". on top of that we have functions overloading - the ability to create multiple functions of the same name with different implementations (handled by the compiler).

  • Parametric polymorphism "A way to make a language more expressive, while still maintaining full static type-safety" (Generics..)

  • Dynamic Dispatch - The process of selecting which implementation of a polymorphic operation (method or function) to call at run time.


TypeClass Trait Interface
A type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types. If a type is a part of a typeclass it should implement the behavior described on that typeclass. A trait is a language feature that tells the Rust compiler about functionality a type must provide We use an interface to define a shape and we use Generics constraints for saying that a certain behaviour (or an attribute) is required by a type.


Following example is from: learnyouahaskell

data Person = Person { firstName :: String  
                     , lastName :: String  
                     , age :: Int  
                     } deriving (Eq) 

let adRock = Person {firstName = "Adam", lastName = "Horovitz", age = 41}  
let mca = Person {firstName = "Adam", lastName = "Yauch", age = 44}  
-- mca == adRock will be false

Enter fullscreen mode Exit fullscreen mode

I'm far of being an haskell developer but it seem to fit the typeclass definition, there's a class Eq, it contains "==" and "/=" functions,
deriving (Eq) tells the compiler to generate instance of the Eq class for the Person type, this way it implements / has the "==" "/=" operators.


trait NoisyAnimal {
    fn make_noise<'staic>(&self) -> &'staic str;
struct Cat{}
impl NoisyAnimal for Cat {
    fn make_noise<'staic>(&self) -> &'staic str {

struct Dog{}
impl NoisyAnimal for Dog {
    fn make_noise<'staic>(&self) -> &'staic str {

fn make_noises(animals: Vec<Box<dyn NoisyAnimal>>) {
                        //       ----- trait object
    for animal in animals.iter() {
        println!("{}", animal.make_noise());

fn make_noises_with_constraints<T: NoisyAnimal>(animal: T) {
    println!("{}", animal.make_noise());

pub fn call_make_noises() {

    let animals: Vec<Box<dyn NoisyAnimal>> = vec![
Enter fullscreen mode Exit fullscreen mode

Struct is a type that is composed of other types. (in Typescript we model in an interface). In the above example we're implementing the trait obj functionality to 2 structs Cat & Dog.
The make_noises fn iterates a vector of types that implements the trait object (dynamic dispatch) - pretty cool :D.
I also created the make_noises_with_constraints() methos which demonstrates a trait constraint which is more relevant to the concepts I raised..


interface Instrument {
    play: () => void;
class Guitar implements Instrument {
    play() {}

class Drums implements Instrument {
    play() {}

function playPart  <T extends Instrument>(instruments: T[]) : void {
    for (const instrument of instruments) {;

playPart([new Guitar(), new Drums()]);
Enter fullscreen mode Exit fullscreen mode

Using Generics constraints I'm not limiting the playPart() function for a single type of instruments, we can make sound of many things, e.g. a bottle of beer, so in case that bottle implements the Instrument interface, it will be playable - (which could be a better name actually :P ).


Top comments (2)

iquardt profile image
Iven Marquardt

I want to challenge the Typescript part of your post. Hope that is ok.

Your Typescript example doesn't really use the generic type variable T. What it illustrates is subtype polymorphism, not ad-hoc. Typescript only supports a rather primitive form of ad-hoc polymorphism at runtime through function overloading. You can overload a single name with several function types and than provide the implementation, where each type must be represented in a corresponding if/else branch.

Type classes in Haskell are a compile time mechanism. They go beyond simple name overloading by giving the mapping of a name to several types itself a name. Now you can refer to this named mapping and assign several overloaded names to it. You can express laws its members must abide by. You can form hierarchies where a subclass inherits overloaded names from a superclass. Named mappings are type classes.

lironn_h profile image

Challenging is more than OK! my purpose of writing about those topics is "technical/personal grow"! The generic type just gives a certain agility.. I like comparing different languages to get a bit sharper on my daily lang and keep an interesting conversion :)