Можно ли комментировать составленный полиморфный вариант типа? - PullRequest
0 голосов
/ 25 декабря 2018

Я использую полиморфные варианты для обработки ошибок с типами результатов (взяты из 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 ]

0 голосов
/ 29 декабря 2018

Есть ли причина, по которой вам нужен тип 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 ]
0 голосов
/ 26 декабря 2018

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

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

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

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

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

...