Type quantifiers in Haskell functions - haskell

Suppose I have two Haskell functions of the following types, with ExplicitForAll activated,
f :: forall a. (a -> Int)
g :: forall a. (Int -> a)
It seems to me that the type of g is isomorphic to Int -> (forall a. a), because for example the type of g(2) is forall a. a.
However, the type of f doesn't look isomorphic to (forall a. a) -> Int. f is a polymorphic function, it knows what to compute on each input type a, in mathematics I guess it would rather be a family of functions ; but I don't think it can handle a single argument that has all types.
Is it a rule of typed lambda calculus that type quantifiers distribute on functions target types, but not on functions source types ?
Does the type (forall a. a) -> Int exist in Haskell, possibly restrained to a type class (forall a. SomeClass a => a) -> Int ? Is it useful ?

weird :: (forall a. a) -> Int is unnecessarily specific.
undefined is the only value that has type forall a. a, so the definiton would have to be weird _ = someInteger, which is just a more restrictive version of const.

A ∀ a . is basically just an extra implicit argument, or rather, a specification of how type-constraints pertaining to that argument should be handled. For instance,
f :: ∀ a . Show a => (a -> Int)
g :: ∀ a . Show a => (Int -> a)
are in essence functions of two arguments,
f' :: ShowDictionary a -> a -> Int
g' :: ShowDictionary a -> Int -> a
or even dumber,
type GenericReference = Ptr Foreign.C.Types.Void -- this doesn't actually exist
f'' :: (GenericReference -> String) -> GenericReference -> Int
g'' :: (GenericReference -> String) -> Int -> GenericReference
Now, these are just monomorphic (or weak-dynamic typed) functions. We can clearly use flip on them to obtain
f''' :: GenericReference -> (GenericReference -> String) -> Int
g''' :: Int -> (GenericReference -> String) -> GenericReference
The latter can simply be evaluated partially with any Int argument, hence g is indeed equivalent to γ :: Int -> (∀ a . Show a => Int -> a).
With f''', applying it to some void-pointered argument would be a recipe to disaster, since there's no way for the type system to ensure that the actually passed type matches the one the Show function is prepared to handle.

This is a copy of Chi's comment above, it explains the theoretical part by interpreting functions as logical implications (Curry-Howard correspondence) :
Type quantifers can be swapped with arrows as in logic: the
proposition p -> forall a. q(a) is equivalent to forall a. p -> q(a)
provided p does not depend on a. If Haskell had existential types, we
would have the isomorphism (forall a. p(a) -> q) ~ ((exists a. p(a))
-> q). It commutes with products too (forall a. p a, forall a. q a) ~ forall a. (p a, q a). On sums it's trickier.
I also link the specs of RankNTypes. It does enforce the rules of "floating out" type quantifiers and defines the type (forall a. SomeClass a => a) -> Int.

Related

What does the `forall a -> b` syntax mean?

In GHCi, the kind of FUN is displayed like this:
λ> :k FUN
FUN :: forall (n :: Multiplicity) -> * -> * -> *
At first, I thought this was a roundabout way of saying
FUN :: Multiplicity -> * -> * -> *
but it turns out Template Haskell has a separate constructor for this form: ForallVisT. I can't find any documentation on it, and I have no idea how to even begin experimenting with it in a meaningful way.
What does this syntax mean? How does forall a -> b differ from a "normal" forall a. a -> b?
forall a -> _ is used when the result type depends on an explicit argument.
-- type NonDep :: Type -> Type; argument is explicit
data NonDep (x :: Type) :: Type
-- type ImpDep :: forall a. Maybe a -> Type; first argument is implicit and dependent
data ImpDep (x :: Maybe a) :: Type
-- so if you want an explicit and dependent argument...
-- type ExpDep :: forall (a :: Type) -> Maybe a -> Type
data ExpDep (a :: Type) (x :: Maybe a) :: Type
It is odd that FUN's type has forall (m :: Multiplicity) -> and not Multiplicity -> as the following arguments (two implicit RuntimeReps and two TYPEs) do not depend on it, but such is the weirdness that surrounds GHC primitives.

What does forall on the right of a function arrow mean?

The topic of Section 7.12.5 of the GHC Users Guide is higher rank polymorphism. There are some example valid types, among others:
f4 :: Int -> (forall a.a->a)
Now I wonder what this type means. I think it is the same as:
f4' :: forall a. Int -> a -> a
If this is so, can we generally mentally move forall like the above (that appears right of the rightmost arrow) to the left assuming that no type variable with the same name occurs in the rest of the type (but this could be dealt with renaming, simply)?
For example, the following would still be correct, wouldn't it:
f5 :: Int -> (forall a. (forall b. b -> a) -> a)
f5' :: forall a. Int -> (forall b. b -> a) -> a
Would be thankful for an insightful answer.
Background: In this talk about lenses by SPJ, we have:
type Lens' s a = forall f. Functor f => (a -> f a) -> s -> f s
and then, when you compose them, you have such a lens type in the result.
Therefore I just wanted to know whether my intuition is correct, that the forall in the result doesn't really matter - it just appears "accidentally" because of the type synonym. Otherwise, there must be some difference between the types for f4, f4'and f5, f5' above I would want to learn about.
Here is a ghci session:
Prelude> let f5 :: Int -> (forall a. (forall b. b -> a) -> a); f5 i f = f i
Prelude> :t f5
f5 :: Int -> (forall b. b -> a) -> a
Prelude>
Looks like GHC agrees with me, at least in this case .....
f4 in your example can be universally quantified because Int -> (forall a. a -> a) and forall a. Int -> (a -> a) are essentially same.
But we can not apply you analogy for example in this Rank2 type (forall a. a -> a) -> (forall b. b -> b). This type is essentially same as forall b. (forall a. a -> a) -> (b -> b). But moving the first forall out (forall a b. (a -> a) -> (b -> b)) essentially changes the semantics of the type.
f :: (forall a. a -> a) -> (forall b. b -> b) -- Rank 2
g :: forall a b. (a -> a) -> (b -> b) -- Rank 1
To see the difference, you can instantiate a in g to be Int and thus can pass a function of type Int -> Int to g. On the other hand the argument to f has to be universally quantified (specified by that forall before the function type) and should work for all types (Example of such a function is id).
Here is a good explanation of higher rank types.

Understanding a rank 2 type alias with a class constraint

I have code that frequently uses functions that look like
foo :: (MyMonad m) => MyType a -> MyOtherType a -> ListT m a
To try to shorten this, I wrote the following type alias:
type FooT m a = (MyMonad m) => ListT m a
GHC asked me to turn on Rank2Types (or RankNTypes), but didn't complain when I used the alias to shorten my code to
foo :: MyType a -> MyOtherType a -> FooT m a
By contrast, when I wrote another type alias
type Bar a b = (Something a, SomethingElse b) => NotAsBar a b
and used it in a negative position
bar :: Bar a b -> InsertTypeHere
GHC loudly yelled at me for being wrong.
I think I have an idea of what's going on, but I'm sure I could get a better grasp from your explanations, so I have two questions:
What are the type aliases actually doing/what do they actually mean?
Is there a way to get the terseness in both cases?
There are essentially three parts to a type signature:
variable declarations (these are usually implicit)
variable constraints
the type signature head
These three elements essentially stack. Type variables must be declared before they can be used, either in constraints or elsewhere, and a class constraint scopes over all uses within the type signature head.
We can rewrite your foo type so the variables are explicitly declared:
foo :: forall m a. (MyMonad m) => MyType a -> MyOtherType a -> ListT m a
The variable declarations are introduced by the forall keyword, and extend to the .. If you don't explicitly introduce them, GHC will automatically scope them at the top level of the declaration. Constraints come next, up to the =>. The rest is the type signature head.
Look at what happens when we try to splice in your type FooT definition:
foo :: forall m a. MyType a -> MyOtherType a -> ( (MyMonad m) => ListT m a )
The type variable m is brought into existence at the top level of foo, but your type alias adds a constraint only within the final value! There are two approaches to fixing it. You can either:
move the forall to the end, so m comes into existence later
or move the class constraint to the top
Moving the constraint to the top looks like
foo :: forall m a. MyMonad m => MyType a -> MyOtherType a -> ListT m a
GHC's suggestion of enabling RankNTypes does the former (sort of, there's something I'm still missing), resulting in:
foo :: forall a. MyType a -> MyOtherType a -> ( forall m. (MyMonad m) => ListT m a )
This works because m doesn't appear anywhere else, and it's right of the arrow, so these two mean essentially the same thing.
Compare to bar
bar :: (forall a b. (Something a, SomethingElse b) => NotAsBar a b) -> InsertTypeHere
With the type alias in a negative position, a higher-rank type has a different meaning. Now the first argument to bar must be polymorphic in a and b, with appropriate constraints. This is different from the usual meaning, where bars caller chooses how to instantiate those type variables. It's not
In all likelihood, the best approach is to enable the ConstraintKinds extension, which allows you to create type aliases for constraints.
type BarConstraint a b = (Something a, SomethingElse b)
bar :: BarConstraint a b => NotAsBar a b -> InsertTypeHere
It's not quite as terse as what you hoped for, but much better than writing out long constraints every time.
An alternative would be to change your type alias into a GADT, but that has several other consequences you may not want to bring in. If you're simply hoping to get more terse code, I think ConstraintKinds is the best option.
You can think of typeclass constraints essentially as implicit parameters -- i.e. think of
Foo a => b
as
FooDict a -> b
where FooDict a is a dictionary of methods defined in the class Foo. For example, EqDict would be the following record:
data EqDict a = EqDict { equal :: a -> a -> Bool, notEqual :: a -> a -> Bool }
The differences are that there can only be one value of each dictionary at each type (generalize appropriately for MPTCs), and GHC fills in its value for you.
With this in mind, we can come back to your signatures.
type FooT m a = (MyMonad m) => ListT m a
foo :: MyType a -> MyOtherType a -> FooT m a
expands to
foo :: MyType a -> MyOtherType a -> (MyMonad m => ListT m a)
using the dictionary interpretation
foo :: MyType a -> MyOtherType a -> MyMonadDict m -> ListT m a
which is equivalent by reordering of arguments to
foo :: MyMonadDict m -> MyType a -> MyOtherType a -> ListT m a
which is equivalent by the inverse of the dictionary transformation to
foo :: (MyMonad m) => MyType a -> MyOtherType a -> ListT m a
which is what you were looking for.
However, things do not work out that way in your other example.
type Bar a b = (Something a, SomethingElse b) => NotAsBar a b
bar :: Bar a b -> InsertTypeHere
expands to
bar :: ((Something a, SomethingElse b) => NotAsBar a b) -> InsertTypeHere
These variables are still quantified at the top level (i.e.
bar :: forall a b. ((Something a, SomethingElse b) => NotAsBar a b) -> InsertTypeHere
), since you mentioned them explicitly in bar's signature, but when we do the dictionary transformation
bar :: (SomethingDict a -> SomethingElseDict b -> NotAsBar a b) -> InsertTypeHere
we can see that this is not equivalent to
bar :: SomethingDict a -> SomethingElseDict b -> NotAsBar a b -> InsertTypeHere
which would give rise to what you want.
It's pretty tough to come up with realistic examples in which a typeclass constraint is used at a different place than its point of quantification -- I have never seen it in practice -- so here's an unrealistic one just to show that that's what's happening:
sillyEq :: forall a. ((Eq a => Bool) -> Bool) -> a -> a -> Bool
sillyEq f x y = f (x == y)
Contrast to what happens if we use try to use == when we are not passing an argument to f:
sillyEq' :: forall a. ((Eq a => Bool) -> Bool) -> a -> a -> Bool
sillyEq' f x y = f (x == y) || x == y
we get a No instance for Eq a error.
The (x == y) in sillyEq gets its Eq dict from f; its dictionary form is:
sillyEq :: forall a. ((EqDict a -> Bool) -> Bool) -> a -> a -> Bool
sillyEq f x y = f (\eqdict -> equal eqdict x y)
Stepping back a bit, I think the way you are tersifying here is going to be painful -- I think you're wanting the mere use of something to quantify its context, where its context is defined as the "function signature where it is used". That notion has no simple semantics. You should be able to think of Bar as function on sets: it takes as arguments two sets and returns another. I don't believe there will be such a function for the use you are trying to achieve.
As far as shortening contexts, you may be able to make use of the ConstraintKinds extension which allows you to make constraint synonyms, so at least you could say:
type Bars a = (Something a, SomethingElse a)
to get
bar :: Bars a => Bar a b -> InsertTypeHere
But what you want still may be possible -- your names are not descriptive enough for me to tell. You may want to look into Existential Quantification and Universal Quantification, which are two ways of abstracting over type variables.
Moral of the story: remember that => is just like -> except that those arguments are filled in automatically by the compiler, and make sure that you are trying to define types with well-defined mathematical meanings.

How to express existential types using higher rank (rank-N) type polymorphism?

We're used to having universally quantified types for polymorphic functions. Existentially quantified types are used much less often. How can we express existentially quantified types using universal type quantifiers?
It turns out that existential types are just a special case of Σ-types (sigma types). What are they?
Sigma types
Just as Π-types (pi types) generalise our ordinary function types, allowing the resulting type to depend on the value of its argument, Σ-types generalise pairs, allowing the type of second component to depend on the value of the first one.
In a made-up Haskell-like syntax, Σ-type would look like this:
data Sigma (a :: *) (b :: a -> *)
= SigmaIntro
{ fst :: a
, snd :: b fst
}
-- special case is a non-dependent pair
type Pair a b = Sigma a (\_ -> b)
Assuming * :: * (i.e. the inconsistent Set : Set), we can define exists a. a as:
Sigma * (\a -> a)
The first component is a type and the second one is a value of that type. Some examples:
foo, bar :: Sigma * (\a -> a)
foo = SigmaIntro Int 4
bar = SigmaIntro Char 'a'
exists a. a is fairly useless - we have no idea what type is inside, so the only operations that can work with it are type-agnostic functions such as id or const. Let's extend it to exists a. F a or even exists a. Show a => F a. Given F :: * -> *, the first case is:
Sigma * F -- or Sigma * (\a -> F a)
The second one is a bit trickier. We cannot just take a Show a type class instance and put it somewhere inside. However, if we are given a Show a dictionary (of type ShowDictionary a), we can pack it with the actual value:
Sigma * (\a -> (ShowDictionary a, F a))
-- inside is a pair of "F a" and "Show a" dictionary
This is a bit inconvenient to work with and assumes that we have a Show dictionary around, but it works. Packing the dictionary along is actually what GHC does when compiling existential types, so we could define a shortcut to have it more convenient, but that's another story. As we will learn soon enough, the encoding doesn't actually suffer from this problem.
Digression: thanks to constraint kinds, it's possible to reify the type class into concrete data type. First, we need some language pragmas and one import:
{-# LANGUAGE ConstraintKinds, GADTs, KindSignatures #-}
import GHC.Exts -- for Constraint
GADTs already give us the option to pack a type class along with the constructor, for example:
data BST a where
Nil :: BST a
Node :: Ord a => a -> BST a -> BST a -> BST a
However, we can go one step further:
data Dict :: Constraint -> * where
D :: ctx => Dict ctx
It works much like the BST example above: pattern matching on D :: Dict ctx gives us access to the whole context ctx:
show' :: Dict (Show a) -> a -> String
show' D = show
(.+) :: Dict (Num a) -> a -> a -> a
(.+) D = (+)
We also get quite natural generalisation for existential types that quantify over more type variables, such as exists a b. F a b.
Sigma * (\a -> Sigma * (\b -> F a b))
-- or we could use Sigma just once
Sigma (*, *) (\(a, b) -> F a b)
-- though this looks a bit strange
The encoding
Now, the question is: can we encode Σ-types with just Π-types? If yes, then the existential type encoding is just a special case. In all glory, I present you the actual encoding:
newtype SigmaEncoded (a :: *) (b :: a -> *)
= SigmaEncoded (forall r. ((x :: a) -> b x -> r) -> r)
There are some interesting parallels. Since dependent pairs represent existential quantification and from classical logic we know that:
(∃x)R(x) ⇔ ¬(∀x)¬R(x) ⇔ (∀x)(R(x) → ⊥) → ⊥
forall r. r is almost ⊥, so with a bit of rewriting we get:
(∀x)(R(x) → r) → r
And finally, representing universal quantification as a dependent function:
forall r. ((x :: a) -> R x -> r) -> r
Also, let's take a look at the type of Church-encoded pairs. We get a very similar looking type:
Pair a b ~ forall r. (a -> b -> r) -> r
We just have to express the fact that b may depend on the value of a, which we can do by using dependent function. And again, we get the same type.
The corresponding encoding/decoding functions are:
encode :: Sigma a b -> SigmaEncoded a b
encode (SigmaIntro a b) = SigmaEncoded (\f -> f a b)
decode :: SigmaEncoded a b -> Sigma a b
decode (SigmaEncoded f) = f SigmaIntro
-- recall that SigmaIntro is a constructor
The special case actually simplifies things enough that it becomes expressible in Haskell, let's take a look:
newtype ExistsEncoded (F :: * -> *)
= ExistsEncoded (forall r. ((x :: *) -> (ShowDictionary x, F x) -> r) -> r)
-- simplify a bit
= ExistsEncoded (forall r. (forall x. (ShowDictionary x, F x) -> r) -> r)
-- curry (ShowDictionary x, F x) -> r
= ExistsEncoded (forall r. (forall x. ShowDictionary x -> F x -> r) -> r)
-- and use the actual type class
= ExistsEncoded (forall r. (forall x. Show x => F x -> r) -> r)
Note that we can view f :: (x :: *) -> x -> x as f :: forall x. x -> x. That is, a function with extra * argument behaves as a polymorphic function.
And some examples:
showEx :: ExistsEncoded [] -> String
showEx (ExistsEncoded f) = f show
someList :: ExistsEncoded []
someList = ExistsEncoded $ \f -> f [1]
showEx someList == "[1]"
Notice that someList is actually constructed via encode, but we dropped the a argument. That's because Haskell will infer what x in the forall x. part you actually mean.
From Π to Σ?
Strangely enough (although out of the scope of this question), you can encode Π-types via Σ-types and regular function types:
newtype PiEncoded (a :: *) (b :: a -> *)
= PiEncoded (forall r. Sigma a (\x -> b x -> r) -> r)
-- \x -> is lambda introduction, b x -> r is a function type
-- a bit confusing, I know
encode :: ((x :: a) -> b x) -> PiEncoded a b
encode f = PiEncoded $ \sigma -> case sigma of
SigmaIntro a bToR -> bToR (f a)
decode :: PiEncoded a b -> (x :: a) -> b x
decode (PiEncoded f) x = f (SigmaIntro x (\b -> b))
I found an anwer in Proofs and Types by Jean-Yves Girard, Yves Lafont and Paul Taylor.
Imagine we have some one-argument type t :: * -> * and construct an existential type that holds t a for some a: exists a. t a. What can we do with such a type? In order to compute something out of it we need a function that can accept t a for arbitrary a, that means a function of type forall a. t a -> b. Knowing this, we can encode an existential type simply as a function that takes functions of type forall a. t a -> b, supplies the existential value to them and returns the result b:
{-# LANGUAGE RankNTypes #-}
newtype Exists t = Exists (forall b. (forall a. t a -> b) -> b)
Creating an existential value is now easy:
exists :: t a -> Exists t
exists x = Exists (\f -> f x)
And if we want to unpack the existential value, we just apply its content to a function that produces the result:
unexists :: (forall a. t a -> b) -> Exists t -> b
unexists f (Exists e) = e f
However, purely existential types are of very little use. We cannot do anything reasonable with a value we know nothing about. More often we need an existential type with a type class constraint. The procedure is just the same, we just add a type class constraint for a. For example:
newtype ExistsShow t = ExistsShow (forall b. (forall a. Show a => t a -> b) -> b)
existsShow :: Show a => t a -> ExistsShow t
existsShow x = ExistsShow (\f -> f x)
unexistsShow :: (forall a. Show a => t a -> b) -> ExistsShow t -> b
unexistsShow f (ExistsShow e) = e f
Note: Using existential quantification in functional programs is often considered a code-smell. It can indicate that we haven't liberated ourselves from OO thinking.

Why can't the type of id be specialised to (forall a. a -> a) -> (forall b. b -> b)?

Take the humble identity function in Haskell,
id :: forall a. a -> a
Given that Haskell supposedly supports impredicative polymorphism, it seems reasonable that I should be able to "restrict" id to the type (forall a. a -> a) -> (forall b. b -> b) via type ascription. But this doesn't work:
Prelude> id :: (forall a. a -> a) -> (forall b. b -> b)
<interactive>:1:1:
Couldn't match expected type `b -> b'
with actual type `forall a. a -> a'
Expected type: (forall a. a -> a) -> b -> b
Actual type: (forall a. a -> a) -> forall a. a -> a
In the expression: id :: (forall a. a -> a) -> (forall b. b -> b)
In an equation for `it':
it = id :: (forall a. a -> a) -> (forall b. b -> b)
It's of course possible to define a new, restricted form of the identity function with the desired signature:
restrictedId :: (forall a. a -> a) -> (forall b. b -> b)
restrictedId x = x
However defining it in terms of the general id doesn't work:
restrictedId :: (forall a. a -> a) -> (forall b. b -> b)
restrictedId = id -- Similar error to above
So what's going on here? It seems like it might be related to difficulties with impredicativity, but enabling -XImpredicativeTypes makes no difference.
why is it expecting a type of (forall a. a -> a) -> b -> b
I think the type forall b.(forall a. a -> a) -> b -> b is equivalent to the type you gave. It is just a canonical representation of it, where the forall is shifted as much to the left as possible.
And the reason why it does not work is that the given type is actually more polymorphic than the type of id :: forall c. c -> c, which requires that argument and return types be equal. But the forall a in your type effectively forbids a to be unified with any other type.
You are absolutely correct that forall b. (forall a. a -> a) -> b -> b is not equivalent to (forall a. a -> a) -> (forall b. b -> b).
Unless annotated otherwise, type variables are quantified at the outermost level. So (a -> a) -> b -> b is shorthand for (forall a. (forall b. (a -> a) -> b -> b)). In System F, where type abstraction and application are made explicit, this describes a term like f = Λa. Λb. λx:(a -> a). λy:b. x y. Just to be clear for anyone not familiar with the notation, Λ is a lambda that takes a type as a parameter, unlike λ which takes a term as a parameter.
The caller of f first provides a type parameter a, then supplies a type parameter b, then supplies two values x and y that adhere to the chosen types. The important thing to note is the caller chooses a and b. So the caller can perform an application like f String Int length for example to produce a term String -> Int.
Using -XRankNTypes you can annotate a term by explicitly placing the universal quantifier, it doesn't have to be at the outermost level. Your restrictedId term with the type (forall a. a -> a) -> (forall b. b -> b) could be roughly exemplified in System F as g = λx:(forall a. a -> a). if (x Int 0, x Char 'd') > (0, 'e') then x else id. Notice how g, the callee, can apply x to both 0 and 'e' by instantiating it with a type first.
But in this case the caller cannot choose the type parameter like it did before with f. You'll note the applications x Int and x Char inside the lambda. This forces the caller to provide a polymorphic function, so a term like g length is not valid because length does not apply to Int or Char.
Another way to think about it is drawing the types of f and g as a tree. The tree for f has a universal quantifier as the root while the tree for g has an arrow as the root. To get to the arrow in f, the caller instantiates the two quantifiers. With g, it's already an arrow type and the caller cannot control the instantiation. This forces the caller to provide a polymorphic argument.
Lastly, please forgive my contrived examples. Gabriel Scherer describes some more practical uses of higher-rank polymorphism in Moderately Practical uses of System F over ML. You might also consult chapters 23 and 30 of TAPL or skim the documentation for the compiler extensions to find more detail or better practical examples of higher-rank polymorphism.
I'm not an expert on impredictive types, so this is at once a potential answer and a try at learning something from comments.
It doesn't make sense to specialize
\/ a . a -> a (1)
to
(\/ a . a -> a) -> (\/ b . b -> b) (2)
and I don't think impredictive types are a reason to allow it. The quantifiers have the effect of making the types represented by the left and right side of (2) inequivalent sets in general. Yet the a -> a in (1) implies left and right side are equivalent sets.
E.g. you can concretize (2) to (int -> int) -> (string -> string). But by any system I know this is not a type represented by (1).
The error message looks like it results from an attempt by the Haskel type inferencer to unify the type of id
\/ a . a -> a
with the type you've given
\/ c . (c -> c) -> \/ d . (d -> d)
Here I'm uniqifying quantified variables for clarity.
The job of the type inferencer is to find a most general assignment for a, c, and d that causes the two expressions to be syntactically equal. It ultimately finds that it's required to unify c and d. Since they're separately quantified, it's at a dead end and quits.
You are perhaps asking the question because the basic type inferencer -- with an ascription (c -> c) -> (d -> d) -- would just plow ahead and set c == d. The resulting type would be
(c -> c) -> (c -> c)
which is just shorthand for
\/c . (c -> c) -> (c -> c)
This is provably the least most general type (type theoretic least upper bound) expression for the type of x = x where x is constrained to be a function with the same domain and co-domain.
The type of "restricedId" as given is in a real sense excessively general. While it can never lead to a runtime type error, there are many types described by the expression you've given it - like the aforementioned (int -> int) -> (string -> string) - that are impossible operationally even though your type would allow them.

Resources