Search code examples
haskelldynamicstatictypeclassdispatch

Is the dispatch of a Haskell TypeClass dynamic?


Given the following Haskell code snapshot:

class Foo a where
   bar  :: a -> ...
   quux :: a -> ...
   ...

Where the value of a is determined at runtime - the class dispatches on this value.

I'm assuming that the compiler can statically check the types at compile-time, and ensure that no invalid types can dispatch on it.

Now if we compare this to a dynamic dispatch in Java:

interface Flippable {
  Flippable flip();
}

class Left implements Flippable {
  Right flip();
}

class Right implements Flippable {
  Left flip();
}

class Demo {
  public static void main(String args[]) {
    Flippable flippable = new Right();
    System.out.println(flippable.flip);
  }
}

Assumptions:

  • Haskell can dispatch on return type as well as multiple arguments making dispatch different to other languages.

My question is: Is the dispatch of a Haskell TypeClass dynamic?


Solution

  • It depends what you mean by "dynamic" dispatch. There aren't subtyping in haskell, so your Java example is hard to translate.

    In situation when you have type class, say Show and want to put different elements into the list, you can use existential quantification:

    {-# LANGUAGE ExistentialQuantification #-}
    
    data Showable = forall a. Show a => Showable a
    
    instance Show Showable where
      show (Showable x) = show x
    
    test :: main ()
    test = let list = [Showable True, Showable "string"]
           in print list
    
    -- >>> test
    -- [True,"string"]
    

    Here you can say that dispatch happens "dynamically". It happens in the same way as in C++ or Java. The Show dictionary is carried with an object, like a vtable in C++ (or class definition ptr in Java, dunno how it's called).

    Yet, as @MathematicalOrchid explained, this is an anti-pattern.


    Yet if you want to flip from Left to Right and back, you can state that in type class definition.

    {-# LANGUAGE ExistentialQuantification #-}
    {-# LANGUAGE FunctionalDependencies #-}
    
    class Flippable a b | a -> b where
      flip' :: Flippable b a => a -> b
    
    newtype INL = INL Int deriving Show
    newtype INR = INR Int deriving Show
    
    instance Flippable INL INR where
      flip' (INL x) = INR x
    
    instance Flippable INR INL where
      flip' (INR x) = INL x
    
    -- >>> flip' $ INL 1
    -- INR 1
    -- >>> flip' $ flip' $ INL 1
    -- INL 1
    

    In this case flip' calls are resolved already at compile-time.


    You could have have class Flip a where flip' :: a -> Flippable using existential quantification too. Then consecutive calls will be dispatched dynamically. As always, it depends on your needs.

    {-# LANGUAGE ExistentialQuantification #-}
    {-# LANGUAGE FunctionalDependencies #-}
    {-# LANGUAGE StandaloneDeriving #-}
    
    class Flip a where
      flip' :: a -> Flippable
    
    -- Show is only for repl purposes
    data Flippable = forall a. (Flip a, Show a) => Flippable a
    
    deriving instance Show Flippable
    
    instance Flip Flippable where
      flip' (Flippable x) = flip' x
    
    newtype INL = INL Int deriving Show
    newtype INR = INR Int deriving Show
    
    instance Flip INL where
      flip' (INL x) = Flippable (INR x)
    
    instance Flip INR where
      flip' (INR x) = Flippable (INL x)
    
    -- >>> flip' $ flip' $ flip' $ INL 1
    -- Flippable (INR 1)
    

    Hopefully this answers your question.