What is the type inferred by a Haskell type synthesizer when unifying
the types c -> a -> b
and (a -> b) -> c
?
Can someone explain me how can I solve it?
Thanks!
What is the type inferred by a Haskell type synthesizer when unifying
the types c -> a -> b
and (a -> b) -> c
?
Can someone explain me how can I solve it?
Thanks!
This seems to be some kind of exercise/homework so I will not spoil everything but give you some hints first:
c -> a -> b
is actually c -> (a -> b)
c -> (a -> b)
with (a -> b) -> c
, that is:
c
with a -> b
(first part)a -> b
with c
(second part)now what could that (try to get rid of c
;) ) be now?
PS: I am assuming you want those types a
, b
, .. to be the same
In other answers, we have seen how to perform the unification by hand, and how to ask ghci
some limited unification questions when we do not need to connect type variables in the two types we want to unify. In this answer, I show how to use existing tooling to answer the question you asked as I understand you to intend it.
The trick is to use type-equality constraints to ask GHC to unify two types, then expose the results as a tuple type. The type equality constraint kicks off the unifier; when unification is done, the type variables in our tuple type will be simplified according to what was learned during unification.
Thus, your question looks like this, for example:
> :set -XTypeFamilies
> :{
| :t undefined -- a dummy implementation we don't actually care about
| :: ((a -> b) -> c) ~ (c -> a -> b) -- the unification problem
| => (a, b, c) -- how we ask our query (what are the values of a, b, and c after unification?)
| :}
<snip -- a copy of the above text>
:: (a, b, a -> b)
From this, we learn that for any types a
and b
, we can choose a ~ a
, b ~ b
, and c ~ a -> b
as a solution to the unification problem. Here is another query you might wonder: after unification, what is the simplified type of (a -> b) -> c
? You could run the previous query, and substitute in a
, b
, and c
by hand, or you could ask ghci:
> :t undefined :: ((a -> b) -> c) ~ (c -> a -> b) => (a -> b) -> c
undefined :: ((a -> b) -> c) ~ (c -> a -> b) => (a -> b) -> c
:: (a -> b) -> a -> b
The only thing I changed in this command is the "query" part. The result tells us that (a -> b) -> c
becomes (a -> b) -> a -> b
after unification. Note well that the a
and b
in the result type are not guaranteed to be exactly the same as the a
and b
in the query -- though probably in GHC that will always be the case.
Another quick trick worth mentioning is that you can use Proxy
to turn an arbitrarily-kinded type variable into a *
type for use in a tuple; thus, for example:
> :t undefined :: f a ~ (b -> c) => (a, b, c, f)
<interactive>:1:42:
Expecting one more argument to ‘f’
The fourth argument of a tuple should have kind ‘*’,
but ‘f’ has kind ‘* -> *’
In an expression type signature: f a ~ (b -> c) => (a, b, c, f)
In the expression: undefined :: f a ~ (b -> c) => (a, b, c, f)
> :m + Data.Proxy
> :t undefined :: f a ~ (b -> c) => (a, b, c, Proxy f)
undefined :: f a ~ (b -> c) => (a, b, c, Proxy f)
:: (c, b, c, Proxy ((->) b))
You can ask ghci
:t [undefined :: c -> a -> b, undefined :: (a -> b) -> c]
It will need to unify the types to figure out what type the elements of the list are. We can unify any number of types this way; even 0, try it!
The type variables on the left in c -> a -> b
are distinct from the type variables on the right in a -> b -> c
. GHC will rename type variables to keep them distinct, but it will try to preserve the original names. It does this by adding numbers to the end of the type variable names. The answer to this query includes some of the type variables a
, a1
, b
, b1
, c
, and c1
. If you don't want the type variables to be distinct, you can read off the answer ignoring the added numbers.
If you do want the type variables to be distinct, it can be a bit tricky to tell what ghc is doing because you don't know which type variables where renamed to what. In practical coding, this can be a problem when trying to understand type errors. In both cases there is a simple solution: rename the type variables with distinctive names yourself so that ghc doesn't need to rename them.
:t [undefined :: c1 -> a1 -> b1, undefined :: (a2 -> b2) -> c2]
We're done with what vanilla Haskell can do, but you can get the compiler to answer questions more generally by using type equality constraints as described in Daniel Wagner's answer. The next section just describes why forall
scoped types are not the general solution.
Before reading this section you should think about whether it is possible to unify, for all c
, c -> a -> b
and (a -> b) -> c
.
To the experienced haskeller, it might seem like you could keep the type variables from being distinct by introducing them in an explicit forall
scope with the ScopedTypeVariables extension. I don't know an easy way to do this in ghci, but the following snipet with a hole† asks the compiler to unify a -> b
and a -> b
.
{-# LANGUAGE ScopedTypeVariables #-}
example1 :: forall a b. ()
example1 = (undefined :: _) [undefined :: a -> b, undefined :: a -> b]
The output seems to tell us that the list is a list of a -> b
.
Found hole `_' with type: [a -> b] -> ()
If we try to use this for the example problem, it doesn't work.
example2 :: forall a b c. ()
example2 = (undefined :: _) [undefined :: c -> a -> b, undefined :: (a -> b) -> c]
The compiler politely tells us why†
Couldn't match type `c' with `a -> b'
It is not true that for all types c
, c
is a function. Some example types that aren't functions include Int
, Bool
, and IO a
.
† I use (undefined :: _)
instead of _
when asking what the type that goes in a hole is. If you just use _
ghc doesn't type check all of the expression. The compiler may lead you to believe a hole is possible to fill when it is in fact impossible. In the output for example2
there is also the following, extremely misleading line
Found hole `_' with type: [c -> a -> b] -> ()