使用元组作为参数定义异常

axs*_*uul 5 ocaml

我试图在OCaml中定义一个异常,它接受一对元组列表作为参数.但是,这种情况不起作用?

# exception Foo of string list * string list;; 
exception Foo of string list * string list
# let bar = (["a"], ["b"; "c"; "d"]);;
val bar : string list * string list = (["a"], ["b"; "c"; "d"])
# raise(Foo bar);;
Error: The constructor Foo expects 2 argument(s),
       but is applied here to 1 argument(s)
Run Code Online (Sandbox Code Playgroud)

但是,如果我这样做,它就可以了

# raise (Foo (["a"], ["b"; "c"; "d"]));;
Exception: Foo (["a"], ["b"; "c"; "d"]).
Run Code Online (Sandbox Code Playgroud)

这是怎么回事?谢谢!

Vic*_*let 11

你看错了(虽然我不会责怪你:起初这是非常令人惊讶的).在你看来,构造函数遵循语法Name of type,其中类型部分遵循普通类型语法(允许它包含元组).

实际上,元组和构造函数遵循完全相同的语法:构造函数只是一个在其前面有名称的元组:

tuple/constructor == [name of] type [* type] [* type] ...
Run Code Online (Sandbox Code Playgroud)

因此,*构造函数定义不是元组语法的一部分,它们是构造函数语法的一部分.您实际上将构造函数定义为此名称,后跟N个参数而不是此名称,后跟一个参数,它是一个元组.

这种微妙的行为差异背后的原因是性能.现在,元组和构造函数在内存中表示如下:

[TYPE] [POINTER] [POINTER] [POINTER]
Run Code Online (Sandbox Code Playgroud)

这是一种相当紧凑和有效的表示.如果构造函数的多个参数确实可以作为元组访问,那么这将要求运行时独立于构造函数来表示该元组(为了使其可以独立寻址),所以它看起来像这样:

[TYPE] [POINTER]
           |
           v
          [TYPE] [POINTER] [POINTER] [POINTER]
Run Code Online (Sandbox Code Playgroud)

这将使用更多的内存,在使用构造函数时需要两倍的分配,并降低模式匹配元组的性能(因为额外的解引用).为了保持最大的性能,name of type * type使用第一图案表示,并且需要显式输入name of (type * type),以切断*of并由此落回到所述第二图案.

请注意,这两种模式都是通过相同的模式匹配和构造语法访问的:name (arg,arg).这意味着类型推断不能基于用法推断出模式.这对于常规构造函数来说没有问题,它总是在类型定义中定义,但它会导致变体(不需要初步定义)自动回退到第二个版本.

此处有关类型的内存表示的附加读数.


Pas*_*uoq 5

在这方面,OCaml的异常构造函数就像普通构造函数一样:

Constr(1,2,3)是一种特殊的句法结构,其中不会出现三元组.另一方面,三重发生在Constr((1,2,3)).实现匹配此行为,将Constr(1,2,3)其分配为单个块,并Constr((1,2,3))作为两个块,一个包含指向另一个块(三元组)的指针.在运行时表示中Constr(1,2,3)没有三元组来获取指针,如果需要,则必须分配一个新的指针.

注意:Constr(((1,2,3)))相当于Constr((1,2,3)).在Constr(((1,2,3))),中间括号被解释为绕过表达式(1,2,3),并且在表达式中的括号被遗忘在抽象语法树中.