Моделирование грамматики с помощью GADT, но параметр типа не унифицируется

Я подумал, что нашел изящный способ моделирования грамматик с использованием GADT, предоставив каждому конструктору (например, Char) параметр возвращаемого типа, который является полиморфным вариантом (например, [`Char] t), а затем с помощью функции унификации полиморфных вариантов, чтобы ограничить допустимые конструкторы следующими (например, [<`Minus | `Char] t).

(* Grammar ::= Open (Minus? Char)+ Close *)

type _ t =
  | Open:  [<`Minus | `Char] t -> [`Open] t
  | Minus: [<`Char] t -> [`Minus] t
  | Char:  [<`Minus | `Char | `Close] t -> [`Char] t
  | Close: [`Close] t

Это работает по назначению, разрешая только те последовательности, которые соответствуют грамматике:

let example =
  Open (Minus (Char (Char (Minus (Char (Close))))))

(* let counter_example =
  Open (Minus (Char (Minus (Minus (Char (Close)))))) *)

Однако написать какую-либо тривиальную функцию для этого типа невозможно:

let rec to_list = function
  | Open rest -> "(" :: to_list rest
  | Minus rest -> "-" :: to_list rest
  | Char rest -> "a" :: to_list rest
  | Close -> [")"]
File "./tmp.ml", line 21, characters 4-14:
21 |   | Minus rest -> "-" :: to_list rest
         ^^^^^^^^^^
Error: This pattern matches values of type [ `Minus ] t
       but a pattern was expected which matches values of type [ `Open ] t
       Type [ `Minus ] is not compatible with type [ `Open ]

Насколько я понимаю, полиморфные варианты не унифицированы, потому что параметры типа GADT не могут иметь дисперсии. Значит ли это, что такой подход совершенно бесполезен?

1 ответ

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

let rec to_list : type a. a t -> string list = function
  | Open rest -> "(" :: to_list rest
  | Minus rest -> "-" :: to_list rest
  | Char rest -> "a" :: to_list rest
  | Close -> [")"]

Для объяснения, @octachron написал отличный ответ на аналогичный вопрос ранее сегодня.

Редактировать:

Чтобы ограничить эту функцию подмножеством случаев, я думаю, вам нужно применить две разные сигнатуры типов. Одна внешняя подпись для полиморфного варианта и одна внутренняя для локально абстрактного типа. Однако, поскольку функция рекурсивна, нам также необходимо, чтобы локально абстрактный тип был внешним, когда мы рекурсивно используем его. В противном случае мы могли бы просто аннотировать значение, сопоставленное с шаблоном.

Я могу придумать несколько способов сделать это:

Вариант 1, оберните его в модуль (или поместите внешнюю подпись в.mli):

module M : sig
  val to_list : [<`Minus | `Char | `Close] t -> string list
end = struct
  let rec to_list : type a. a t -> string list = function
    | Open rest -> "(" :: to_list rest
    | Minus rest -> "-" :: to_list rest
    | Char rest -> "a" :: to_list rest
    | Close -> [")"]
end

Вариант 2, используйте локально определенную функцию, чтобы поставить "внутреннюю" подпись:

let to_list : [<`Minus | `Char | `Close] t -> string list =
  let rec aux : type a. a t -> string list = function
    | Open rest -> "(" :: aux rest
    | Minus rest -> "-" :: aux rest
    | Char rest -> "a" :: aux rest
    | Close -> [")"]
  in
  aux
Другие вопросы по тегам