Ocaml 流解析器AST上模式匹配的一般策略

Ocaml 流解析器AST上模式匹配的一般策略,ocaml,flowtype,Ocaml,Flowtype,我正在做一个使用流解析器的项目。我对OCaml有点陌生,所以所有的参数化类型都让我头晕目眩。举一个简单的例子: utop#Parser_flow.program“let x;”;; -:Loc.t Ast.program*(Loc.t*Parse_error.t)列表= ({Loc.source=None;start={Loc.line=1;column=0;offset=0}; _end={Loc.line=1;column=6;offset=6}, [({Loc.source=None;st

我正在做一个使用流解析器的项目。我对OCaml有点陌生,所以所有的参数化类型都让我头晕目眩。举一个简单的例子:

utop#Parser_flow.program“let x;”;;
-:Loc.t Ast.program*(Loc.t*Parse_error.t)列表=
({Loc.source=None;start={Loc.line=1;column=0;offset=0};
_end={Loc.line=1;column=6;offset=6},
[({Loc.source=None;start={Loc.line=1;column=0;offset=0};
_end={Loc.line=1;column=6;offset=6},
Ast.Statement.VariableDeclaration
{Ast.Statement.VariableDeclaration.declarations=
[({Loc.source=None;start={Loc.line=1;column=4;offset=4};
_end={Loc.line=1;column=5;offset=5},
{Ast.Statement.VariableDeclaration.Declarator.id=
({Loc.source=None;start={Loc.line=1;column=4;offset=4};
_end={Loc.line=1;column=5;offset=5},
模式标识符
{Ast.Pattern.Identifier.name=
({Loc.source=None;
start={Loc.line=1;column=4;offset=4};
_end={Loc.line=1;column=5;offset=5},
“x”);
typeAnnotation=None;可选=false});
init=None}];
kind=Ast.Statement.VariableDeclaration.Let}],
[]),
[])
考虑到
Loc.t Ast.program
部分:因此
Ast.program
是由
Loc.t
参数化的,我想我会遵循这一点:

type nonrec'M program='M*'M Ast.Statement.t list*'M Ast.Comment.t list

现在只考虑语句列表,假设我想单步执行并为每个
variableddeclaration
打印一些内容,例如。我将如何匹配该构造?我可以做一些类似于:

(**x是Loc.t Ast.Statement.t list*的一个元素)
让我们匹配测试x=
将x与
|(Loc.tl,Ast.Statement.VariableDeclaration b)->打印字符串“声明!”
|打印字符串“无声明!”

谢谢你的指点/帮助

OCaml模式匹配与代数数据类型一起构成了一种强大的数据语言,可以轻松地对复杂数据结构进行编码和处理。这使得OCaml成为分析结构数据的好工具。令人惊讶的是,模式匹配在主流语言中并不常见,所以这个概念有时会让人困惑。让我试着用简单的例子来解释它,然后我们可以朝着更复杂的方向发展

为了构造数据,我们需要定义数据构造函数。数据构造函数表示创建给定类型值的不同可能方式。这就是为什么它们通常也被称为变体。比如说,

type image = 
 | Circle of int
 | Rectangle of int * int
 | Row of image * image
 | Column of image * image
 | Layer of image * image
半径为10的圆表示为
圆10
,我们可以使用
层(圆10,圆5)
将一个圆置于另一个圆之上,例如

# let eye = Layer (Circle 10, Circle 5);;
val eye : image = Layer (Circle 10, Circle 5)
我们现在有一个
image
类型的对象,名为
eye
。OCaml顶级很好地为我们打印它,作为人类,我们可以很容易地看到它的布局。但是作为程序员,我们需要分析它。我们需要解构图像,看看它是由什么部分构成的。要解构一个对象,我们使用与构建该对象相同的语法,只是我们将结构写在等号的左侧,解构的对象写在右侧,例如

let Layer (Circle 10, Circle 5) = eye
如果我们在OCaml顶级中键入它,它将同意我们的意见,但会发出警告,例如,如果
眼睛
对象不是两个圆的层,该怎么办?或者哪些圆有不同的半径?在这种情况下,我们无法解构眼睛对象,我们的程序将失败。因此,我们需要某种程度上能够考虑所有的可能性,首先,让我们学会如何与任何大小的眼睛匹配,例如

let Layer (Circle _, Circle _) = eye
这种图案可以与任何尺寸的眼睛搭配。我们使用了一个通配符模式,上面写着“匹配我不在乎的任何东西”。但是如果我们在乎,如果我们想要匹配任何大小,但仍然能够知道它们呢?这就是绑定匹配发挥作用的地方,我们可以在模式中使用变量而不是数据构造函数,当匹配发生时,变量将引用相应位置的对象,例如

 # let Layer (Circle outer, Circle inner) = eye
 val outer : int = 10
 val inner : int = 5
现在我们可以使用
外部
内部
变量,例如

 assert (outer > inner)
现在,我们不再只匹配一只具有给定配置的眼睛,而是能够匹配无限多只不同的眼睛,但仍然只匹配眼睛(即,两层圆圈)

终于到了引入匹配表达式的时候了

# let what_is_that = 
    match eye with
    | Layer (Circle _, Circle _) -> "an eye"
    | _ -> "I don't know
val what_is_that : string = "an eye"
现在OCaml终于高兴了,因为我们分析了所有可能的图像,所以没有发出警告。通常,匹配表达式的形式为

 match <exp> with
 | <pat1> -> <e1>
 | <pat2> -> <e2>
 ...
 | <patN> -> <eN>
这就是它的工作原理

# bounding_box eye;;
- : int * int = (20, 20)

# bounding_box (Row (eye,eye));;
- : int * int = (20, 40)
此外,什么是
(20,20)
?它被称为元组,是构造对、三元组等的内置方式。当然,我们可以定义自己的配对,比如

type pair = Pair of int * int
甚至使其成为多态数据结构,例如

type ('a,'b) = Pair of 'a * 'b
然后对三重、四重等重复同样的步骤。但是这种语言的设计者认为这太麻烦了,因为元组的算术很容易用逗号的数量来描述,所以我们可以在没有任何特定命名构造函数的情况下构造元组。否则,元组按照相同的规则运行,并使用相同的语法构造和解构,例如

let 4, 3 = 2*2, 4-1
括号是可选的,但经常使用,因为逗号运算符的优先级很低。因此,人们总是倾向于使用括号,例如

let (4, 3) = (2*2, 4-1)
虽然元组非常方便,但由于缺少名称,很容易忘记哪个元素表示什么。就像我甚至不确定上面的
边界框
函数是否正确一样,我也没有把宽度和高度混淆在一起。此外,它甚至不明显是什么
let (4, 3) = (2*2, 4-1)
type box = {height : int; width : int}