Given these two procedures (written in JavaScript) …
// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f=> g=> x=> f (g (x))
// comp2 :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
const comp2 = comp (comp) (comp)
My question is how to derive comp2
's Hindley-Milner Type without referencing comp
's implementation
If we know comp
's implementation, it's easy … We can use the substitution model through the entire evaluation to arrive at the expanded expression …
comp (comp) (comp)
= (f => g => x => f (g (x))) (comp) (comp)
= x => comp (comp (x))
= y => comp (comp (y))
= y => (f => g => x => f (g (x))) (comp (y))
... keep going until ...
= f=> g=> x=> y=> f (g (x) (y))
Ring-a-ding. The expanded evaluation matches comp2
's type. No one is impressed.
// comp2 :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
const comp2 = f=> g=> x=> y=> f (g (x) (y))
But what if we only knew comp
's type and did not know its implementation? Instead of evaluating the code to determine the type, could I have performed some sort of substitutions/evaluation on comp
's type to end up with comp2
's type ?
Given only this, the problem becomes much harder … (at least for me)
// comp :: (b -> c) -> (a -> b) -> (a -> c)
// comp2 :: ???
const comp2 = comp (comp) (comp)
There's gotta be a way, right ? Isn't this what algebraic data types are all about?
Let's look at a simplified example to clarify my question: If we have a function like add
and map
…
// add :: Number -> Number -> Number
// map :: (a -> b) -> [a] -> [b]
If we wanted to define a function using map
and add
we could figure out the type systematically without knowing add
or map
's implementation
// add :: Number -> Number -> Number
// map :: (a -> b) -> [a] -> [b]
// add6 :: Number -> Number
let add6 = add (6)
// mapAdd6 :: [Number] -> [Number]
let mapAdd6 = map(add6)
This is really powerful because it allows you to reason about code you didn't make without having to go digging through the implementation (as much)
However, when trying to do it with the comp2
example, I get stuck pretty quick
// comp :: (b -> c) -> (a -> b) -> (a -> c)
// comp2 :: ??
const comp2 = comp (comp) (comp)
// initial type
(b -> c) -> (a -> b) -> (a -> c)
// apply to comp once ... ???
[(b -> c) -> (a -> b) -> (a -> c)] -> (a -> b) -> (a -> c)
// apply the second time ... ???
[(b -> c) -> (a -> b) -> (a -> c)] -> [(b -> c) -> (a -> b) -> (a -> c)] -> (a -> c)
// no... this can't be right
HOW TO HINDLEY MILNER