Ocaml 映射多态变体的子集

Ocaml 映射多态变体的子集,ocaml,Ocaml,我经常希望将函数应用于某个变量中的值,以便函数的结果与输入具有相同的类型。我怎样才能使这些类型工作?以下是我目前的尝试: module T : sig type generic = [ `Foo of int | `Bar of int ] val map : (int -> int) -> ([< generic] as 'a) -> 'a (* val a : [`Foo of int] *) end = struct type g

我经常希望将函数应用于某个变量中的值,以便函数的结果与输入具有相同的类型。我怎样才能使这些类型工作?以下是我目前的尝试:

module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  val map : (int -> int) -> ([< generic] as 'a) -> 'a

(*   val a : [`Foo of int] *)
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  let map fn = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)
  let map : (int -> int) -> ([< generic] as 'a) -> 'a = Obj.magic map

(*
  let a : [`Foo of int] = `Foo 1 |> map succ
  let b : [`Bar of int] = `Bar 1 |> map succ
*)
end
定义
a
似乎改变了
map
的类型,这似乎有些奇怪

另一方面,将其放在模块结束后工作:

open T
let a : [`Foo of int] = `Foo 1 |> map succ
let b : [`Bar of int] = `Bar 1 |> map succ
最后,如果我将
map
的定义更改为:

  let map : 'a. (int -> int) -> ([< generic] as 'a) -> 'a = Obj.magic map
有人能解释一下发生了什么事吗?有更好的方法吗?我可以使用GADT来避免使用
Obj.magic
,但是我必须将它传递给每个我希望避免的函数调用

关于真实系统的注释 在我的实际程序中,我有各种节点类型(
区域
项目
动作
联系人
,等等),不同的操作适用于不同的类型,但有些是常见的

例如,使用_name的
可以重命名任何节点类型,但如果我重命名一个
操作
,则结果必须是另一个操作。如果我重命名一个
[Area | Project | Action]
,那么结果必须是一个
[Area | Project | Action]
,等等


我最初使用的是元组,外部有公共细节(例如,
(name*Action…
),但这使得用户很难匹配不同的类型(特别是因为它们是抽象的),并且一些特性对于子集来说是公共的(例如,只有
项目
操作
可以加星号).

你刚刚被价值限制咬了一口。类型检查器不知道
Obj.magic map
中的类型,特别是它们的内射性/方差,无法立即进行泛化并等待模块边界。如果您使用merlin查看,它会显示以下类型:
(int->int)->(['a
。请注意显示单态类型变量的
。类型变量在第一次使用时会被专门化,因此在取消注释
a
时会出现这种行为

显然,typechecker设法在模块边界处进行概括,我不会试图猜测原因,因为其中涉及(Obj.)魔力

对于这一点,没有明显的好的解决方案,因为ocaml不允许您在输入一个分支时操作行变量,也不专门化类型变量,除非使用gadt(这可能需要一个功能请求。它与)

如果您只有几个案例,或者没有复杂的组合,您可以尝试:

module T : sig
  type foo = FOO
  type bar = BAR

  type _ generic =
    | Foo : int -> foo generic
    | Bar : int -> bar generic

  val map : (int -> int) -> 'a generic -> 'a generic

  val a : foo generic
  val b : bar generic
end = struct

  type foo = FOO
  type bar = BAR

  type _ generic =
    | Foo : int -> foo generic
    | Bar : int -> bar generic

  let map (type a) fn (x : a generic) : a generic = match x with
    | Foo x -> Foo (fn x)
    | Bar x -> Bar (fn x)

  let a = Foo 1 |> map succ
  let b = Bar 1 |> map succ
end

唯一可能改变类型的方法是当它很弱时,实际上,模块中的
map
具有type
(int->int)->(['a
,请注意这一点。因此,这意味着,在使用地图之前,您需要离开
T
模块:

let a = Foo 1 |> T.map succ
这是一种魅力

关于更一般的方法,不需要任何魔法,那么我将使用以下方法。我将明确定义一个多态类型。唯一的区别是,由于
map
是从
'a t
'a t
a
b
的映射,因此将具有更通用的类型
'a t

module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  type 'a t = 'a constraint 'a = generic
  val a : 'a t
  val b : 'a t
  val map : (int -> int) -> 'a t -> 'a t
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  type 'a t = 'a constraint 'a = generic

  let map fn = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)

  let a = `Foo 1 |> map succ
  let b = `Bar 1 |> map succ
end
但您可能会说,这破坏了整个要点,也就是说,您希望
map
保留其参数的类型,因此如果您使用Foo调用它,它应该返回Foo。这只是意味着我们应该为我们的类型选择另一个约束,即使用
[
而不是不太一般的
[generic]
。换句话说,我们是说我们的
'a
对于
generic
类型的子集是多态的,也就是说,它可以实例化为
generic
类型的子集。有了它,我们甚至可以隐藏我们的
'a t
,并在签名中仅显示
泛型
类型,从而实现最初的目标:

module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  val map : (int -> int) -> generic -> generic

  val a : [`Foo of int]
  val b : [`Bar of int]
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  type 'a t = 'a constraint 'a = [< generic]

  let map fn : 'a t -> 'a t = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)

  let a : [`Foo of int] = `Foo 1 |> map succ
  let b : [`Bar of int] = `Bar 1 |> map succ
end
模块T:sig
类型泛型=
[`Foo of int
|“整型棒]
val映射:(int->int)->generic->generic
val a:[`Foo of int]
val b:[`整巴]
end=struct
类型泛型=
[`Foo of int
|“整型棒]
键入'a t='a约束'a=['a t=函数
|`Foo x->`Foo(fn x)
|`Bar x->`Bar(fn x)
设a:[`Foo of int]=`Foo 1 |>map such
设b:[`Bar of int]=`Bar 1 |>map such
结束

为什么需要对
映射
函数进行签名

val map : (int -> int) -> ([< generic] as 'a) -> 'a
其中,
map f
可应用于
generic
的任何超类型,但只处理
generic
中的变量,并可能保留其他值不变

第一个签名可以实现为

module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  val map : (int -> int) -> [< generic ] -> [> generic ]
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  let map fn = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)
end
module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  val map : (int -> int) -> ([> generic ] as 'a)-> 'a
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  let map fn = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)
    | x -> x
end

对我来说,写一个范围是封闭变量的函数没有什么意义,应该使用经典的求和类型。专家们可能会证实,编写这样一个函数甚至是不可能的。

您的第二个示例看起来很有趣,但没有为我编译(4.02.1):
第二个变量类型不允许标记`Bar
。我希望
map
是多态的
a
b
只是为了测试。好吧,它是多态的,你自己试试看,
让a:[Foo of int]=Foo 1 |>T.map succ
,这在模块之外,可以应用于
generic
的一个子集,而不会失去该子集的可反驳性。你在使用哪一版本的OCaml?我尝试使用4.01和4.02,但它拒绝编译:
文件“test.ml”,第21行,字符26-44:错误:此表达式的类型为泛型t,但表达式的类型应为[`Foo of int]第二个变体类型不允许标记`Bar
,我正在使用
ocamlmerlin
,它接受此代码。事实上,当我用
ocaml
编译器编译时,它显示了相同的结果。看起来我们已经在
ocamlmerlin
中找到了bug,我们将更仔细地查看它…感谢链接。我之前用GADT尝试过,但对于真正的程序,我真的需要子集
val map : (int -> int) -> [< generic] -> [> generic]
val map : (int -> int) -> ([> generic] as 'a) -> 'a
module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  val map : (int -> int) -> [< generic ] -> [> generic ]
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  let map fn = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)
end
module T : sig
  type generic =
    [ `Foo of int
    | `Bar of int ]

  val map : (int -> int) -> ([> generic ] as 'a)-> 'a
end = struct
  type generic =
    [ `Foo of int
    | `Bar of int ]

  let map fn = function
    | `Foo x -> `Foo (fn x)
    | `Bar x -> `Bar (fn x)
    | x -> x
end