Код неожиданно принят GHC/GHCi

Я не понимаю, почему этот код должен пройти проверку типов:

foo :: (Maybe a, Maybe b)
foo = let x = Nothing in (x,x)

Поскольку каждый компонент связан с одной и той же переменной x, я бы ожидал, что наиболее общим типом для этого выражения будет (Maybe a, Maybe a). Я получаю те же результаты, если я использую where вместо let. Я что-то пропустил?

Ответ 1

Короче говоря, тип x обобщается на let. Это ключевой шаг в алгоритме вывода типа Hindley-Milner.

Конкретно, let x = Nothing изначально присваивает x тип Maybe t, где t - переменная нового типа. Затем тип обобщается, универсально оценивая все его переменные типа (технически: кроме тех, которые используются в другом месте, но здесь мы имеем только t). Это вызывает x :: forall t. Maybe t x :: forall t. Maybe t. Обратите внимание, что это точно такой же тип, как Nothing :: forall t. Maybe t Nothing :: forall t. Maybe t.

Следовательно, каждый раз, когда мы используем x в нашем коде, это относится к потенциально другому типу Maybe t, как Nothing. По этой причине (x, x) получает тот же тип, что и (Nothing, Nothing).

Вместо этого лямбды не имеют одного и того же шага обобщения. Для сравнения (\x → (x, x)) Nothing "только" не имеет типа forall t. (Maybe t, Maybe t) forall t. (Maybe t, Maybe t), где оба компонента вынуждены быть одного типа. Здесь x снова присваивается тип Maybe t, с t fresh, но он не является обобщенным. Тогда (x, x) присваивается тип (Maybe t, Maybe t). Только на верхнем уровне мы обобщаем добавление forall t, но в этот момент слишком поздно, чтобы получить гетерогенную пару.