简体   繁体   中英

Why is this eta expansion necessary?

Can someone help me understand this/point me to some reading materials? The following works fine:

type F a b = Functor f => f a -> f b
fComp :: F b c -> F a b -> F a c
fComp f f' = f . f'

But if I write fComp = (.) instead, the type checker complains:

Couldn't match type ‘b0 -> f c’
              with ‘forall (f1 :: * -> *). Functor f1 => f1 b -> f1 c’

(This specific example isn't particularly useful; I'm just trying to shrink an example that came up while studying lenses.)

fComp has a higher-rank type and type inference for higher-rank types is very limited. It might be a little easier to understand (but much longer!) if we expand the type synonym:

fComp :: forall f a b c. Functor f => 
                (forall f1. Functor f1 => f1 b -> f1 c) ->
                (forall f2. Functor f2 => f2 a -> f2 b) ->
                (f a -> f c)

The higher-rank types of f and f' are specified explicitly in this type signature. This lets the type inference start already knowing the types of f and f' and so being able to unify them with the type of . .

However, if you get rid of f and f' , the type that . has to take is not known. Unfortunately, the system can't infer higher-rank types like this, so you get a type error.

In essence, the compiler can't create higher-rank types to fill unknowns during type inference and has to rely on programmer annotations and we need both the name ( f and f' ) and the type signature to get those annotations.

An easier example to understand would be a higher-rank id function:

myId :: (forall a. a) -> (forall b. b)

The definition myId x = id x compiles, but myId = id gives the following error:

/home/tikhon/Documents/so/eta-expansion-needed.hs:11:8:
    Couldn't match type ‘b’ with ‘forall a. a’
      ‘b’ is a rigid type variable bound by
          the type signature for myId :: (forall a. a) -> b
          at /home/tikhon/Documents/so/eta-expansion-needed.hs:11:1
    Expected type: (forall a. a) -> b
      Actual type: b -> b
    In the expression: id
    In an equation for ‘myId’: myId = id
Failed, modules loaded: none.

(Keep in mind that forall b. (forall a. a) -> b is the same as (forall a. a) -> (forall b. b) .)

Let me rewrite the example using a System-F-like notation, wherein we pass types as well. Below, \\\\ stands for type-abstraction (big lambda), as well as dictionary abstraction. Also, @ stands for type/dictionary application.

Before doing that, recall the type of (.) :

(.) :: forall a b c . (b -> a) -> (c -> b) -> (c -> a)

Here's the annotated code (beware, not for the faint of heart):

fComp :: F b c -> F a b -> F a c
fComp (f :: forall f1. Functor f1 => f1 b -> f1 c)
      (f':: forall f2. Functor f2 => f2 a -> f2 b) 
   = \\ ff :: (* -> *) ->
     \\ ffD :: Functor ff ->
     ((.) @ (ff c) @ (ff b) @ (ff a))   -- instantiated composition
     (f  @ ff @ ffD)                    -- first argument of (.)
     (f' @ ff @ ffD)                    -- second argument of (.)

(Above I pretended a , b , c are type constants to avoid further type-level lambdas.)

The important parts:

  • f and f' are being used at specific types. That is they are being applied to type-level arguments before being fed to (.) .
  • (.) is being applied at the type level to types ( ff c , etc) which are not the polytypes of f and f'

As you can see, the original code is far from being trivial. Type inference is able to add the needed type-level lambdas and applications. After those are added, we can not simply eta contract fComp anymore.

In the pointfree variant, type inference would need to do more than in the pointful case. While the first argument of fComp is of type F ab , the first argument of (.) must be of the form x -> y , which does not unify for F ab = forall g . ... F ab = forall g . ... . Indeed, there's no way to successfully solve the typing attempt below:

fComp :: F b c -> F a b -> F a c
fComp 
   = \\ ff :: (* -> *) ->
     \\ ffD :: Functor ff ->
     ((.) @ ???a @ ???b @ ???c)

Above there's no ???a ,... that can lead to the wanted type.

The only possibility would be to instantiate the forall -quantified variables hidden in the F xy types, but to do that we need a point . The compiler might eta-expand that code for you so that points appear and so can be instantiated, theoretically speaking, but in practice will not.

(Also, eta expansion is not always valid in Haskell: eg seq (undefined::()->()) 3 loops while seq (\\x->(undefined::()->()) x) 3 returns 3 ).

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