similar concept, it may help to think of typeclasses as being like interfaces to data that can work across multiple datatypes. Ref: haskellbook, CH6 “The goal is to define a datatype by cases, where one can add new cases to the datatype and new functions over the datatype, without recompiling existing code, and while retaining static type safety (e.g., no casts).”
where show Chrome = "Chrome" show Firefox = "Firefox" show Servo = "Servo" `instance` keyword to declare an instance of Type Class The Type Class of this instantiation The type provided for this instantiation `where` keyword terminates the initial declaration and beginning of the instance. Implement method(s) included in the Type Class
class method, we can only define the method with type signature. Type signature can make sure that we’ll always have same type though. However, we cannot guarantee correct logic for users. Thus, we can define laws for the type class and let user follow them.
instance eqBrowser :: Eq Browser where eq Chrome Firefox = true eq Firefox Firefox = true eq _ _ = false Obviously, we can see Chrome is equal to Firefox but Firefox is not equal to Chrome.
a where compare :: a -> a -> Ordering Using `<=` in type class means superclass for the Type Class. It means `Eq` is a superclass of `Ord` so when you’d like to instantiate `Ord` for a datatype, you should confirm it has also instantiated `Eq`.
Example, We can always have the original array no matter we do left append or right append with empty array. We can always have same string no matter we do left concat or right concat with empty string.
Methods How about foldMap? Looking at the type signature of foldMap. We can know the m argument must be a Monoid. So, we can handle the folding for the empty case via mempty. Let’s see an example with showing an array.
Example: Show an array with joined text The first argument of foldMap is a function which takes a type a and return a Monoid m. And, show is a function which takes a type a and return a String which is a Monoid. foldMap implementation of Array is folding from mempty as a starting point; mempty of String is an empty String. So, we don’t need to pass the second argument like in foldl or foldr which is an empty String.
Ord a where compare :: a -> a -> Ordering In PureScript / Haskell, via type signature, we can know that we’ll have same type, `a`, of arguments in `compare` function. Ref: How do type classes differ from interfaces?
Eq { public Ordering compare(Ord other); } public enum Ordering { LT, EQ, GT } public class OrdUtil { static <A extends Ord> bool lessThanOrEqual(A a1, A a2) { Ordering result = a1.compare(a2); return result == LT || result == EQ; } } In Java, we can `extends` Ord interface; however, it cannot make sure the type `A` are always same type. Ref: How do type classes differ from interfaces?
from interfaces? Separation of Implementation interface SomeOtherPackage { public bool doSomeThing(int lol); } public class MyLarge implements SomeOtherPackage { public final Large large; public MyLarge(Large large) { this.large = large; } public bool doSomething(int lol) { System.out.println("wut"); } } If we’d like to create our `interface`, we’ll need to create a new Class to wrap the `Large`.
from interfaces? Separation of Implementation import JokesAreFun (ToyOrd( ..)) class MyNewClass a where doSomething :: a -> Int -> IO () instance MyNewClass ToyOrd where doSomething Smol x = putStrLn "hahahaa yess" doSomething (Large x) y = putStrLn ("numbers! " ++ show (x + y)) “Type classes separate the definition of data types and the instances of a class.” So, when we need to create our own type class and instantiate it for upstream package datatype, we can just make the datatype as one of instance of our new type class.