愚蠢的快速参考
本页展示了Dafny中许多最常见的语言功能。为了让你更快地开始,这里的描述被简化了——本页不是语言参考。例如,这些页面不会进入模块、迭代器或精化,除非您用Dafny编写更大或更高级的程序,否则不需要这些。
程序
在顶层,Dafny程序(存储为扩展名为.dfy的文件)是一组声明。声明介绍类型, 方法,和功能,其中介绍顺序无关。这些用户定义的类型包括类和归纳数据类型。类本身还包含一组声明,引入字段、方法和函数。如果程序包含一个名为Main的无参数方法,则编译后的程序将从那里开始执行,但不需要使用Main方法进行验证。
注释以//开头并转到行尾,或者以/*开头并以*/结尾并可以嵌套。
领域
在类中,某种类型T的字段x声明为:
变量x:T
与局部变量和绑定变量不同,类型是必需的,不会被推断。通过在声明之前使用关键字ghost,可以将字段声明为ghost字段。Dafny的类型包括bool表示布尔值,int表示数学(即无界)整数,string表示字符串,用户定义的类和归纳数据类型,set对于T值的有限数学(不可变)集(其中T是类型),以及seq用于T值的数学(不可变)序列。此外,还有一个或多个维度的数组类型(类似于预定义的“类”类型),即写入数组,阵列2,阵列3, …. 类型对象是所有类类型的超类型,也就是说,对象表示任何引用,包括null。另一个有用的类型是nat,它表示int的子范围,即非负整数。
方法
方法声明(在顶层或类内部)的形式如下:
方法M(a:a,b:b,c:c)返回(x:x,y:y,z:y)需要PremodifiesFrameassuresPostdirectedisRank{Body}
其中a、b、c是方法的内参数,x、y、z是方法的外参数,Pre是表示方法的前提条件的布尔表达式,Frame表示其字段可以由方法更新的一组对象,Post是表示方法后条件的布尔表达,Rank是方法的变量函数,Body是实现该方法的语句。框架可以是表达式的列表,每个表达式都是一组对象或单个对象,后者代表由该对象组成的单个集。方法的框架是这些集合的并集,加上方法体分配的对象集。例如,如果c和d是c类的参数,那么
修改{c,d}
修改{c}+{d}
修改c,{d}
修改c,d
所有的意思都是一样的。
如果省略,前置和后置条件默认为true,框架默认为空集。变量函数是表达式列表,表示由给定表达式隐式后跟“top”元素组成的无限字典元组。如果省略,Dafny将猜测该方法的一个变量函数,即以该方法的内参数列表开头的字典元组。
通过在声明之前使用关键字ghost,可以将方法声明为ghost。默认情况下,类中的方法具有隐式接收器参数this。可以通过在方法声明之前使用关键字static删除此参数。类C中的静态方法M可以由C.M(…)调用。
在类中,可以通过将关键字方法替换为关键字构造函数来将方法声明为构造函数方法。构造函数只能在分配对象时调用(参见下面的对象创建示例),对于包含一个或多个构造函数的类,对象创建必须与对构造函数的调用一起完成。当然,通常情况下,一个方法有一个名称,但一个类可以有一个没有名称的构造函数——匿名构造函数–就像这样:
构造函数(n:int)修改这个{Body}
通常,ghost方法被用作引理。通过使用引理而不是ghost方法声明方法,可以在程序文本中更清楚地说明这一点。
下面是一个示例方法,它采用3个整数作为内参数,并按排序顺序在3个外参数中返回这些整数:
Sort方法(a:intb:int,c:int)返回(x:int,y:int,z:int)确保x<=y<=z&&multiset{a,b,c}==multisset{x,y,z}{x,y,z:=a,b;c;如果z<y{y,z:=z,y;}如果y<x{x,y:=y,x;}
功能
函数声明(在顶层或类内部)的形式如下:
函数F(a:a,b:b,c:c):查询预读取帧确保后降低排名{正文}
其中a、b、c是方法的参数,T是函数结果的类型,Pre是表示函数前提的布尔表达式,Frame表示函数体可能依赖其字段的一组对象,Post是表示函数后条件的布尔表达式;Rank是函数的变量函数,Body是定义函数的表达式。前提条件允许函数是部分的,也就是说,前提条件说明了函数的定义时间(Dafny将验证函数的每次使用都符合前提条件)。通常不需要后置条件,因为函数体给出了完整的定义。然而,后置条件可以是一个方便的地方来声明可能需要归纳证明才能建立的函数的属性。例如:
函数阶乘(n:int):函数间0≤n≥1≤阶乘(n){如果n==0,则1否则阶乘(n-1)*n}
表示阶乘的结果总是正数,Dafny从函数体中归纳验证了这一点。要在后置条件中引用函数的结果,请使用函数本身,如示例所示。
默认情况下,函数是ghost,不能从非ghost代码调用。要使其非重影,请使用两个关键字函数方法替换关键字函数。返回布尔值的函数可以用关键字谓词声明(然后省略冒号和返回类型)。
默认情况下,类中的函数具有隐式接收器参数this。可以通过在函数声明之前使用关键字static删除此参数。类C中的静态函数F可以由F.M(…)调用。这可以为在单独的类中声明多个helper函数提供一种方便的方法。
课程
类的定义如下:
类C{//此处显示成员声明}
其中类的成员(字段、方法和函数)是在大括号内定义的(如上所述)。
数据类型
归纳数据类型是一种类型,其值是使用一组固定的构造函数创建的。带有构造函数Leaf和Node的数据类型Tree声明如下:
数据类型Tree=叶|节点(Tree,int,Tree)
施工人员之间用竖条隔开。无参数构造函数不需要使用括号,如这里所示的Leaf。
对于每个构造函数Ct,数据类型隐式声明一个布尔成员Ct?,对于使用Ct构造的那些值,返回true。例如,在代码段之后:
var t0:=叶;var t1:=节点(t0,5,t0);
表达式t1.Node?计算为true和t0。节点?计算结果为false。如果使用相同的构造函数和该构造函数的相同参数创建了两个数据类型值,则这两个值相等。因此,对于像Leaf这样的无参数构造函数,t.Leaf?给出了与t==Leaf相同的结果。
构造函数可以有选择地为其任何参数声明析构函数,这是通过为参数引入名称来实现的。例如,如果Tree声明为:
数据类型树=叶|节点(左:树,数据:int,右:树)
然后t1.data==5和t1.left==t0保持在上面的代码片段之后。
泛型
Dafny支持类型参数。也就是说,任何类、归纳数据类型、方法和函数都可以有类型参数。这些在声明内容名称后的尖括号中声明。例如:
类MyMultiset{/*…*/}数据类型树=叶|节点(树,T,树)方法Find(键:T,集合:树){/*…*/}函数IfThenElse(b:bool,x:T,y:T):T{/*…*/}
声明
以下是Dafny中最常见的语句示例。
var局部变量:=ExprList;L值:=ExprList;断言BoolExpr;打印ExprList;
如果BoolExpr0{Stmts0},则为else如果BoolExpr1{Stmts1},否则为{Stmts2}
BoolExprinvariant Inv修改帧时降低秩{Stmts}
match Expr{case Empty=>Stmts0case节点(l,d,r)=>Stmts1}
断裂;回报;
var语句引入了局部变量(不允许隐藏在同一组最紧密的花括号内声明的其他变量)。对于任何类型的T,每个变量都可以有选择地后跟:T,这将显式地为前面的变量提供类型T(而不是被推断)。具有初始值的ExprList是可选的。要将变量声明为ghost变量,请在声明之前使用关键字ghost。
赋值语句将ExprList中的每个右侧赋值给Lvalues中相应的左侧。这些赋值是并行执行的(更确切地说,所有必要的读取都发生在写入之前),因此左侧必须表示不同的L值。每个右侧可以是以下形式之一的表达式或对象创建:
new Tnew T.Init(ExprList)new T(Expr列表)new T[SizeExpr]new T[大小Expr0,大小Expr1]
第一种形式分配类型为T的对象。第二种形式还对新分配的对象调用初始化方法或构造函数。第三种形式显示了当调用的构造函数是匿名的时,语法与第二种形式的区别。其他形式显示了数组分配的示例,特别是T值的一维数组和二维数组。
赋值的整个右侧也可以是方法调用,在这种情况下,左侧是实际的输出参数(如果没有输出参数,则省略:=)。
assert语句声明给定表达式的计算结果为true(由验证器检查)。
print语句将给定打印表达式的值输出到标准输出。字符串中的字符可以转义;例如,print语句感兴趣的是,n表示字符串中的换行符。
if语句是常见的语句。该示例显示了使用else-if将备选方案串在一起。else分支通常是可选的。
while语句是常见的循环,其中不变量声明提供循环不变量,modifies子句限制循环的修改框架,decreases子句为循环引入变量函数。默认情况下,循环不变量为true,修改框架与封闭上下文中的相同(通常是封闭方法的modifies子句),变量函数是从循环保护中猜测出来的。
match语句计算源Expr(一个其类型为归纳数据类型的表达式),然后执行与使用哪个构造函数创建源数据类型值相对应的情况,将构造函数参数绑定到给定的名称。如果不需要它们来标记match语句的结尾,那么可以省略围绕大小写的花括号。
break语句可用于退出循环,return语句可用于结束方法。
表达式
Dafny中的表达式与类Java语言中的表达式非常相似。这里有一些值得注意的差异。
除了短路布尔运算符&&(and)和||(or)之外,Dafny还有一个短路隐含运算符==>和一个if-and-only-if运算符<==>。从宽度来看,<==>的结合力低于==>,而==>又低于&&和||。
Dafny比较表达式可以是链式的,这意味着“同一方向”的比较可以串在一起。例如,
0<=i<j<=a.长度==N
含义与:
0<=i&&i<j&&j<=a.长度&&a.长度==N
注意,布尔等式可以使用==和<==>来表示。这两者之间有两个区别。首先,==比<==>具有更高的约束力。第二,==是连锁而<==>是相联的也就是说,a==b==c与a==b&&b==c相同,而a<===>b<===>c与a<===>(b<===>c)相同,后者也与(a<===>b)<===>c相同。
对整数的运算是常见的,除了/(整数除法)和%(整数模)遵循欧几里德定义,这意味着%总是产生非负数。(因此,当/或%的第一个参数为负数时,结果与在C、Java或C#中得到的结果不同,请参见http://en.wikipedia.org/wiki/Moduleo_operation网站(在新选项卡中打开).)
Dafny表达式包括通用量词和存在量词,其形式如下:
对于所有x::Expr
与exists类似,其中x是绑定变量(可以用显式类型声明,如x:T),Expr是布尔表达式。
集合上的运算包括+(并集)、*(交集)和–(集差),以及集合比较运算符<(适当子集)、<=(子集)、它们的对偶>和>=、和!!(脱节)。S中的表达式x表示x是集合S的一个成员,并且x!S是一种方便的书写方式!(x英寸S)。要从某些元素组成一个集合,请将它们括在花括号中。例如,{x,y}是由x和y组成的集(如果x==y,则为单例集),{x}是包含x的单例集,而{}是空集。
序列上的操作包括+(串联)和比较运算符<(适当的前缀)和<=(前缀)。可以像检查集合那样检查成员身份:S中的x和x!在S中,序列S的长度用|S|表示,这样的序列的元素具有从0到小于|S|的索引。表达式S[j]表示序列S的索引j处的元素。表达式S[m.n]返回一个序列,其中0<=m<=n<=|S|的元素是从索引m开始的S的n-m元素(即从S[m]、S[m+1]……到但不包括S[n])。表达式S[m..](通常称为“drop m”)与S[m.|S|]相同,也就是说,它返回的序列的元素除了S的前m个元素外,其余都是S的元素。表达式S[..n](通常称作“take n”)与S[0..n]相同,即它返回由S的前n个元素组成的序列。如果j是序列S的有效索引,那么表达式S[j:=x]是类似于S的序列,只是它在索引j处有x。最后,要从一些元素构成序列,请将它们括在方括号中。例如,[x,y]是由两个元素x和y组成的序列,其中[x,y][0]==x和[x,y][1]==y,[x]是唯一元素为x的单元素序列,[]是空序列。
if-then-else表达式的形式为:
如果是BoolExpr,则是Expr0 else Else1
其中Expr0和Expr1是相同类型的任何表达式。与if语句不同,if-then-else表达式使用then关键字,并且必须包含显式else分支。
match表达式类似于match语句,其形式如下:
match Expr{case Empty=>Expr0case节点(l,d,r)=>Expr 1}
大括号可以用来标记匹配表达式的结尾,但最常见的情况是不需要这样做,然后可以省略大括号。
在新选项卡中打开