Как может эта-сокращение хорошо типизированной функции приводит к ошибке типа?

Я играл с объективами Ван Лаарховена и столкнулся с проблемой, когда контролер типа отклонил отмененную форму с помощью eta-функции:

{-# LANGUAGE RankNTypes #-}

import Control.Applicative

type Lens c a = forall f . Functor f => (a -> f a) -> (c -> f c)

getWith :: (a -> b) -> ((a -> Const b a) -> (c -> Const b c)) -> (c -> b)
getWith f l = getConst . l (Const . f)

get :: Lens c a -> c -> a
get lens = getWith id lens

Вышеупомянутый тип-проверки, но если я и-уменьшу get до

get :: Lens c a -> c -> a
get = getWith id

Тогда GHC (7.4.2) жалуется, что

Couldn't match expected type `Lens c a'
            with actual type `(a0 -> Const b0 a0) -> c0 -> Const b0 c0'
Expected type: Lens c a -> c -> a
  Actual type: ((a0 -> Const b0 a0) -> c0 -> Const b0 c0)
               -> c0 -> b0
In the return type of a call of `getWith'
In the expression: getWith id

Я могу понять, что если функция не имела явной сигнатуры типа, то эта-сокращение в сочетании с ограничением мономорфизма может смутить вывод типа, особенно когда мы имеем дело с более высокими типами рангов, но в этом случае я "Не знаю, что происходит".

Что заставляет GHC отклонять форму, уменьшенную eta, и является ли это ошибкой/ограничением в GHC или какой-то фундаментальной проблемой с более высокими типами рангов?

Ответ 1

Я бы сказал, что причина не в самой η-редукции, проблема в том, что с RankNTypes вы теряете основные типы и введите вывод.

Проблема с типом вывода с рангом более высокого порядка заключается в том, чтобы вывести тип λx.M для подчинения правилу

     Γ, x:σ |- M:ρ
----------------------
  Γ |- λx:σ.M : σ→ρ

мы не знаем, какой тип σ мы должны выбрать для x. В случае системы типа Hindley-Milner мы ограничиваемся типами без кванторов для x, и вывод возможен, но не с произвольными ранжированными типами.

Так что даже при RankNTypes, когда компилятор встречается с термином без информации о явном типе, он обращается к Хиндли-Милнеру и выводит его основной тип ранга-1. Однако в вашем случае тип, который вам нужен для getWith id, является ранг-2, поэтому компилятор не может вывести его сам по себе.

Ваш явный случай

get lens = getWith id lens

соответствует ситуации, когда тип x уже задан явно λ(x:σ).Mx. Компилятор знает тип lens перед проверкой типов getWith id lens.

В приведенном случае

get = getWith id

компилятор должен вывести тип getWidth id на свой собственный, поэтому он придерживается Hindley-Milner и выводит неадекватный тип ранга-1.

Ответ 2

На самом деле это довольно прямолинейно: GHC отображает типы для выражения, затем начинает их унифицировать через =. Это работает всегда отлично, когда вокруг есть только типы ранга 1, потому что выбирается наиболее полиморфная (эта четко определенная); поэтому любое объединение, которое вообще возможно, будет успешным.

Но он не будет выбирать более общий ранг-2-тип, даже если бы это было возможно, поэтому getWith id предполагается, что оно ((a -> Const a a) -> c -> Const a c) -> (c -> a), а не (forall f . Functor f => (a -> f a) -> c -> f c) -> (c -> a). Я полагаю, что если бы GHC делал такие вещи, традиционный вывод типа 1-го уровня больше не работал бы. Или он просто никогда не завершится, потому что не существует одного четко определенного полиморфного типа ранга n.

Это не объясняет, почему он не может видеть из подписи get, что здесь нужно выбрать ранг-2, но, вероятно, для этого есть веская причина.