Functional Dependencies
Observe the type of (+):
ghci> :t (+)(+) :: forall a. Num a => a -> aThis is quite different in Python:
>>> type(1 + 1)class <'int'>>>> type(1 + 1.0)class <'float'>>>> type(1.0 + 1)class <'float'>>>> type(1.0 + 1.0)class <'float'>The + operator in Python behaves heterogenously—when given
two ints we get an int; when given at least one
float we get a float. How would we encode this in
Haskell?
Simple! Create a multi-parameter typeclass that describes the argument types and the result type!
class (Num a, Num b, Num c) => HAdd a b c where (+#) :: a -> b -> cThen we can write instances for the possible permutations of the desired types:
instance Num a => HAdd a a a where (+#) :: a -> a -> a (+#) = (+)
instance HAdd Int Double Double where (+#) :: Int -> Double -> Double x +# y = fromIntegral x + y
instance HAdd Double Int Double where (+#) :: Double -> Int -> Double x +# y = x + fromIntegral yHowever, trying to use (+#) is very cumbersome:
ghci> x :: Int = 1ghci> y :: Double = 2.0ghci> x +# y<interactive>:3:1: error: - No instance for (HAdd Int Double ()) arising from a use of 'it' - In the first argument of 'print', namely 'it' In a stmt of an interactive GHCi command: print itghci> x +# y :: Double3.0This occurs because without specifying the return type c,
Haskell has no idea what it is since it is ambiguous! As per the
definition, no one is stopping us from defining another
instance HAdd Int Double String! On the other hand, we know
that adding an Int and a Double must result in
a Double and nothing else; in other words, the types of the
arguments to (+#) uniquely characterizes the resulting
type.
The way we introduce this dependency between these type variables by
introducing functional dependencies on typeclass declarations, which,
adding them to our declaration of HAdd, looks something like
the following:
{-# LANGUAGE FunctionalDependencies #-}class (Num a, Num b, Num c) => HAdd a b c whereclass (Num a, Num b, Num c) => HAdd a b c | a b -> c where (+#) :: a -> b -> cThe way to read the clause a b -> c is “a and b uniquely
characterizes/determines c”, or in other words, c is a function
of a and b, i.e. it is not possible that given a fixed a and b
that we have two different inhabitants of c. This (1) prevents the
programmer from introducing different values of c for the same a and
b (which we haven’t) and (2) allows the compiler to infer the right
instance just with a and b alone.
ghci> x :: Int = 1ghci> y :: Double = 2.0ghci> x +# y3.0ghci> :{ghci| instance HAdd Int Double String whereghci| x +# y = show xghci| :}<interactive>:8:10: error: Functional dependencies conflict between instance declarations: instance [safe] HAdd Int Double Double -- Defined at <interactive>:17:10 instance HAdd Int Double String -- Defined at <interactive>:21:10