Tekmo
For example, take the following example type:
newtype Example f c a b = Example (f (c a b))
instance (Applicative f, Category c) => Category (Example f c) where
id = Example (pure id)
Example f . Example g = Example (liftA2 (.) f g)
You can prove that if `c` satisfies the `Category` laws and `f` satisfies the `Applicative` laws, then `Example f c` also satisfies the `Category` laws.This in turn lets you take any `Category` and keep extending it with as many `Applicative`s as you want in any order, confident in the knowledge that your extended `Category` still satisfies the `Category` laws.
This is a common pattern in code that is organized around category theory and/or abstract algebra and I wrote a post showing a simpler example of this phenomenon (using `Monoid` instead of `Category`): http://www.haskellforall.com/2014/07/equational-reasoning-at...
>>> let showAdd x y = show (x + y)
>>> :type showAdd
showAdd :: (Num a, Show a) => a -> a -> String
The compiler infers:* The two arguments, `x` and `y`, must be some type `a`
* They must be the same type `a`
* The type `a` must implement the `Num` interface (because we add them together using `(+)`
* The result of addition must also be the same type `a`
* The type `a` must also implement the `Show` interface (because we call `show` on the result of addition, which also has type `a`)
Notice how the compiler works backwards from the call to `show` on the result of addition to infer that the two original arguments must also implement the `Show` interface.
We didn't need to annotate any argument types or interfaces: the compiler did all the work for us. This is what people mean when they say that Haskell has "global" type inference.
A language like Rust has "local" type inference, meaning that you have to help a the compiler a little bit by providing the types and interfaces of function arguments, but then the compiler can infer the types of locally bound variables from that point forward.