假设我有以下代码:
type s = A of a | B of b
let foo (a:?) =
let bar (input:s) = match input with
| A a -> foo input
| B b -> ...
我的问题是我应该在foo的签名中填写问号,以便我不需要(冗余)匹配语句?或者有一个击球手模式来做这个?
答案 0 :(得分:7)
如果你想避免重赛,我会看到3个解决方案:
让函数foo
简单地使用"有效负载"值构造函数A
的值,并将类型s
的值重构为其输出(或与bar
的输出类型匹配的任何其他类型)。
# type a;;
type a
# type b;;
type b
# module Ex1 = struct
type s = A of a | B of b
let foo (a:a) = A a
let bar (input:s) = match input with
| A a -> foo a
| B b -> (* ... *) input
end;;
module Ex1 :
sig
type s = A of a | B of b
val foo : a -> s
val bar : s -> s
end
使用多态变体:
# module Ex2 = struct
type s = [ `A of a | `B of b ]
let foo (`A a) = `A a
let bar (input:s) = match input with
| `A _ as a -> foo a
| `B b -> (* ... *) input
end;;
module Ex2 :
sig
type s = [ `A of a | `B of b ]
val foo : [< `A of 'a ] -> [> `A of 'a ]
val bar : s -> s
end
使用GADT:
# module Ex3 = struct
type _ s =
| A : a -> a s
| B : b -> b s
let foo (a: a s) : a s =
match a with
| A a -> A a
let bar: type x. x s -> x s = fun input ->
match input with
| A _ as a -> foo a
| B b -> (* ... *) input
end;;
module Ex3 :
sig
type _ s = A : a -> a s | B : b -> b s
val foo : a s -> a s
val bar : 'x s -> 'x s
end
答案 1 :(得分:5)
从您的示例开始,解决方案很简单:
type s = A of a | B of b
let foo (a:a) =
let bar (input:s) = match input with
| A a -> foo a
| B b -> ...
但这里不需要约束。看起来你误解了类型约束的想法。一般来说,在OCaml类型约束中不能影响程序。具有和不具有类型约束的程序具有相同的行为。所以,在这里你根本不需要任何约束。您必须将类型注释视为程序员的辅助工具。
我仍然不确定,我明白你想要什么,但是如果你想将你的变种分成子集,并保持这种分裂可以反驳,那么,你可以使用多态变体,就像Pascal建议的那样。 / p>
首先让我重新解释一下这些问题。假设我有类型:
type t = A | B | C | D | E
我有一个模式匹配
let f = function
| A | B | C as x -> handle_abc x
| D | E as x -> handle_de x
我如何向编译器证明handle_abc
仅占用所有可能构造函数的子集,即A | B | C
?
答案是,常规变种是不可能的。但是有多态变体是可能的:
type t = [`A | `B | `C | `D | `E]
let f = function
| `A | `B | `C as x -> handle_abc x
| `D | `E as -> handle_de x
因此,handle_abc
现在只需要对三个变体进行模式匹配,并且不需要任何冗余匹配。此外,您可以为一组构造函数指定名称,并对此名称进行模式匹配:
type abc = [`A | `B | `C ]
type de = [`D | `E ]
type t = [ abc | de ]
let f = function
| #abc as x -> handle_abc x
| #de as -> handle_de x
作为一个真实世界的例子,你可以看看BAP项目,其中多态变体用于表示instruction code。在这里,我们将所有代码分成不同的子组,如所有移动指令,所有分支指令等。之后我们可以直接pattern match对这些群组进行调查。
答案 2 :(得分:1)
您必须在问号中填写Foo签名的类型,然后在其中使用匹配语句。问号所在的位置表示类型。在某种程度上,它通过告诉它你想要什么样的确切类型来协助编译器,并且它将严格确保你在 a 或输入上执行的操作属于匹配类型
匹配语句不是多余的,并且不会损害性能,因为它在OCaml中非常有效。但是我们有另一种方法如下。
或者如果您只有一个参数,则可以通过执行 function 代替匹配来保存一些输入。例如:
let foo (c:s) = match c with ....
我们可以做到
let foo = function
| A -> ...
| B -> ...
请注意,只有传入一个参数时,功能词才会起作用(如果你愿意的话,你可以将所有参数整理到列表中并传入)
这是另一个让我的观点得到解释的例子:
type number = |Int of int
|Float of float
let to_int: (number -> int option) = function
| Int n -> Some n
| _ -> None
(*this is the same as *) (*also note that int option denotes return type*)
let to_int (input:number) : int option =
match input with
| Int n -> Some n
| _ -> None
(*notice how the first one does not have a input as a parameter name*)
let n1:number = Int 1;;
let n2:number = Int 2;;
Example use: `to_int(n1);`
为了清楚起见,没有必要填写它,类型助手也帮助程序员,对我来说,在一些模糊的情况下帮助确保编译器知道我想要的东西。根据我的教授几个学期前的说法,明确提及它以控制类型是一个好习惯。
答案 3 :(得分:1)
incurs a runtime cost的一个解决方案是让变体包含元组而不是单个值。然后更容易捕获整个元组并将其发送到专门的函数:
type s =
(* Note the extra parentheses! *)
| Foo of (int * string)
| Bar of (char * int * string)
let foo (i, s) = "foo"
let bar (c, i, s) = "bar"
let main input =
match input with
| Foo f -> foo f (* `f` is bound to a tuple of type `int * string` *)
| Bar b -> bar b (* `b` is bound to a tuple of type `char * int * string` *)