我经常想要将函数应用于某个变量中的值,以便函数的结果与输入具有相同的类型。如何使类型成功?这是我目前的尝试:
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
这是按原样运行的,但是如果我取消注释let a
行,那么我得到:
Values do not match:
val map : (int -> int) -> [ `Foo of int ] -> [ `Foo of int ]
is not included in
val map : (int -> int) -> ([< generic ] as 'a) -> 'a
似乎定义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
(即只是在开头添加一个明确的'a.
)然后抱怨:
Error: This definition has type (int -> int) -> ([< generic ] as 'a) -> 'a
which is less general than
'b. (int -> int) -> ([< generic ] as 'b) -> 'b
有人可以解释一下发生了什么吗?有一个更好的方法吗?我可以使用GADT来避免使用Obj.magic
,但是我必须将它传递给我希望避免的每个函数调用。
在我的实际程序中,我有各种节点类型(Area
,Project
,Action
,Contact
等等,并且不同的操作适用于不同的类型,但有些是常见的。
例如,with_name
可以重命名任何节点类型,但如果我重命名Action
,则结果必须是另一个操作。如果我重命名[Area | Project | Action]
,那么结果必须是[Area | Project | Action]
等等。
我最初使用了一个元组,其中包含常见的详细信息(例如(name * Action ...)
),但这使得用户很难匹配不同类型(特别是因为它们是抽象的)并且某些功能很常见到子集(例如,只有Project
和Action
可以加星标。)
答案 0 :(得分:3)
你只是被价值限制咬了一口。类型检查器不知道Obj.magic map
中的类型,特别是它们的注入/方差,不能立即推广并等待模块边界。如果您使用merlin,它会显示以下类型:(int -> int) -> (_[< generic ] as 'a) -> 'a
。注意显示单态类型变量的_
。类型变量在第一次使用时专门化,因此当您取消注释a
时的行为。
显然,类型检查员设法在模块边界处进行概括,我不会试图猜测为什么会有(Obj。)魔法。
没有明显的解决方案,因为当你输入一个分支时,ocaml不会让你操纵行变量并且不会对类型变量进行专门化,除非使用gadt(这可能值得一个)功能请求。它与this)相关。
如果您只有少数情况,或者没有复杂的组合,您可以尝试:
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
答案 1 :(得分:2)
类型可能更改的唯一方法是当它很弱时,实际上,模块中的map
类型为(int -> int) -> (_[< generic ] as 'a) -> '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]
而不是更少的[generic]
。换句话说,我们说'a
对于generic
类型的子集是多态的,即它可以实例化为generic
子集的任何类型。有了这个,我们甚至可以隐藏我们的'a t
并在我们的签名中仅显示generic
类型,从而实现最初的目标:
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
答案 2 :(得分:1)
为什么要求map
函数的签名为
val map : (int -> int) -> ([< generic] as 'a) -> 'a
当你试图限制变体类型的“开放性”时,对我来说没什么意义。在我看来,有两个自然的签名 对于地图功能。第一种可能性是
val map : (int -> int) -> [< generic] -> [> generic]
这样map f
可以应用于generic
的子类型,并生成一个自然嵌入generic
的任何超类型的值。该
第二种可能性是
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
编写一个范围是封闭变量的函数对我来说没有意义,应该使用经典的sum类型。也许甚至不可能编写这样的功能,专家可以确认。