Com*_*ish 3 ocaml type-inference
我有一个源于现有代码的大量类型.让我们说它看起来像这样:
type some_type =
| Variant1 of int
| Variant2 of int * string
Run Code Online (Sandbox Code Playgroud)
虽然这两个Variant1和Variant2在其他地方使用,我有一个只能对特定的功能Variant2:
let print_the_string x =
match x with
| Variant2(a,s) -> print_string s; ()
| _ -> raise (Failure "this will never happen"); ()
Run Code Online (Sandbox Code Playgroud)
由于此辅助函数仅从另一个地方调用,因此很容易显示它将始终使用输入调用Variant2,而不是输入Variant1.
让我们说这个电话看起来像这样:
let () =
print_the_string (Variant2(1, "hello\n"))
Run Code Online (Sandbox Code Playgroud)
如果Variant1和Variant2是不同的类型,我希望OCaml的推断类型Variant2 -> ()的print_the_string,但是,因为它们是相同的总和型的两种变体,OCaml的推断签名some_type -> ().
当我遇到一个抛出异常的程序时,会发出一条消息,例如"这将永远不会发生",我通常认为原来的程序员做错了什么.
当前的解决方案有效,但这意味着程序中的错误将在运行时捕获,而不是作为编译器错误而不是更好.
理想情况下,我希望能够像这样注释函数:
let print_the_string (x : some_type.Variant2) =
Run Code Online (Sandbox Code Playgroud)
但是,当然,这是不允许的.
问题:有没有办法在Variant1传递给任何情况下导致编译器错误print_the_string?
这里提出了一个相关的问题,但是nlucarioni和Thomas的答案只是解决了处理不正确呼叫的更简洁方法.我的目标是让程序更明显地失败,而不是更少.
更新:我接受了加莱的解决方案,因为在玩完它后,它似乎是实现这样的最干净的方式.不幸的是,如果没有非常凌乱的包装器,我不相信任何解决方案都适用于我无法修改原始定义的情况some_type.
您的帖子中没有足够的信息来决定以下内容是否对您有用.这种方法基于传播一个不变量,并且如果你的代码是不变的,它将很好地发挥作用.基本上,如果你没有类型的函数some_type -> some_type将使用Variant2它们作为头构造函数的值转换为使用那些构造的函数,Variant1那么你应该对这种方法没问题.否则很快就会变得很烦人.
在这里,我们将Variant2使用幻像类型并定义some_type为GADT,将不变的"使用构建" 编码到类型中
.我们首先声明其唯一目的是扮演标签角色的类型.
type variant2
type variantNot2
Run Code Online (Sandbox Code Playgroud)
现在,我们可以使用这些类型来记录用于生成值的构造函数some_type.这是Ocaml中的GADT语法; 它与ADT略有不同,因为我们可以声明构造函数的返回类型是什么,不同的构造函数可以有不同的返回类型.
type _ some_type =
| Variant1 : int -> variantNot2 some_type
| Variant2 : int * string -> variant2 some_type
Run Code Online (Sandbox Code Playgroud)
只要他们的签名记录他们不是的事实,人们也可以投入几个额外的构造函数Variant2.我今后不会处理它们,但你可以尝试扩展下面给出的定义,以便它们能够很好地处理这些额外的构造函数.您甚至可以添加一个print_the_second_int将只需要Variant3与Variant4作为输入检查你背后的想法.
| Variant3 : int * int -> variantNot2 some_type
| Variant4 : float * int -> variantNot2 some_type
Run Code Online (Sandbox Code Playgroud)
现在,类型print_the_string可以非常精确:我们只对some_type使用构造函数构建的元素感兴趣Variant2.换句话说,输入print_the_string应该有类型variant2 some_type.并且编译器可以静态地检查这Variant2是该类型的值的唯一可能的构造函数.
let print_the_string (x : variant2 some_type) : unit =
match x with Variant2 (_, s) -> print_string s
Run Code Online (Sandbox Code Playgroud)
好.但是如果我们有类型的价值,'a some_type因为它是由客户交给我们的呢?我们建造它扔硬币; 等等.?好吧,那里没有魔力:如果你想使用print_the_string,你需要确保使用Variant2构造函数构建了这个值.您可以尝试将值强制转换为variant2 some_type1(但这可能会失败,因此使用该option类型):
let fromVariant2 : type a. a some_type -> (variant2 some_type) option = function
| Variant2 _ as x -> Some x
| Variant1 _ -> None
Run Code Online (Sandbox Code Playgroud)
或者(甚至更好!)决定价值在哪个领域:
type ('a, 'b) either = | Left of 'a | Right of 'b
let em : type a. a some_type -> (variant2 some_type, variantNot2 some_type) either =
fun x -> match x with
| Variant1 _ -> Right x
| Variant2 _ -> Left x
Run Code Online (Sandbox Code Playgroud)
我的解决方案是拥有print_the_string : int * string -> unit,因为该Variant2部分没有提供任何信息,因此您应该删除它。
| 归档时间: |
|
| 查看次数: |
487 次 |
| 最近记录: |