Почему порядок операндов влияет на область видимости?

Я пытаюсь понять, почему определенный конструктор принимается в одном выражении, а не в другом. Я ожидал, что это будет вне сферы действия в обоих случаях. Я новичок в OCaml (в основном я использую Haskell), поэтому я мог упустить что-то совершенно очевидное для опытного человека.

type zero = Zero
type 'n succ = Succ
type 'n snat =
  | SZero : zero snat
  | SSucc : 'm snat -> 'm succ snat

module SimpleInduction (Pred : sig type 'n pred end) = struct
  open Pred
  type hyps =
    { base : zero pred
    ; step : 'm. 'm pred -> 'm succ pred}

  let rec induct : type n. hyps -> n snat -> n pred =
          fun h sn -> match sn with
          | SZero -> h.base
          | SSucc p -> h.step (induct h p)
end;;

let module Snot = struct type 'n pred = Top end in
  let module Goop = SimpleInduction(Snot) in
    Goop.induct {base = Top; step = fun _ -> Top} SZero = Top;;
(*
let module Snot = struct type 'n pred = Top end in
  let module Goop = SimpleInduction(Snot) in
    Top = Goop.induct {base = Top; step = fun _ -> Top} SZero;;
*)

Это компилируется просто отлично, по какой-то причине. Когда второе определение Snot раскомментировано, я получаю сообщение об ошибке:

19 |     Top = Goop.induct {base = Top; step = fun _ -> Top} SZero;;
         ^^^
Error: Unbound constructor Top

Что вводит Top в рамки первого определения Snot? Использование обычных модулей, а не первоклассных локальных, не имеет значения.

Если я использую Snot.Top с левой стороны, я не получаю жалоб с правой стороны. Почему это?

Я подозреваю, что это просто выведено из типа Goop.induct. Типы выводятся за один проход, слева направо, поэтому, когда он встречает Top во втором определении, он понятия не имеет, что Snot.pred задействован.

glennsl 10.12.2020 21:40

Кроме того, немного не по делу, но все же полезно знать, что здесь не используются первоклассные модули. Существуют локальные модули и функторы, но ни один из них не является действительно первоклассным в том смысле, что их можно передавать в качестве значений обычным функциям. Модуль первого класса — это представление модуля на уровне значений, которое вы создаете, упаковывая его вместе с сигнатурой. Например. let first_class_module = (module M : MSig).

glennsl 10.12.2020 21:48

@glennsl, значит, компилятор просто помещает конструктор в область видимости, если он может сделать вывод, что я ищу что-то в этом роде?

dfeuer 10.12.2020 21:49

По крайней мере, это мое понимание. Я ожидаю, что вы получите ответ с подробным объяснением механизма в течение дня или двух, как это обычно бывает :)

glennsl 10.12.2020 21:53
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
81
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Короче говоря, направленное на тип устранение неоднозначности действительно не ограничивается рамками.

С явной аннотацией типа средство проверки типов может выбрать конструктор из типа, не помещая конструктор в область видимости.

Например,

module M = struct type 'a t = A of 'a end
let ok: _ M.t = A ()
let wrong: _ M.t = A (A ())

первый пример действителен, потому что аннотации типа достаточно, чтобы знать, что A в A () является _ A.t. Однако второй пример не работает, потому что конструктор не попал в область видимости.

Кроме того, для устранения неоднозначности по типу требуется, чтобы был известен только ожидаемый тип конструктора или записи. Как правило, в этом примере

let expected =
  let f (M.A x) = x in
  f (A ())

мы знаем, что тип аргумента f является _ M.t, поэтому мы знаем, что A в f (A ()) происходит от _ M.t, и мы можем использовать устранение неоднозначности, направленное на тип, как в случае с явной аннотацией.

Если вы считаете такое поведение экзотическим, в такой ситуации можно использовать предупреждение 42 [имя вне области видимости]. Компиляция вашего примера с этим предупреждением дает (среди многих других случаев этого предупреждения)

23 |     Goop.induct {base = Top; step = fun _ -> Top} SZero = Top
                                                               ^^^
Warning 40 [name-out-of-scope]: Top was selected from type Snot.pred.
It is not visible in the current scope, and will not 
be selected if the type becomes unknown.

(имена предупреждений новые в 4.12)

Что касается вашего второго пункта, порядок выражения может иметь значение при отсутствии явных аннотаций. Действительно, без явной аннотации устранение неоднозначности, направленное на тип, сможет выбрать правильный конструктор только тогда, когда ожидаемый тип уже известен. А проверка типов в OCaml идет слева направо. Таким образом, в

... = Top

тип левой части уже выведен, и поэтому ожидаемый тип Top равен _ Snot.pred. Когда порядок обратный

Top = ...

средство проверки типов пытается найти конструктор Top без какой-либо информации о типе, и в области видимости нет конструктора Top. Таким образом, он терпит неудачу с ошибкой Unbound constructor. Если вы хотите избежать зависимости от порядка, вы можете либо

  • напишите полное имя конструктора:
  Snot.Top = ...
  • использовать явную аннотацию типа
(Top: _ Snot.pred) = ...
  • откройте модуль Snot.
  Snot.( Top ) = ...
  (* or *)
  let open Snot in Top = ...

Я бы посоветовал использовать одно из этих решений, поскольку они более надежны.

В конце концов, полагаться на конкретную реализацию проверки типов нельзя. На самом деле, есть флаг компилятора -principal и предупреждение (18) [not-Principal], которое заботится о том, чтобы выдать предупреждение при наличии такого потенциально хрупкого вывода:

23 |     Goop.induct {base = Top; step = fun _ -> Top} SZero = Top
                                                               ^^^
Warning 18 [not-principal]: this type-based constructor disambiguation is not principal.

Здесь «не основной» означает, что результат устранения неоднозначности на основе типов зависел от порядка проверки типов.

Другие вопросы по теме