The most succinct way of describing the problem is that you are asking for a higher-rank type whereas Elm only supports rank-1 types.

Let’s unpack that terminology a bit by looking at another example that isolates the problem.

```
myPair : (Int, String)
myPair = (identity 0, identity "")
```

We reuse `identity`

for two different inputs of different types. Easy peasy. `identity`

is polymorphic after all.

Let’s say we want to do a common refactor and pull out `identity`

as a parameter to `myPair`

instead.

```
myPairF f = (f 0, f "")
```

Uh oh, Elm has an error.

```
The 1st argument to `f` is not what I expect:
1| myPairF f = (f 0, f "")
^^
This argument is a string of type:
String
But `f` needs the 1st argument to be:
number
Hint: Try using String.toInt to convert it to an integer?
```

But what if we really try to tell Elm that `f`

is meant to be polymorphic just like `identity`

by adding a type annotation?

```
myPairF : (a -> a) -> (Int, String)
myPairF f = (f 0, f "")
```

Still no luck.

```
The 1st argument to `f` is not what I expect:
2| myPairF f = (f 0, f "")
^
This argument is a number of type:
number
But `f` needs the 1st argument to be:
a
```

What’s going on? Elm elides a certain feature of our type annotations that is often called `forall`

.

```
-- The "real" type of identity, note forall isn't valid Elm syntax
identity : forall a. a -> a
```

When you call `identity 0`

, the `Int`

associated with the `0`

is substituted for the variable that appears in the left-most `forall`

.

Importantly, because our `forall`

is on the “outside,” how `a`

gets substituted is determined by the caller of `identity`

. E.g. `identity 0`

turns `a`

into an `Int`

because `0`

is an `Int`

, `identity ""`

turns a into a `String`

because `""`

is a `String`

, etc.

So in `myPair`

everything is fine, `identity`

has its `a`

substituted twice in different ways (by an `Int`

and `String`

respectively) and we’re fine.

What about `myPairF`

?

```
-- Again the "real" type signature of myPairF
myPairF : forall a. (a -> a) -> (Int, String)
```

Again, because `forall`

is on the left-most side of this type signature, **the caller** gets to decide how `a`

is substituted.

So whoever calls `myPairF`

gets to arbitrarily set `a`

to `Int`

, `String`

, `Float`

, or whatever!

This means the following type signatures are all valid specializations of `myPairF`

:

```
myPairF : (String -> String) -> (Int, String)
myPairF : (Int -> Int) -> (Int, String)
-- etc.
```

So for example `myPairF String.toUpper`

turns `a`

into a `String`

. That’s bad! `myPairF String.toUpper`

which makes `f = String.toUpper`

is impossible to square with `f 0`

. This is why when Elm’s typechecker goes to typecheck the definition of `myPairF`

, within `myPairF`

`a`

is an opaque type that is incompatible with `Int`

and `String`

.

So what you need is some way to tell Elm to “delay” the resolution of `a`

.

```
-- Hypothetical Elm syntax
myPairF0 : (forall a. a -> a) -> (Int, String)
myPairF0 f = (f 0, f "")
```

Notice now that `forall a`

has been “moved in” one level; it is no longer on the left-most side. In particular that means arguments to `myPairF0`

cannot cause concrete type substitutions for `a`

. In other words, your caller isn’t allowed now to willy-nilly say “I want `a`

to be an `Int`

.” You can only decide to set `a`

to a concrete value after “unwrapping” it once (in this case by using it in the body of `myPairF0`

.

In particular `myPairF0 String.toUpper`

is a type error because `String -> String`

is not the same type as `forall a. a -> a`

. However, `myPairF0 identity`

works just fine, and yields the original `myPair`

result.

Now `myPairF0`

is an example of a type signature with a rank-2 type, where a rank-n type is how “deep” a `forall`

is in the type signature (in this case it is one additional level deep because it’s inside a set of parentheses, rather than at the outermost layer of the type signature). Elm only supports rank-1 types. That means that `forall`

must always appear on the left-most side of the type signature and cannot be inside any parentheses. That also means that you cannot write the `myPairF0`

function in Elm!

Okay now that we’ve isolated the particular problem, how does this apply to your original puzzle? Let’s write out everything with explicit `forall`

s.

```
-- Notice that forall is on the left-most side!
obj : forall a. { int : Int, fn : a -> a }
obj = { int = 0, fn = identity }
-- This is not the "same" fn as obj.fn
-- You're effectively "re-specializing" obj.fn
-- Read on for what I mean
fn : forall a. a -> a
fn = obj.fn
-- For example this is perfectly valid as well
fn0 : Int -> Int
fn0 = obj.fn
-- Just to drive the point home, notice that this is valid!
-- (which is probably not what you want)
-- Elm is allowing an "in-place" update of `identity` to `\x -> x + 1`!
thisIsValid : { int : Int, fn : Int -> Int }
thisIsValid = { obj | fn = (\x -> x + 1) }
y : forall a. { int : Int, fn : a -> a }
y = fn obj
puzzle : forall a. { int : Int, fn : a -> a } -> ???
puzzle arg = arg.fn arg
-- Notice that because forall is on the left-most side
-- puzzle thisIsValid
-- must be a valid Elm value. But what could it be?
-- `fn` in `thisIsValid` is `Int -> Int` after all.
```

Notice that because `forall`

is on the left-most side of `puzzle`

, it must decided once upfront by the caller, so something like `puzzle thisIsValid`

could completely spoil `puzzle`

!

What you really want is for the `forall`

to be nested inside your record.

```
fixedObj : { int : Int, fn : forall a. a -> a }
fixedObj = { int = 1, fn = identity }
puzzle : { int : Int, fn : forall a. a -> a } -> { int : Int, fn : forall a. a -> a }
puzzle arg = arg.fn arg
-- We're fine here because `arg.fn` exposes its `forall a` _inside__ puzzle.
-- This allows `arg`'s type signature to be substituted for `a`
-- Notice that thisIsValid wouldn't work here:
-- (\x -> x + 1) does not have the type signature forall a. a -> a
puzzle fixedObj -- Works great!
puzzle thisIsValid -- Type error
```

But you can’t write this in Elm and it is unlikely Elm would ever get the ability to move `forall`

s in and out of parentheses. This is because this affects type inference. Notice that `obj`

and `fixedObj`

have identical definitions, but have incompatible type signatures (unlike `Int -> Int`

and `a -> a`

one cannot be substituted for the other). This would mean that certain expressions in Elm would **require*** type annotations (whereas right now type annotations are **always** optional).

* I’m telling a small fib here. It turns out you can preserve global type inference for rank-2 types, but not for any even higher-rank types, e.g. if you had even further levels of nesting of `forall`

. However, the rank-2 type inference algorithm is significantly more complicated than Elm’s current type inference algorithm.

EDIT: I made a small error in one of my code samples (see the edit history of this post for details)

EDIT 2: Also just for accuracy’s sake, what I’ve actually illustrated in the revised type signature of `puzzle`

is something known as impredicative types, but the difference is subtle and somewhat moot for Elm because Elm’s not getting either one unless something drastically changes in its philosophy.

The pure rank-2 type would be

```
puzzle : forall b. (forall a . { int : Int, fn : a -> a }) -> { int : Int, fn : b -> b }
```

but it’s a bit overwhelming to parse the repeated nested `forall`

s and I’d need to explain why `forall b`

is allowed to appear at the beginning of the type signature rather than also being nested with the output (I can if people are interested, but it’d make this reply even longer). So I cheated a little by using impredicative types. However, the fundamental idea is the same: judicious application of `forall`

gets you what you want, but Elm is not capable of expressing it.