简体   繁体   中英

How does type unification/function application work at a high level in Haskell?

I feel like I'm not really getting how function application works or maybe I'm fixating on something that I shouldn't be.

Let's try to determine the type signature of (fmap .)

fmap :: (x -> y) -> fx -> fy

(.) :: (b -> c) -> (a -> b) -> a -> c -- signatures taken from GHC

Applying fmap to (.) , we esentially wanna make (x -> y) -> fx -> fy and (b -> c) equal. When I first tried to do this I remember having a really hard time. You gotta match a set of 4 variables to a set of 2, so obviously you gotta do some grouping, but how? There's 3 possible solutions that I see, are all of them valid? Or is it only 1? I read that parentheses aren't really taken into consideration by the compiler, so we end up with x -> y -> fx -> fy and b -> c , but also functions associate to the right, so fmap should actually look like x -> (y -> (fx -> fy)) , which lead me to

b = x

c = y -> fx -> fy

resulting in (a -> x) -> (a -> y -> fx -> fy) as a signature for (fmap .) .

The grouping I've seen this solved with though is (a -> b) -> (fa -> fb) . So

b = x -> y

c = fx -> fy

leading to (a -> x -> y) -> a -> fx -> fy , which is more or less the same as GHC's (a1 -> a2 -> b) -> a1 -> f a2 -> fb if you rename some type variables.

Now to compare the 2 results, parentheses removed:

a -> x -> a -> y -> fx -> fy

a -> x -> y -> a -> fx -> fy

We can clearly see that the 3rd and 4th parameter types are switched, which means they're obviously not the same since flip is a thing. Which means this substitution

b = x

c = y -> fx -> fy

is not valid. Or am I missing something?

I have a feeling that I'm trying to make the functions work based on the signatures when I should be doing it the other way around. :t fmap 'c' fails with Couldn't match expected type 'a -> b' with actual type 'Char' which means that you can't really partially apply the a -> b part of fmap . You can only partially apply fmap itself and its first argument is indivisible I guess. I don't know if I'm getting the pattern behind it all though.

I should really try to write a typed lambda calculus.

There's 3 possible solutions that I see, are all of them valid?

No.

Or is it only 1?

Only one. (x -> y) -> fx -> fy is equal to (x -> y) -> (fx -> fy) and only to that since -> is right associative. Putting parentheses in other points is wrong.

I read that parentheses aren't really taken into consideration by the compiler,

That's false. The compiler implicitly works as parentheses are always added in a right-associative way: the type a -> b -> c -> d is handled as a -> (b -> (c -> d)) , only. By contrast, the type (a -> b) -> c -> d is handled as (a -> b) -> (c -> d) , only, and the type (a -> b -> c) -> d is handled as (a -> (b -> c)) -> d , only.

so we end up with x -> y -> fx -> fy and b -> c ,

No, that's wrong, you must keep the parentheses. We must satisfy the type equality

(x -> y) -> (f x -> f y)
~
b -> c

and the unique solution to this is

b ~ (x -> y)
c ~ (f x -> f y)

as you found out later.

To understand what's going on in unification, I think it's beneficial if you start by adding the implicit parentheses in a right-associative way. If you do that, you can forget about types like a -> b -> c , and only care about the fundamental case (T -> U) and its unification step:

from (T1 -> U1) ~ (T2 -> U2)
deduce T1 ~ T2 and U1 ~ U2

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM