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