A Proof Assistant Based Formalisation of Core Erlang
aa r X i v : . [ c s . P L ] M a y A Proof Assistant BasedFormalisation of Core Erlang
Péter Bereczky − − − , Dániel Horpácsi − − − ,and Simon Thompson , − − − X ] Eötvös Loránd University, HU [email protected] and [email protected] University of Kent, UK
Abstract.
Our research is part of a wider project that aims to inves-tigate and reason about the correctness of scheme-based source codetransformations of Erlang programs. In order to formally reason aboutthe definition of a programming language and the software built usingit, we need a mathematically rigorous description of that language.In this paper, we present our proof-assistant-based formalisation of asubset of Erlang, intended to serve as a base for proving refactoringscorrect. After discussing how we reused concepts from related work, weshow the syntax and semantics of our formal description, including theabstractions involved (e.g. closures). We also present essential proper-ties of the formalisation (e.g. determinism) along with their machine-checked proofs. Finally, we prove the correctness of some simple refac-toring strategies.
Keywords:
Erlang formalisation · Formal semantics · Machine-checkedformalisation · Operational semantics · Term rewrite system · Coq
There are a number of language processors, development and refactoring tools formainstream languages, but most of these tools are not theoretically well-founded:they lack a mathematically precise description of what they do to the sourcecode. In particular, refactoring tools are expected to change programs withoutaffecting their behaviour, but in practice, this property is typically verified byregression testing only. Higher assurance can be achieved by making a formalargument (i.e. a proof) about this property, but neither programming languagesnor program transformations are easily formalised.When arguing about behaviour-preservation of program refactoring, we argueabout program semantics. To be able to do this in a precise manner, we needa formal, mathematical definition of the programming language semantics inquestion, which enables formal verification. Unfortunately, most programminglanguages lack fully formal definitions, which makes it challenging to deal withthem in formal ways. Since we are dedicated to improve trustworthiness of Erlang
P. Bereczky et al. refactorings via formal verification, we put effort in formalising Erlang and itsfunctional core, i.e. Core Erlang. Core Erlang is not merely a subset of Erlang;in fact, Erlang (along with other functional languages) translates to Core Erlangas part of the compilation process.This paper presents work on the Coq formalisation of a big-step semanticsfor Core Erlang. In general, if formal semantics is not available for a particularlanguage, one can take the language specification and the reference implementa-tion to build a formalisation thereon; in our case, we could rely not only on theseartifacts, but also on some previously published semantics definitions. Thus, wereviewed the existing papers on the Core Erlang language and its semantics,distilled, merged and extended them, to obtain a definition that can be prop-erly embedded in Coq. Using that we also proved some basic properties of thesemantics as well as proved simple program equivalences.The main contributions of this paper are:1. The definition of a formal semantics for a sequential subset of Erlang (CoreErlang), based partly on existing formalisations.2. An implementation for this semantics in the Coq proof assistant.3. Theorems that formalise a number of properties of this formalisation, e.g.determinism, with their machine-checked proofs.4. Results on program evaluation and equivalence verification using the seman-tics definition, all formalised in the Coq proof assistant.The rest of the paper is structured as follows. In Section 2 we review the existingformalisations of Core Erlang and Erlang, and compare them in order to helpunderstand the construction of our formal semantics. In Section 3 we describethe proposed formal description, including abstractions, syntax, and semantics,while in Section 4 a number of applications of the semantics are described.Section 5 discusses future work, then concludes.
Although there have already been a number of attempts to build a full-featuredformal definition of the Erlang programming language, the existing definitionsshow varying language coverage, and only some of them, covering mostly paral-lel parts of Core Erlang or Erlang, is implemented in a machine-checked proofsystem. This alone would provide a solid motivation for the work presented inthis paper, but our ultimate goal is to prove refactoring-related theorems in theCoq proof assistant, so our goal is formalise the semantics of Erlang in Coq in away that enables flawless verification of program equivalence.In order to reuse existing results, we have reviewed the extensive related workon formalisations of both Erlang [6, 7, 9, 15] and Core Erlang [5, 8, 10–14], andtried to incorporate ideas from all of these sources. We have decided to buildthe formalisation of Core Erlang as a stepping stone toward the definition of theentire Erlang language.
Proof Assistant Based Formalisation of Core Erlang 3
The vast majority of related work presents small-step operational semantics.In particular, one of our former project members has already defined [9] mostelements of sequential Erlang in the K specification language, which could beused both for interpretation and for verification. We wish we could reuse thisdefinition in Coq, but for proofs to be carried out in the proof assistant, it is toofine-grained, so we are seeking a big-step operational definition.As a matter of fact, most papers addressing the formal definition of Erlangfocus on the concurrent part (process management and communication primi-tives) of the language, which is not relevant to our current formalisation goals.Harrison’s formalisation of CoErl [8] concentrates on the way that communica-tion works, and in particular focusses on how mailboxes are processed. Althoughthe papers dealing with the sequential parts tend to present different approachesto defining the semantics, the elements of the language covered and the syntaxused to describe them appears to be very similar in each paper. However, thereare slight differences in the level of detail. Some definitions model the languagevery closely, whilst some do abstract away particular elements; for instance, un-like [13], [11] describes function applications only for function names.There is another notable difference in the existing formalisations from thesyntax point of view: some define values as a subset of expressions distinguishedby defining them in a different syntactic category [6, 7, 13, 15], and some definevalues as “ground patterns” [10–12, 14], i.e. subset of patterns. Both approacheshave their advantages and disadvantages, we will discuss this question in moredetail in Section 3.We principally used the work by Lanese et al. on defining reversible semanticsfor Erlang [10–12, 14], who define a language “basically equivalent to a subset ofCore Erlang” [14]. Although they do not take Core Erlang functions and theirclosures into consideration (except for top-level functions), which we needed todefine from scratch, their work proved to be a good starting point for defininga big-step operational semantics. In addition, we took the Core Erlang Doc-umentation [4] and the reference compiler for Core Erlang as reference pointsfor understanding the basic abstractions of the language in more detail. Whendefining function applications, we took some ideas from a paper embedding CoreErlang into Prolog [5], and when tackling match expressions, the big-step se-mantics for FMON [3] proved to be useful. Fredlund’s fundamental work [6] wasvery influential, but his Erlang formal semantics section discusses parallel partsmainly.There were some abstractions missing in almost all papers (e.g. the let binding with multiple variables, letrec , map expressions), for which we had torely on the informal definitions described in [4] and the reference implementation.Also, in most of the papers, the global environment is modified in every singlestep of the execution; in contrast, our semantics is less fine-grained as side-effectshave been not implemented yet. Unfortunately, the official language specificationdocument was written in 2004, and there were some new features (e.g. the mapdata type) introduced to Core Erlang since then. These features do not have an P. Bereczky et al. informal description either; however, we took the reference implementation andbuild the formalisation thereon.
After reviewing related work, we present our formal definition of Core Erlangformalised in Coq. Throughout this section, we will frequently quote the Coqdefinition; in some cases, we use the Coq syntax and quote literally, but in caseof the semantic rules, we turned the consecutive implications into inference rulenotation for better readability. The entire formalisation is available on Github [2].
This section gives a brief overview of the syntax in our formalisation.
Inductive
Literal : Type := | Atom ( s : string ) | Integer ( x : Z ) | EmptyList | EmptyTuple | EmptyMap . Fig. 1.
Syntax of literals
Inductive
Pattern : Type := | PVar ( v : Var ) | PLiteral ( l : Literal ) | PList ( hd tl : Pattern ) | PTuple ( t : list Tuple ) Fig. 2.
Syntax of patterns
The syntax of literals and patterns (Figures 1 and 2) is based on the papersmentioned in Section 2. The only addition is the map construction (
EmptyMap literal); float literals are left out, because in our applications, they can be handledas if they were integers. The tuple pattern is represented with Coq’s built-in list,which is constructed inductively.For the definition of the syntax of expressions, we need the following auxiliarytype:
Definition
FunctionIdentifier : Type := string × nat .With the help of this type alias and the previous definitions, we can describethe syntax of the expressions (Figure 3). As mentioned in Section 2, our expres-sion syntax is very similar to the existing definitions found in the related work.The main abstractions are based on [6,7,15] and the additional expressions (e.g. let , letrec , apply , call ) on [4, 10–14]. However, in our formalisation, we in-cluded the map type, primitive operations and function calls are handled alike,and in addition, the ELet and
ELetrec statements handle multiple bindings atthe same time.
Proof Assistant Based Formalisation of Core Erlang 5
Inductive
Expression : Type := | ELiteral ( l : Literal ) | EVar ( v : Var ) | EFunSig ( f : FunctionIdentifier ) | EFun ( vl : list Var ) ( e : Expression ) | EList ( hd tl : Expression ) | ETuple ( l : list Expression ) | ECall ( f : string ) ( l : list Expression ) | EApply ( exp : Expression ) ( l : list Expression ) | ECase ( e : Expression ) ( l : list Clause ) | ELet ( s : list Var ) ( el : list Expression ) ( e : Expression ) | ELetrec ( fnames : list FunctionIdentifier ) ( fs a : list (( list Var ) × Expression )) ( e : Expression ) | EMap ( kl vl : list Expression ) with Clause : Type := | CCons ( p : Pattern ) ( guard e : Expression ). a This is the list of the defined functions (list of variable lists and body expressions)
Fig. 3.
Syntax of expressions
Values
In Core Erlang, literals, lists, tuples, maps, and closures can be values,i.e. results of the evaluation of other expressions. As pointed out in Section 2,there are two approaches discussed in the related work: either values are relatedto patterns [10–12, 14] or values are related to expressions [6, 7, 13, 15]. We havedecided to relate values to expressions, because semantically values are derivedfrom expressions and not patterns. Moreover, there are three methods to definethe aforementioned relation of values and expressions: – Values are not a distinct syntactic category, so they are defined with anexplicit subset relation; – Values are syntactically distinct and are used in the definition of expres-sions [6, 7, 15]; – Values are syntactically distinct, but there is no explicit subset relation be-tween values and expressions [13].When values are not defined as a distinct syntactic set (or as a semanticdomain), a subset relation has to be defined that tells whether an expressionrepresents a value. In Coq, this subset relation is defined by a judgment on ex-pressions, but this would require a proof every time an expression is handled as avalue: the elements of a subset are defined by a pair, i.e. the expression itself anda proof that the expression is a value. While this is a feasible approach, it gen-erates lots of unnecessary trivial statements to prove in the dynamic semantics:instead of using a list of values, a list of expressions has to be used where proofsmust be given about the head and tail being values (see the example in Sec-tion 3.2 for more details about list evaluation). In addition, the main issue withthese approaches is that values do not always form a proper subset of patternsor expressions [4]: when lambda functions and function identifiers (signatures)
P. Bereczky et al. are considered, values must include closures, which, on the other hand, are notpresent in the syntax.For the reasons above, we define values separately from syntax, but un-like [13], we include function closures in the definition rather than functionsthemselves. In fact, we define values as a semantic domain, to which expressionsare evaluated (see Figure 4). This distinction of values allows the semantics tobe defined in a big-step way with domain changing (from expressions to values).Naturally, this approach causes duplication in the syntax definition (i.e. valuesyntax is not reused, unlike in [6,7,15]), but it saves a lot when proving theoremsabout values.
Inductive
Value : Type := | VLiteral ( l : Literal ) | VClosure a ( ref : Environment + FunctionIdentifier ) ( vl : list Var ) ( e : Expression ) | VList ( vhd vtl : Value ) | VTuple ( vl : list Value ) | VMap ( kl vl : list Value ). a A closure represents a function definition together with an environment representingthe context in which the function was defined: ref will be the environment or a ref-erence to it, vl will be the function parameter list and e will be the body expression. Environment is defined in Section 3.2 below.
Fig. 4.
Syntax of values
In the upcoming sections, we will use the following syntax shortcuts: tt := VLiteral ( Atom “true” ) ff := VLiteral ( Atom “false” ) We define a big-step operational semantics for the Core Erlang syntax describedin the previous section. In order to do so, we need to define environment types tobe included in the evaluation configuration. In particular, we define environments which hold values of variable symbols and function identifiers, and separately wedefine closure environments to store closure-local context.
Environment
The variable environment stores the bindings made with patternmatching in parameter passing as well as in let , letrec , case (and try ) state-ments. Note that the bindings may include both variable names and functionidentifiers, with the latter being associated with function expressions in nor-mal form (closures). In addition, there are top-level functions in the language,and they too are stored in this environment, similarly to those defined with the letrec statement. Proof Assistant Based Formalisation of Core Erlang 7
Top-level, global definitions could be stored in a separate environment in aseparate configuration cell, but we decided to handle all bindings in one environ-ment, because this separation would cause a lot of duplication in the semanticrules and in the actual Coq implementation. Therefore, there is one union typeto construct a single environment for function identifiers and variables, both lo-cal and global. It is worth mentioning that in our case the environment alwaysstores values since Core Erlang evaluation is strict, i.e. first expressions evaluateto some values, then variables can be bound to these values.We define the environment in the following way:
Definition
Environment : Type := list (( Var + FunctionIdentifier ) × Value ).We denote this mapping by Γ in what follows, whilst ∅ is used to denotethe empty environment. We also define a number of helper functions to manageenvironments, which will be used in formal proofs below. For the sake of simplic-ity, we omit the actual Coq definitions of these operations and rather provide ashort summary of their effect. – get value Γ key : Returns the value associated with key in Γ . – insert value Γ key value : Inserts the (key,value) pair into Γ . If this key hasalready been present, it will replace the original binding with the new one(according to [4], section 6). The next three function is implemented withthis replacing insertion. – add bindings bindings Γ : Appends to Γ the variable-value bindings given in bindings . – append vars to env varlist valuelist Γ : It is used for let statements andadds the bindings ( varlist elements to valuelist elements) to Γ . – append funs to env funsiglist param-bodylist Γ : Appends to Γ function sig-nature-closure pairs. The closures are constructed from param-bodylist whichcontains parameter lists and body expressions. Closure Environment
In Core Erlang, function expressions evaluate to clo-sures. Closures have to be modeled in the semantics carefully in order to capturethe bindings in the context of the closure properly. The following Core Erlangprogram shows an example where we need to explicitly store a binding contextto closures: l e t
X = 5 inl e t
Y = fun ( ) − > X inl e t X = 10 in a pply Y( )The semantics needs to make sure that we apply static binding here: thefunction Y has to return rather than . This requires the Y ’s context tobe stored along with its body, which is done by coupling them into a functionclosure . P. Bereczky et al.
When evaluating a function expression a closure is created. This is a copy ofthe current environment, an expression (the function body), and a variable list(the parameters of the function).This information could be encoded with the
VClosure constructor in the
Value inductive type using the actual environment (see Figure 4), however, thiscannot be used when the function is recursive. Here is an example:l e t r e c ’ f1 ’ / 0 = fun ( ) − > a pply ’ f1 ’ / 0 ( )In Core Erlang, letrec allows definition of recursive functions, so the body ofthe ’f1’/0 must be evaluated in an environment which stores ’f1’/0 mapped toa closure. But this closure contains the environment in which the body expressionmust be evaluated and that is the same environment mentioned before. So thethis is a recursion in embedded closures in the environment. Here is the problemvisualized:{’f1’/0 : VClosure {’f1’/0 :
VClosure {’f1’/0 : ...}} [] ( apply ’f1’/0() ) }We do not apply any syntactical changes to the function body, but we solvethis issue by introducing the concept of closure environments. The idea is thatthe name of the function (variable name or function identifier) is mapped to theapplication environment (this way, it can be used as a reference). It is enoughto encode the function’s name with the
VClosure constructor. This closure en-vironment can only be used together with the use of the environment and itemscannot be deleted from it.
Definition
Closures : Type := list ( FunctionIdentifier × Environment ).All in all, closures will ensure that the functions will be evaluated in the rightenvironments (a fully formal example is described in Section 4.2). There are twoways of using their evaluation environment ( ref attribute of
Environment +FunctionIdentifier type): – Either using the concrete environment from the closure value directly if ref is from the type
Environment ; – Or using the reference and the closure environment to get the evaluationenvironment when the type of ref is FunctionIdentifier .In the next sections, we denote this function-environment mapping with ∆ ,and ∅ denotes the empty closure environment. Similarly to ordinary environ-ments, closure environments are managed with a number of simple helper func-tions; like before, we omit the formal definition of these and provide an informa-tive summary instead. – get env key ∆ : Returns the environment associated with key in ∆ if key isa FunctionIdentifier . If key is an
Environment , the function simply returnsit. This function is implemented with the help of the next function. – get env from closure key ∆ : Returns the environment associated with key .If the key is not present in the ∆ , it returns ∅ . Proof Assistant Based Formalisation of Core Erlang 9 – set closure ∆ key Γ : Adds (key, Γ ) pair to ∆ . If key exists in ∆ , its valuewill be overwritten. Used in the next function. – append funs to closure fnames ∆ Γ : Inserts a ( f unid i , Γ ) binding into ∆ for every f unid i function identifier in fnames . Dynamic Semantics
The presented semantics, theorems, tests and proofs areavailable in Coq on the project’s Github repository [2].With the language syntax and the execution environment defined, we areready to define the big-step semantics for Core Erlang. The operational semanticsis denoted by | Γ, ∆, e | e −→ v ::= eval expr Γ ∆ e v where eval expr is the semantic relation in Figure 5. This means that e Ex-pression evaluates to v Value in the environment Γ and closure environment ∆ . Prior to presenting the rules of the operational semantics, we define a helperfor pointwise evaluation of multiple independent expressions: eval all states thata list of expressions evaluates to a list of values. eval all Γ ∆ exps vals := length exps = length vals = ⇒ ( ∀ exp : Expression , ∀ val : Value , In ( exp , val ) ( combine exps vals ) = ⇒| Γ, ∆, exp | e −→ val )With the help of this proposition, we will be able to define the semantics offunction calls, tuples, and expressions of other kinds in a more readable way. Inthis definition, we reuse length , combine , nth and In from Coq’s built-ins [1].There is another auxiliary definition which will simplify the main definition:( match clause ( v : Value ) ( cs : list Clause ) ( i : nat )) tries to match the i thpattern given in the list of clauses ( cs ) with the value v . The result is optional;if the i th clause does not match the value, it returns nothing or on successfulmatching it returns the guard and body expressions with the pattern variable-value bindings from the i th clause.The formal definition of the proposed operational semantics for Core Erlangis presented in Figure 5. We remind the reader that the figure presents the actualCoq definition, but the inductive cases are formatted as inference rules. We alsonote that this big-step definition is partly based on the small-step definitiondiscussed in [11, 12, 14] and in some aspects on the big-step semantics in [3, 5].In addition, for most of the language elements defined an informal definition isavailable in [4]. In the next paragraphs, we provide short explanations of the lesstrivial rules. – Rule 3.7: At first, the case expression e must be evaluated to some v value.Then this v must match to the pattern ( match clause function) of the spec-ified i th clause. This match provides the guard, the body expressions of the Inductive eval expr : Environment → Closures → Expression → Value → Prop := | Γ, ∆,
ELiteral l | e −→ VLiteral l (3.1) | Γ, ∆,
EVar s | e −→ get value Γ ( inl s ) (3.2) | Γ, ∆,
EFunSig fsig | e −→ get value Γ ( inr fsig ) (3.3) | Γ, ∆,
EFun vl e | e −→ VClosure ( inl Γ ) vl e (3.4) eval all Γ ∆ exps vals | Γ, ∆,
ETuple exps | e −→ VTuple vals (3.5) | Γ, ∆, hd | e −→ hdv | Γ, ∆, tl | e −→ tlv | Γ, ∆,
EList hd tl | e −→ VList hdv tlv (3.6)For the next rule we introduce no previous match i ∆ Γ cs v := ( ∀ j : nat , j < i = ⇒ ( ∀ ( gg , ee : Expression ) , ( bb : list ( Var × Value ) , match clause v cs j = Some ( gg , ee , bb ) = ⇒ ( | add bindings bb Γ, ∆, gg | e −→ ff ))) . match clause v cs i = Some ( guard , exp , bindings ) | add bindings bindings Γ, ∆, guard | e −→ tt | add bindings bindings Γ, ∆, exp | e −→ v’ | Γ, ∆, e | e −→ v no previous match i ∆ Γ cs v | Γ, ∆,
ECase e cs | e −→ v’ (3.7) eval all Γ ∆ params vals eval fname vals = v | Γ, ∆,
ECall fname params | e −→ v (3.8) eval all Γ ∆ params vals | Γ, ∆, exp | e −→ VClosure ref var list body | append vars to env var list vals ( get env ref ∆ ) , ∆, body | e −→ v | Γ, ∆,
EApply exp params | e −→ v (3.9) eval all Γ ∆ exps vals | append vars to env vars vals Γ, ∆, e | e −→ v | Γ, ∆,
ELet vars exps e | e −→ v (3.10)For the following rule we introduce Γ ′ ::= append funs to env fnames funs Γ length funs = length fnames | Γ ′ , append funs to closure fnames ∆ Γ ′ , e | e −→ v | Γ, ∆,
ELetrec fnames funs e | e −→ v (3.11) eval all Γ ∆ kl kvals eval all Γ ∆ vl vvals length kl = length vl | Γ, ∆,
EMap kl vl | e −→ VMap kvals vvals (3.12)
Fig. 5.
The big-step operational semantics of Core Erlang Proof Assistant Based Formalisation of Core Erlang 11 clause and also the pattern variable binding list. The guard must be evalu-ated to tt in the extended environment with the result of the pattern match-ing (the binding list mentioned before). The no previous match states, thatfor every clause before the i th one the pattern matching cannot succeed orthe guard expression evaluates in the extended environment to ff . Thereafterthe evaluation of the body expression can continue in this environment. – Rule 3.8: At first, the parameters must be evaluated to values. Then thesevalues are passed to the auxiliary eval function which simulates the be-haviour of inter-module function calls (e.g. the addition inter-module call isrepresented in Coq with the addition of numbers). This results in a valuewhich will be the result of the
ECall evaluation. – Rule 3.9: This rule works in similar way to the one described in [5] withthe addition of closures. To use this rule, first exp has to be evaluated to aclosure. Moreover, every parameter must be evaluated to a value. Finally, theclosure’s body expression evaluates to the result in an extended environmentwhich is constructed from the parameter variable-value bindings and theevaluation environment of the closure. This environment can be acquiredfrom the closure environment indirectly or it is present in the closure valueitself (Section 3.2). – Rule 3.10: At first, every expression given must be evaluated to a value. Thenthe body of the let expression must be evaluated in the original environmentextended with the variable-value bindings. – Rule 3.11: From the functions described (a list of variable list and bodyexpressions), closures will be created and appended to the environment andclosure environment associated with the given function identifiers ( fnames ).In these modified contexts the evaluation continues. – Rule 3.12: Introduces the evaluation for maps. This rule states that every keyin the map’s key list and value list must be evaluated to values resulting intwo lists of values (for the map keys and their associated values) from whichthe value map is constructed. In the future, this evaluation must be modified,because the normal form of maps cannot contain duplicate keys, moreover itis ordered based on these keys, according to the reference implementation.After discussing these rules, we show an example where the approach in whichvalues are defined as a subset of expressions is more difficult to work with. Letus consider a unary operator ( val ) on expressions which marks the values of theexpressions. With the help of this operator, the type of values can be defined:
Value ::= { e : Expression | e val } . Let us consider the key ways in which this would modify our semantics. – Environment → Closures → Expression → Expression → Prop would be thetype of eval expr . This way an additional proposition is needed which statesthat values are expressions in normal form, i.e. they cannot be used on theleft side of the rewriting rules. – The expressions which are in normal form could not be rewritten. – Function definitions have to be handled as values – Because of the strictness of Core Erlang, the derivation rules change, addi-tional checks are needed in the preconditions, e.g. in the rule 3.6: tlv valhdv val | Γ, ∆, hd | e −→ hdv ∨ hd = hdv | Γ, ∆, tl | e −→ tlv ∨ tl = tlv | Γ, ∆,
EList hd tl | e −→ VList hdv tlv
This approach has the same expressive power as the presented one, but ithas more preconditions to prove while using it. For reason, argue that our for-malisation is easier to use.
Proofs of properties of the semantics
We have also managed to formaliseand prove theorems about the attributes of the operations, auxiliary functionsand the semantics. Here we present two of these together with proof sketches.
Theorem 1 (Determinism). ∀ ( Γ : Environment ) , ( ∆ : Closures ) , (e : Expression), ( v : Value), | Γ, ∆, e | e −→ v = ⇒ ( ∀ v : Value, | Γ, ∆, e | e −→ v = ⇒ v = v ).Proof. Induction by the construction of the semantics. – – – – – The other cases are similar to those presented above. ⊓⊔ Theorem 2 (Commutativity). ∀ (v, v’ : Value),eval “plus” [ v ; v’ ] = eval “plus” [ v’ ; v ] . Proof.
First we separate cases based on the all possible construction of values(5 constructors, v and v’ values, that is 25 cases). In every case where either ofthe values is not an integer literal, the eval function results in the same errorvalue on both side of the equality.One case is remaining, when both v and v’ are integer literals. In this casethe definition of eval is the addition of these numbers, and the commutativityof this addition has already been proven in the Coq standard library [1]. ⊓⊔ Proof Assistant Based Formalisation of Core Erlang 13
In the previous section we have defined a big-step operational semantics for thesequential part of the Core Erlang language, which we also formalised in the Coqproof assistant.In this section we present some use cases. First, we elaborate on the verifica-tion of the semantics definition by testing it against the reference implementationof the language, then we show some examples on how we used the formalisationfor deriving program behaviour and for proving program equivalence.
Due to a lack of an up-to-date language specification, we validated the correct-ness of our semantics definition by comparing it to the behaviour of the codeemitted by the official compiler.To test our formal semantics, we used equivalence partitioning. We havewritten tests both in Coq (version 8.11.0) and in Core Erlang (OTP version22.0) for every type of expression defined in our formalisation, these were thefirst partitions. Moreover, there have also been special complex expressions thathave needed separate test cases (e.g. using bound variables in let expressions,application of recursive functions, etc.), with these we could divide the biggerpartitions into smaller ones.
Now let us demonstrate how Core Erlang programs are evaluated in the formalsemantics. For the sake of readability, we use concrete Core Erlang syntax in theproofs, and trivial statements are omitted from the proof tree.The first example shows how to evaluate a simple expression with binding: { X : 5 } ( X ) = 5 |{ X : 5 } , ∅ , X | e −→ | ∅ , ∅ , let X = 5 in X | e −→ The second example is intended to demonstrate the purpose of the closurevalues. Here at the application of 3.9 it is shown that the body of the applicationis evaluated in the environment given by the closure. { X : 42 } ( X ) = 42 |{ X : 42 } , ∅ , X | e −→ |{ X : 5 , Y : VClosure ( inl { X : 42 } ) [] X } , ∅ , apply Y () | e −→ |{ X : 42 , Y : VClosure ( inl { X : 42 } ) [] X } , ∅ , let X = 5 in apply Y () | e −→ |{ X : 42 } , ∅ , let Y = fun () → X in let X = 5 in apply Y () | e −→ | ∅ , ∅ , let X = 42 in let Y = fun () → X in let X = 5 in apply Y () | e −→ The third example cannot be evaluated in our formalisation, because of infi-nite recursion. For readability Γ := { ′ x ′ / VClosure ( inr ′ x ′ /
0) [] apply ′ x ′ / } (the environment after the binding is added) is introduced. This example alsopresents, that to evaluate recursive functions, the evaluation environment canbe gotten from the closure environment. ... | Γ, { ′ x ′ / Γ } , apply ′ x ′ / | e −→ ?? | Γ, { ′ x ′ / Γ } , apply ′ x ′ / | e −→ ?? | ∅ , ∅ , letrec ′ x ′ / fun () → apply ′ x ′ / in apply ′ x ′ / | e −→ ?? Last but not least, let us present some program equivalence proofs demonstratingthe usability of this semantics definition implemented in Coq. This is a significantresult of the paper since our ultimate goal with the formalisation is to proverefactorings correct.For the simplicity, we use + to refer to the append vars to env function and e + e will denote the ECall “plus” [ e , e ] expression in the following sections.First, we present a rather simple example of program equivalence. Example 1 (Swapping variable values). let
X = 5 in let
Y = 6 in X + Yis equivalent to let
X = 6 in let
Y = 5 in X + Y
Proof.
The formal description of the example looks like the following (usingabstract syntax for this one step): ∀ t : V alue, | ∅ , ∅ , ELet [ “X” ] [ ELiteral ( Integer
ELet [ “Y” ] [ ELiteral ( Integer
ECall “plus” [ EVar “X” ; EVar “Y” ])) | e −→ t ⇐⇒| ∅ , ∅ , ELet [ “X” ] [ ELiteral ( Integer
ELet [ “Y” ] [ ELiteral ( Integer
ECall “plus” [ EVar “X” ; EVar “Y” ])) | e −→ t Both directions of this equivalence are proven exactly the same way, so onlythe = ⇒ direction is presented here. This way, the hypothesis is the left side ofthe equivalence.First, this hypothesis should be decomposed. From the two let statements,it is known that the 5 and 6 expression literals can be evaluated only to theirvalue counterparts (because of the determinism and the rule 3.10). These ones Proof Assistant Based Formalisation of Core Erlang 15 will be associated with X and Y in the evaluation environment for the additionoperator (
ECall “plus” ). When this statement is evaluated (rule 3.8), then ityields the following hypothesis: t = eval “plus” [ VLiteral ( Integer
VLiteral ( Integer
Furthermore, our goal can be proven with the derivation tree presented below.In this tree the trivial parts of the proofs are not described for readability (theseare e.g. that the 5 and 6 expression literals evaluate to their value counterparts,the length of the expression or variable lists are the same as the evaluated valuelists, etc.). eval “plus” [6; 5] = t |{ X : 6 , Y : 5 } , ∅ , X + Y | e −→ t |{ X : 6 } , ∅ , let Y = 5 in X + Y | e −→ t | ∅ , ∅ , let X = 6 in let Y = 5 in X + Y | e −→ t The only remaining goal is to prove that eval “plus” [6; 5] = t . We havealready stated, that t = eval “plus” [5; 6] , so it is sufficient to prove: eval “plus” [6; 5] = eval “plus” [5; 6] The commutativity can be used here (Theorem 2), so we can swap the 5 and6 values in the parameter list. After this modification, we get reflexivity. ⊓⊔ With the same chain of thought, a more abstract refactoring also can beproved correct in our system.
Example 2 (Swapping variable expressions).
If we make the following assump-tions: | Γ, ∆, e | e −→ v | Γ + { X : v } , ∆, e | e −→ v | Γ, ∆, e | e −→ v | Γ + { X : v } , ∆, e | e −→ v then let X = e1 in let
Y = e2 in X + Yis equivalent to let
X = e2 in let
Y = e1 in X + Y
Proof.
In a similar way to the Example 1, we reason like this. ∀ Γ : Environment, ∆ : Closures, t : V alue, | Γ, ∆, e | e −→ v = ⇒ | Γ + { X : v } , ∆, e | e −→ v = ⇒| Γ, ∆, e | e −→ v = ⇒ | Γ + { X : v } , ∆, e | e −→ v = ⇒| Γ, ∆,
ELet [ “X” ] [ e ] ( ELet [ “Y” ] [ e ]( ECall “plus” [ EVar “X” ; EVar “Y” ])) | e −→ t ⇐⇒| Γ, ∆,
ELet [ “X” ] [ e ] ( ELet [ “Y” ] [ e ]( ECall “plus” [ EVar “X” ; EVar “Y” ])) | e −→ t The two directions of this equivalence are proved in exactly the same way,so only the forward ( = ⇒ ) direction is presented here.Now the main hypothesis has two let statements in itself. Similarly to theExample 1, these statements could only be evaluated with rule 3.10, i.e. thereare two values ( v and v because of the determinism and the assumptions) towhich e and e evaluates: | Γ, ∆, e | e −→ v and | Γ + { X : v } , ∆, e | e −→ v Moreover there appeared also a hypothesis: | Γ + { X : v , Y : v } , ∆, X + Y | e −→ t . This hypothesis implies that t = eval “plus” [ v , v ] because of the evaluationwith 3.8.Furthermore, the goal can be solved with the construction of a derivationtree. We denote Γ + { X : v , Y : v } with Γ v . | Γ, ∆, e | e −→ v | Γ v , ∆, X + Y | e −→ t | Γ + { X : v } , ∆, e | e −→ v | Γ + { X : v } , ∆, let Y = e in X + Y | e −→ t | Γ, ∆, let X = e in let Y = e in X + Y | e −→ t Now for the
ECall , the following derivation tree can be used. get value Γ v Y = v | Γ v , ∆, Y | e −→ v get value Γ v X = v | Γ v , ∆, X | e −→ v eval “plus” [ v , v ] = t | Γ v , ∆, X + Y | e −→ t As mentioned before, e and e evaluates to v and v in the initial environ-ment Γ and also in the extended environments (for e : Γ + { X : v } , for e : Γ + { X : v } ) too. So when the rule 3.10 applies, we can give a proof that e and e evaluates to v and v .After making this statement, we can use the rule 3.8 to evaluate the “plus” .The parameter variables will evaluate to v and v . With this knowledge, weget: eval “plus” [ v , v ] = t . As mentioned before t = eval “plus” [ v , v ] . So it issufficient to prove, that: Proof Assistant Based Formalisation of Core Erlang 17 eval “plus” [ v , v ] = eval “plus” [ v , v ] The commutativity of eval (Theorem 2) can be used to solve this equality. ⊓⊔ Example 3 (Swapping variables in simultaneous let). let
Proof.
The proof for this example is very similar to the proof for Example 2.The only difference is that one step is enough to evaluate the let expressions,and that is the reason why no assumptions are needed. ⊓⊔ Example 4 (Function evaluation). eis equivalent to let
X = fun() − > e in apply X() Proof.
In this case, both directions should be proved. At first, we formalise theproblem: ∀ Γ : Environment, ∆ : Closures, t : V alue, | Γ, ∆, e | e −→ t ⇐⇒| Γ, ∆,
ELet [ “X” ] [ EFun [] e ] ( EApply ( EV ar “X” ) [] | e −→ t ⇐ = direction:This can be proved by the construction of a derivation tree. We denote Γ + { X : VClosure ( inl Γ ) [] e } with Γ x and the value VClosure ( inl Γ ) [] e with cl in the tree. 3.4 | Γ, ∆, f un () → e | e −→ cl | Γ x , ∆, X | e −→ cl | Γ, ∆, e | e −→ t | Γ x , ∆, apply X () | e −→ t | Γ, ∆, let X = f un () → e in apply X () | e −→ t Only left to prove: | Γ, ∆, e | e −→ t , but we have the same hypothesis. = ⇒ direction: This can be proved by the deconstruction of the hypothesis for the let ex-pression. First only the 3.10 could be used for the evaluation. This means thatthe
EFun evaluates to some value, i.e. to the closure
VClosure ( inl Γ ) [] e . Weget a new hypothesis: | Γ + { X : VClosure ( inl Γ ) [] e } , ∆, apply X () | e −→ t (be-cause rule 3.4 and the determinism). Then the evaluation continued with therule 3.9. This means, that the X variable evaluates to some closure (this oneis in the environment, so only rule 3.2 could be applied) and the body of thisclosure evaluates to t in the environment from the closure extended with theparameter-value bindings (in this case there is none). This means in our case: | Γ, ∆, e | e −→ t which is exactly what we want to prove. ⊓⊔ To prove these examples in Coq, a significant number of lemmas were needed,such as the exposition of lists, the commutativity of the eval , and so forth.However, the proofs mostly consist of the combination of hypotheses similarto the proofs in this paper. Although sometimes additional case separationswere needed which resulted in lots of subgoals, these ones were solved verysimilarly, thus producing code duplication. In the future, these proofs shouldbecome simpler with the introduction of smart tactics and additional lemmas.Moreover, in the concrete implementation for Example 2 we could use an-other formulation of the four additional assumptions: if e does not contain thevariables X and Y, then it will evaluate to the same value in the environmentscombined from these variables. This statement also stands for e . We showed that our formal semantics is a powerful tool. We managed to formaliseand prove theorems, programs, program equivalence examples. This proves thatthe semantics is usable indeed. With this one we have a powerful tool to argueabout sequential Core Erlang programs. In the previous sections we also men-tioned some other approaches to formalise this semantics, and showed why ourway is more usable for our purpose.On the other hand, it also can be seen that this formalisation is not sim-ple to use either in practice, partly because the Coq Proof Assistant makes itsusers write down everything (trivialities too). Of course this is a necessity of thecorrectness, however, this property results in complex proofs. As a possibilityfor future work, it would be very useful to create smart tactics, to simplify outproofs and examples. In addition, this semantics is not complete yet, so it cannotbe used for any Core Erlang expression.
There are several ways to enhance our formalisation, we are going to focus mainlyon these short term goals:
Proof Assistant Based Formalisation of Core Erlang 19 – Extend semantics with additional expressions (e.g. try ); – Handle errors ( try statement); – Handle and log side effects; – Create new lemmas, theorems and tactics to shorten the Coq implementationof the proofs; – Formalise and prove more refactoring strategy.Our long term goals include: – Advance to Erlang (semantics and syntax); – Distinct primitive operations and inter-module calls; – Formalize the parallel semantics too.The final goal of our project is to change the core of a scheme-based refactoringsystem to a formally verified core.
In this study, we discussed why a language formalisation is needed, then brieflythe goal of our project (to prove refactoring correctness). To reach this objec-tive, Erlang was chosen as the prototype language, then several existing Erlangformalisations were compared. Based on these ones, a new natural semanticswas introduced for a subset of Erlang. This one was also formalised in CoqProof Assistant along with essential theorems, proofs (like determinism) and for-mal expression evaluation examples. We also showed proofs about the meaning-preservation of simple refactoring strategies with our formal semantics. In thefuture, we intend to extend this formalisation with additional Erlang statements,error handling and more equivalence examples.
Acknowledgements
The project has been supported by the European Union, co-financed by the Euro-pean Social Fund (EFOP-3.6.2-16-2017-00013, “Thematic Fundamental ResearchCollaborations Grounding Innovation in Informatics and Infocommunications(3IN)”).Project no. ED 18-1-2019-0030 (Application domain specific highly reliableIT solutions subprogramme) has been implemented with the support providedfrom the National Research, Development and Innovation Fund of Hungary,financed under the Thematic Excellence Programme funding scheme.
References
1. The Coq Proof Assistant Documentation. https://coq.inria.fr/documentation ,accessed: 2020.02.21.2. Core Erlang Formalization. https://github.com/harp-project/Core-Erlang-Formalization ,accessed: 2020.03.13.0 P. Bereczky et al.3. Carlier, M., Dubois, C., Gotlieb, A.: A first step in the design of a formally verifiedconstraint-based testing tool: FocalTest. In: International Conference on Tests andProofs. pp. 35–50. Springer (2012)4. Carlsson, R., Gustavsson, B., Johansson, E., Lindgren, T., Nyström, S.O., Petters-son, M., Virding, R.: Core Erlang 1.0 language specification (2004)5. De Angelis, E., Fioravanti, F., Palacios, A., Pettorossi, A., Proietti, M.: BoundedSymbolic Execution for Runtime Error Detection of Erlang Programs. arXivpreprint arXiv:1809.04770 (2018)6. Fredlund, L.Å.: A framework for reasoning about Erlang code. Ph.D. thesis,Mikroelektronik och informationsteknik (2001)7. Fredlund, L.Å., Gurov, D., Noll, T., Dam, M., Arts, T., Chugunov, G.: A veri-fication tool for Erlang. International Journal on Software Tools for TechnologyTransfer (4), 405–420 (2003)8. Harrison, J.R.: Towards an Isabelle/HOL Formalisation of Core Erlang. In: Pro-ceedings of the 16th ACM SIGPLAN International Workshop on Erlang. p. 55–63.Erlang 2017, ACM, New York, NY, USA (2017)9. Kőszegi, J.: KErl: Executable semantics for Erlang. CEUR Workshop Proceedings , 144–160 (2018)10. Lanese, I., Nishida, N., Palacios, A., Vidal, G.: CauDEr: a causal-consistent re-versible debugger for Erlang. In: International Symposium on Functional and LogicProgramming. pp. 247–263. Springer (2018)11. Lanese, I., Nishida, N., Palacios, A., Vidal, G.: A theory of reversibility for Erlang.Journal of Logical and Algebraic Methods in Programming , 71–97 (2018)12. Lanese, I., Sangiorgi, D., Zavattaro, G.: Playing with Bisimulation in Erlang. In:Models, Languages, and Tools for Concurrent and Distributed Programming, pp.71–91. Springer (2019)13. Neuhäußer, M., Noll, T.: Abstraction and model checking of Core Erlang pro-grams in Maude. Electronic Notes in Theoretical Computer Science176