Можно ли комментировать составленный полиморфный вариант типа?

Я использую полиморфные варианты для обработки ошибок с типами результатов (взяты из http://keleshev.com/composable-error-handling-in-ocaml), и это отлично подходит для исчерпывающей проверки. Недавно я столкнулся с необходимостью аннотировать тип результата в функторе, но не уверен, возможно ли это. Это фрагмент с некоторыми комментариями о том, что я пытаюсь сделать:

open Belt;

/* A quick example of what's working for us right now. This is fine and
   the result error type is [> `Error1 | `Error2 ] */
let res = Result.flatMap(Result.Error(`Error1), _ => Result.Error(`Error2));

/* A really generic version of what what we're trying to do */
module type General = {
  type t;
  type error;
  let res: Result.t(t, error);
};

module Make = (M: General) => {
  let res = M.res;
};

module Specific1 =
  Make({
    type t = string;
    type error = [ | `Specific1Error];
    let res = Result.Error(`Specific1Error);
  });

module Specific2 =
  Make({
    type t = int;
    type error = [ | `Specific2Error];
    let res = Result.Error(`Specific2Error);
  });

/* This definitely doesn't compile because the two error types
   aren't the same but wondering if anything above can be changed so it
   understands the error type is [> `Specific1Error | `Specific2Error] */
let res = Result.flatMap(Specific1.res, _ => Specific2.res);

2 ответа

Это не полный ответ, но он предоставляет немного больше информации и одного возможного решения или обходного пути.

Можно получить последнюю строку для компиляции, добавив явное приведение к конкретному комбинированному типу:

let res =
    Result.flatMap(
      Specific1.res :> Result.t(string, [`Specific1Error | `Specific2Error]),
      _ => (Specific2.res :> Result.t(int, [`Specific1Error | `Specific2Error])));

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

Если type error определили нижнюю границу, мы не должны были бы быть явными, как продемонстрировано этим:

let error1 : Result.t(int, [> `Error1]) = Result.Error(`Error1);
let error2 : Result.t(int, [> `Error2]) = Result.Error(`Error2);

let res = Result.flatMap(error1, _ => error2);

Но поскольку полиморфные варианты верхней и нижней границ имеют неявную переменную типа, нам, по крайней мере, придется изменить type error в type error('a), К сожалению, даже тогда я не уверен, как заставить подпись модуля совпадать с реализациями, так как, например, это:

type error('a) = [> | `Specific1Error] as 'a;

не удается с

Signature mismatch:
...
Type declarations do not match:
  type 'a error = 'a constraint 'a = [> `Specific1Error ]
is not included in
  type 'a error
Their constraints differ.

Также невозможно привести к полиморфному типу варианта с нижней границей, и я не уверен, почему это так:

let res =
    Result.flatMap(
      Specific1.res :> Result.t(string, [> `Specific1Error]),
      _ => (Specific2.res :> Result.t(int, [> `Specific2Error])));

не удается с

This has type:
  (int, [ `Specific2Error ]) Result.t
But somewhere wanted:
  (int, [ `Specific1Error ]) Result.t
These two variant types have no intersection

что указывает на то, что границы просто игнорируются.

Я достиг пределов своих знаний здесь по нескольким направлениям, но я добавил ocaml к вашему вопросу, так как у него есть несколько последователей со значительными знаниями, которые, мы надеемся, могут объединить последние части.

Есть ли причина, по которой вам нужно error тип запечатан внутри вашего General подпись модуля? Похоже, вам нужно знать сумму всех этих вариантов ошибок, чтобы аннотировать окончательный вариант. res Значение в любом случае, вы можете сделать следующее? (Пожалуйста, извините за перевод в стандартный синтаксис и идиомы OCaml.)

open Core

type error =
  [ `Specific1Error
  | `Specific2Error
  ]

module type General = sig
  type t
  val res : (t, error) Result.t
end

module Make (M: General) = struct
  let res = M.res
end

module Specific1 =
  Make (struct
      type t = string
      let res = Result.Error `Specific1Error
    end)

module Specific2 =
  Make (struct
    type t = int
    let res = Result.Error `Specific2Error
  end)

(* This type expands to
 * (int, [ `Specific1Error | `Specific2Error ]) result
 *)
let res : ('a , error) Result.t =
  let open Result.Monad_infix in
  Specific1.res >>= (fun _ -> Specific2.res)

(* These errors will still compose with others down the line: *)
type error_plus = [error | `More_errors ]
Другие вопросы по тегам