仅接受sum类型的一个变量作为OCaml函数参数

Com*_*ish 3 ocaml type-inference

我有一个源于现有代码的大量类型.让我们说它看起来像这样:

type some_type =
  | Variant1 of int
  | Variant2 of int * string
Run Code Online (Sandbox Code Playgroud)

虽然这两个Variant1Variant2在其他地方使用,我有一个只能对特定的功能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)

如果Variant1Variant2是不同的类型,我希望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.

gal*_*ais 6

您的帖子中没有足够的信息来决定以下内容是否对您有用.这种方法基于传播一个不变量,并且如果你的代码是不变的,它将很好地发挥作用.基本上,如果你没有类型的函数some_type -> some_type将使用Variant2它们作为头构造函数的值转换为使用那些构造的函数,Variant1那么你应该对这种方法没问题.否则很快就会变得很烦人.

在这里,我们将Variant2使用幻像类型并定义some_typeGADT,将不变的"使用构建" 编码到类型中 .我们首先声明其唯一目的是扮演标签角色的类型.

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将只需要Variant3Variant4作为输入检查你背后的想法.

  | 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)


Tho*_*ash 5

我的解决方案是拥有print_the_string : int * string -> unit,因为该Variant2部分没有提供任何信息,因此您应该删除它。

  • 那么您应该考虑使用记录来存储该变体的内容。尽管如此,类型的大小应该不重要,因为您不应该将其键入,并且无论如何您都必须匹配类型_某处_的内容才能分解它以进行打印。 (2认同)