Ответ 1
Конкретный случай, о котором вы спрашиваете, может быть успешно решён с использованием GADT и полиморфных
варианты. См. Вызовы M.add
в нижней части этого кода:
type whole = [ `Integer ]
type general = [ whole | `Float ]
type _ num =
| I : int -> [> whole ] num
| F : float -> general num
module M :
sig
val add : ([< general ] as 'a) num -> 'a num -> 'a num
val to_int : whole num -> int
val to_float : general num -> float
end =
struct
let add : type a. a num -> a num -> a num = fun a b ->
match a, b with
| I n, I m -> I (n + m)
| F n, I m -> F (n +. float_of_int m)
(* Can't allow the typechecker to see an I pattern first. *)
| _, F m ->
match a with
| I n -> F (float_of_int n +. m)
| F n -> F (n +. m)
let to_int : whole num -> int = fun (I n) -> n
let to_float = function
| I n -> float_of_int n
| F n -> n
end
(* Usage. *)
let () =
M.add (I 1) (I 2) |> M.to_int |> Printf.printf "%i\n";
M.add (I 1) (F 2.) |> M.to_float |> Printf.printf "%f\n";
M.add (F 1.) (I 2) |> M.to_float |> Printf.printf "%f\n";
M.add (F 1.) (F 2.) |> M.to_float |> Printf.printf "%f\n"
Что печатает
3
3.000000
3.000000
3.000000
Вы не можете изменить любой из вышеперечисленных to_float
на to_int
: он статически
что только добавление двух I
приводит к I
. Однако вы можете изменить
to_int
до to_float
(и отрегулируйте printf
). Эти операции легко компилируют и распространяют информацию о типе.
Глупость с вложенным выражением match
- это взлом, который я попрошу на
список рассылки. Я никогда не видел этого раньше.
Функции общего типа
AFAIK единственный способ оценить общую функцию типа в текущем OCaml требует пользователь должен предоставить свидетеля, то есть некоторую дополнительную информацию о типе и значении. Эта может быть сделано многими способами, например, обертывание аргументов в дополнительных конструкторах (см. ответ @mookid), используя первоклассные модули (также обсуждаемые в следующем раздел), предоставляя небольшой список абстрактных значений на выбор (который реализовать реальную операцию, а оболочка отправляет эти значения). пример ниже использует второй GADT для кодирования конечного отношения:
type _ num =
| I : int -> int num
| F : float -> float num
(* Witnesses. *)
type (_, _, _) promotion =
| II : (int, int, int) promotion
| IF : (int, float, float) promotion
| FI : (float, int, float) promotion
| FF : (float, float, float) promotion
module M :
sig
val add : ('a, 'b, 'c) promotion -> 'a num -> 'b num -> 'c num
end =
struct
let add (type a) (type b) (type c)
(p : (a, b, c) promotion) (a : a num) (b : b num) : c num =
match p, a, b with
| II, I n, I m -> I (n + m)
| IF, I n, F m -> F (float_of_int n +. m)
| FI, F n, I m -> F (n +. float_of_int m)
| FF, F n, F m -> F (n +. m)
end
(* Usage. *)
let () =
M.add II (I 1) (I 2) |> fun (I n) -> n |> Printf.printf "%i\n";
M.add IF (I 1) (F 2.) |> fun (F n) -> n |> Printf.printf "%f\n"
Здесь функция типа ('a, 'b, 'c) promotion
, где 'a
, 'b
являются
аргументы и 'c
- результат. К сожалению, вы должны пройти add
экземпляр promotion
для 'c
, чтобы быть заземленным, то есть что-то вроде этого не будет
(AFAIK):
type 'p result = 'c
constraint 'p = (_, _, 'c) promotion
val add : 'a num -> 'b num -> ('a, 'b, _) promotion result num
Несмотря на то, что 'c
полностью определяется 'a
и 'b
, из-за GADT; компилятор все еще видит, что в основном просто
val add : 'a num -> 'b num -> 'c num
Свидетели действительно не покупают вас, просто имея четыре функции, за исключением того, что
набор операций (add
, multiply
и т.д.) и тип аргумента/результата
комбинации, могут быть сделаны в основном ортогональными друг другу; типизация может быть
лучше, и вещи могут быть немного проще в использовании и реализации.
EDIT На самом деле можно отказаться от конструкторов I
и F
, т.е.
val add : ('a, 'b, 'c) promotion -> 'a -> 'b -> `c
Это делает использование намного проще:
M.add IF 1 2. |> Printf.printf "%f\n"
Однако в обоих случаях это не так сложно, как решение полиморфных вариантов GADT +, поскольку свидетель никогда не выведен.
Future OCaml: модульные импликации
Если ваш свидетель является первоклассным модулем, компилятор может выбрать его для вас
автоматически с модульными имплицитами. Вы можете попробовать этот код в
4.02.1+modular-implicits-ber
. Первый пример просто обертывает свидетелей GADT из предыдущего примера в модулях, чтобы заставить компилятор выбрать их для вас:
module type PROMOTION =
sig
type a
type b
type c
val promotion : (a, b, c) promotion
end
implicit module Promote_int_int =
struct
type a = int
type b = int
type c = int
let promotion = II
end
implicit module Promote_int_float =
struct
type a = int
type b = float
type c = float
let promotion = IF
end
(* Two more like the above. *)
module M' :
sig
val add : {P : PROMOTION} -> P.a num -> P.b num -> P.c num
end =
struct
let add {P : PROMOTION} = M.add P.promotion
end
(* Usage. *)
let () =
M'.add (I 1) (I 2) |> fun (I n) -> n |> Printf.printf "%i\n";
M'.add (I 1) (F 2.) |> fun (F n) -> n |> Printf.printf "%f\n"
С модульными implicits вы также можете просто добавлять немаркированные поплавки и ints. Этот пример соответствует отправке функции "свидетель":
module type PROMOTING_ADD =
sig
type a
type b
type c
val add : a -> b -> c
end
implicit module Add_int_int =
struct
type a = int
type b = int
type c = int
let add a b = a + b
end
implicit module Add_int_float =
struct
type a = int
type b = float
type c = float
let add a b = (float_of_int a) +. b
end
(* Two more. *)
module M'' :
sig
val add : {P : PROMOTING_ADD} -> P.a -> P.b -> P.c
end =
struct
let add {P : PROMOTING_ADD} = P.add
end
(* Usage. *)
let () =
M''.add 1 2 |> Printf.printf "%i\n";
M''.add 1 2. |> Printf.printf "%f\n"