Tutorial on implementing Hoare logic for imperative programs in Haskell
aa r X i v : . [ c s . P L ] F e b T UTOR IAL ON IMPLEMENTING H OAR E LOGIC FOR IMPER ATIVEPROGR AMS IN H ASKELL
A P
REPRINT
Boro Sitnikovski
Skopje, North Macedonia [email protected]
February 18, 2021 A BSTRACT
Using the programming language Haskell, we introduce an implementation for a simple imperativelanguage that can evaluate arithmetic and boolean expressions; step by step, we will expand theimplementation starting from arithmetic expressions, to boolean expressions and then to imperativeprograms. As we expand the language, we will show several evaluation strategies, considering thenormalization property and how it affects an implementation. Finally, we will provide a bottom-upimplementation of Hoare’s logic which will allow us to deduce facts about programs without theneed for a full evaluation. K eywords Imperative languages · Functional languages · Hoare logic · Formal verification · Haskell
Nowadays, imperative programming languages run the world, to list a few popular ones: C, Python, JavaScript,PHP. Code is usually written in these languages by using an imperative style; that is, the computer is being told(”commanded”) what to do and how to do it specifically. The mathematical language is very unlike this, it is moredeclarative rather than imperative, that is, it doesn’t care about the how.Since a programmer has to specify the exact ”how”, it is easy to make a programming error. In addition, given thepopularity of these programming languages, there are many bugs in software applications that are programmed inthem. This motivates for a way to formally verify certain properties about programs written in these languages. Hoarelogic is one way to mechanically reason about computer programs.We will provide an implementation of an imperative language together with a toy implementation of Hoare logic thatwill allow us to reason about programs in this language. Our implementation of Hoare’s logic will be bottom-up, in thesense that we will build the proofs from the ground up, in contrast to e.g. programming languages such as Dafny[1],that build proofs from top to bottom. That is, in Dafny, the user provides a proposition and Dafny will derive the proof,automatically, using the automated theorem prover Z3[2].The programming language that we will implement already has an implementation in Coq[3]. However, implementinga language in Haskell is more concerned about playing at the value level (and to some extent at the type level), whereasin a dependently-typed language the focus is at the type level. Specifically for Hoare’s logic, in Haskell we cannot useany of the meta language’s constructs to do mathematical proofs, so we have to take care of these algorithms ourselves.Haskell is not a strongly normalizing language, which means that not every evaluation necessarily terminates. How-ever, in dependently-typed languages, such as Coq[4], the evaluation of proof terms (e.g. the type checker) is stronglynormalizing, and this is what allows us to express mathematical proofs.There are good introductory books on dependent types[3]. In some books, there is a gentler approach which might behandy for newcomers[5]. There are good introductions to Haskell as well[6].utorial on implementing Hoare logic for imperative programs in Haskell
A P
REPRINT
We provide the syntax, evaluation rules, and the implementation in Haskell of a simple language that is stronglynormalizing.
We start by introducing a language that can do arithmetic. The syntax of the language expressed in BNF (Backus-NaurForm) is as follows: digit ::= "0" | "1" | ... | "8" | "9"aexp ::= aterm | aterm relop atermnumber ::= - digit* | digit*relop ::= "+" | "-" | "*"aterm ::= aexp | number | varvar ::= A | B | C ... | Y | Z
Followed by a direct translation to Haskell code: data Aexp =ANum Integer| AId Char| APlus Aexp Aexp| AMinus Aexp Aexp| AMult Aexp Aexp
We show the evaluation rules for optimizing an expression. Here, ”optimization” is just another evaluation strategy.(A-Opt-Plus)
APlus $ a $ a ֒ → $( a + a ) (A-Opt-Minus) AMinus $ a $ a ֒ → $( a − a ) (A-Opt-Mult) AMult $ a $ a ֒ → $( a · a ) That is, we lift the values from our language to the meta language (Haskell), where the symbol $ represents the valueconstructor ANum , and the symbol ֒ → represents the optimization function.The same rules represented in Haskell: aoptimize :: Aexp -> Aexpaoptimize (APlus (ANum a1) (ANum a2)) = ANum (a1 + a2)aoptimize (AMinus (ANum a1) (ANum a2)) = ANum (a1 - a2)aoptimize (AMult (ANum a1) (ANum a2)) = ANum (a1 * a2)aoptimize x = x Next, we introduce a language that can handle boolean expressions. The syntax of the language expressed in BNF is: bexp ::= bterm | bterm brelop bterm | aterm arelop aterm | unop btermarelop ::= "==" | "<"brelop ::= "&&"bterm ::= "T" | "F"unop ::= "!"
The same syntax is represented with Haskell code: data Bexp =BTrue| BFalse| BEq Aexp Aexp| BLe Aexp Aexp
A P
REPRINT | BNot Bexp| BAnd Bexp Bexp
Similarly to aoptimize , we list the evaluation rules for optimizing a boolean expression: a = a (B-Opt-EqNumTrue) BEq $ a $ a ֒ → BTrue a = a (B-Opt-EqNumFalse) BEq $ a $ a ֒ → BFalse v = v (B-Opt-EqIdTrue) BEq v v ֒ → BTrue v = v (B-Opt-EqIdFalse) BEq v v ֒ → BEq v v (B-Opt-NegTrue) BNot BTrue ֒ → BFalse (B-Opt-NegFalse)
BNot BFalse ֒ → BTrue (B-Opt-AndFalse)
BAnd BFalse x ֒ → BFalse (B-Opt-AndTrue)
BAnd BTrue x ֒ → x Where the symbol represents a variable (the value constructor
AId ).These rules translate to the following implementation in Haskell: boptimize :: Bexp -> Bexpboptimize (BEq (ANum a1) (ANum a2)) = if a1 == a2 then BTrue else BFalseboptimize (BEq (AId v1) (AId v2)) = if v1 == v2 then BTrue else BEq (AId v1) (AId v2)boptimize (BNot BTrue) = BFalseboptimize (BNot BFalse) = BTrueboptimize (BAnd BFalse _) = BFalseboptimize (BAnd BTrue b2) = b2boptimize x = x
The optimization functions aoptimize and boptimize represent a simple single-step evaluation. Next, we introducea way to do a full evaluation of arithmetic and boolean expressions.To support variables, we introduce the notion of a context; one implementation in Haskell is simply a mapping fromcharacters to numbers. type Context = M.Map Char Integer
We list the evaluation rules for arithmetic expressions: ( v, $ v ′ ) ∈ ctx (A-Eval-Id) v → ctx $ v ′ (A-Eval-Num) $ n → ctx n a → ctx a ′ , a → ctx a ′ (A-Eval-Plus) APlus a a → ctx a ′ + a ′ a → ctx a ′ , a → ctx a ′ (A-Eval-Minus) AMinus a a → ctx a ′ − a ′ a → ctx a ′ , a → ctx a ′ (A-Eval-Mult) AMult a a → ctx a ′ · a ′ The arrow symbol → ctx in the rules represents the actual evaluation of arithmetic expressions under context ctx .Follows the implementation of these rules in Haskell: aeval :: Context -> Aexp -> Integeraeval ctx (AId v) = ctx M.! v -- element may not existaeval ctx (ANum n) = naeval ctx (APlus a1 a2) = aeval ctx a1 + aeval ctx a2aeval ctx (AMinus a1 a2) = aeval ctx a1 - aeval ctx a2aeval ctx (AMult a1 a2) = aeval ctx a1 * aeval ctx a2 Finally, the evaluation rules for boolean expressions: 3utorial on implementing Hoare logic for imperative programs in Haskell
A P
REPRINT (B-Eval-True)
BTrue ⇒ ctx True (B-Eval-False)
BFalse ⇒ ctx False a → ctx a ′ , a → ctx a ′ (B-Eval-Eq) BEq a a ⇒ ctx a ′ = a ′ a → ctx a ′ , a → ctx a ′ (B-Eval-Le) BLe a a ⇒ ctx a ′ ≤ a ′ b ⇒ ctx b ′ (B-Eval-Not) BNot b ′ ⇒ ctx ¬ b ′ b ⇒ ctx b ′ , b ⇒ ctx b ′ (B-Eval-And) BAnd b b ⇒ ctx b ′ ∧ b ′ Similarly, the double right arrow symbol ⇒ ctx in the rules represents the actual evaluation of boolean expressions undercontext ctx .We provide the implementation of these rules in Haskell: beval :: Context -> Bexp -> Boolbeval ctx BTrue = Truebeval ctx BFalse = Falsebeval ctx (BEq a1 a2) = aeval ctx a1 == aeval ctx a2beval ctx (BLe a1 a2) = aeval ctx a1 <= aeval ctx a2beval ctx (BNot b1) = not (beval ctx b1)beval ctx (BAnd b1 b2) = beval ctx b1 && beval ctx b2 For example, the single-step optimization functions can be used as following: > let e = BNot BTrue in "Optimize: " ++ show e ++ " = " ++ show (boptimize e)Optimize: ! (TRUE) = FALSE> let e = APlus (ANum 2) (ANum 5) in "Optimize: " ++ show e ++ " = " ++ show (aoptimize e)Optimize: 2 + 5 = 7
However, since the single-step evaluation strategy does not rely on any context, it will not be able to deduce muchabout expressions that involve variables, e.g.
X + 5 , while aeval can substitute for this variable, given a context: > let e = APlus (AId ’X’) (ANum 5) in "Optimize: " ++ show e ++ " = " ++ show (aoptimize e)Optimize: X + 5 = X + 5> let e = APlus (AId ’X’) (ANum 5) in show e ++ " = " ++ show (aeval (M.fromList [(’X’, 5)]) e)X + 5 = 10> let e = BEq (AId ’X’) (ANum 5) in show e ++ " = " ++ show (beval (M.fromList [(’X’, 5)]) e)X == 5 = True
We proceed with providing the syntax, evaluation rules, and the implementation in Haskell of an imperative language.
We show the data type of the imperative language expressed in Haskell: data Command =CSkip| CAssign Char Aexp| CSequence Command Command| CIfElse Bexp Command Command| CWhile Bexp Command| CAssert Bexp Command Bexp
That is, the language contains the minimum set of commands which make an imperative language:•
CSkip is the the no operation command - the empty statement.4utorial on implementing Hoare logic for imperative programs in Haskell
A P
REPRINT • CAssign will assign a value to a variable in a context.•
CSequence will join two commands, which allows for the evaluation of commands in sequence.•
CIfElse accepts a boolean and depending on its value either executes one command, or another.•
CWhile accepts a boolean and keeps executing a command as long as the boolean is true.•
CAssert accepts a precondition, a command, and a postcondition. The evaluation will be successful if theprecondition and the postcondition are satisfied before and after executing the command, respectively.We show the evaluation rules for this language: (C-Eval-Skip)
CSkip ctx ctxv → ctx v ′ (C-Eval-Assign) CAssign c v ctx ctx ∪ ( c, v ′ ) c ctx ctx ′ , c ctx ′ ctx ′′ (C-Eval-Sequence) CSequence c c ctx ctx ′′ b ⇒ ctx True , c ctx ctx ′ (C-Eval-IfTrue) CIfElse b c c ctx ctx ′ b ⇒ ctx False , c ctx ctx ′ (C-Eval-IfFalse) CIfElse b c c ctx ctx ′ b ⇒ ctx True , c ctx ctx ′ , CWhile b c ctx ′ ctx ′′ (C-Eval-WhileTrue) CWhile b c ctx ctx ′′ b ⇒ ctx False (C-Eval-WhileFalse)
CWhile b c ctx ctxb ⇒ ctx True , c ctx ctx ′ , b ⇒ ctx ′ True (C-Eval-Assert)
CAssert b c b The map arrow symbol ctx in the rules represents the actual evaluation of a command under context ctx .In Coq, each implementation of the rules would be represented at the type level. However, since we’re working at thevalue level in Haskell, we will rely on the Either data type to distinguish between provable and not provable terms. eval :: Context -> Command -> Either String Contexteval ctx CSkip = Right ctxeval ctx (CAssign c v) = Right $ M.insert c (aeval ctx v) ctxeval ctx (CSequence c1 c2) = let ctx’ = eval ctx c1 in whenRight ctx’ (\ctx’’ -> eval ctx’’ c2)eval ctx (CIfElse b c1 c2) = eval ctx $ if beval ctx b then c1 else c2eval ctx (CWhile b c) =if beval ctx bthen let ctx’ = eval ctx c in whenRight ctx’ (\ctx’’ -> eval ctx’’ (CWhile b c))else Right ctxeval ctx (CAssert b1 c b2) =if beval ctx b1then whenRight (eval ctx c)(\ctx’ -> if beval ctx’ b2then Right ctx’else Left "Post-condition does not match!")else Left "Pre-condition does not match!" Note that this language is not strongly normalizing; consider the evaluation of
CWhile BTrue CSkip .As an example, the factorial program (sequence of commands) can be represented with the pseudo-code:
Z := XY := 1while (~Z = 0)Y := Y * ZZ := Z - 1
That is, this program will calculate
Y := X! . Here’s an implementation of it in our language:5utorial on implementing Hoare logic for imperative programs in Haskell
A P
REPRINT > :{| fact_X =| let l1 = CAssign ’Z’ (AId ’X’)| l2 = CAssign ’Y’ (ANum 1)| l3 = CWhile (BNot (BEq (AId ’Z’) (ANum 0))) (CSequence l4 l5)| l4 = CAssign ’Y’ (AMult (AId ’Y’) (AId ’Z’))| l5 = CAssign ’Z’ (AMinus (AId ’Z’) (ANum 1))| in CSequence l1 (CSequence l2 l3)| :}> eval (M.fromList [(’X’, 5)]) fact_XRight (fromList [(’X’,5),(’Y’,120),(’Z’,0)])> let e = CAssert (BEq (ANum 5) (AId ’X’)) factX (BEq (ANum 120) (AId ’Y’)) in "Assert {X=5} factX{Y=120}: " ++ show (eval (M.fromList [(’X’, 5)]) e)Assert {X=5} factX {Y=120}: Right (fromList [(’X’,5),(’Y’,120),(’Z’,0)])> let e = CAssert (BEq (ANum 4) (AId ’X’)) factX (BEq (ANum 120) (AId ’Y’)) in "Assert {X=4} factX{Y=120}: " ++ show (eval (M.fromList [(’X’, 5)]) e)Assert {X=4} factX {Y=120}: Left "Pre-condition does not match!"
In the previous chapter, we implemented assertions (
CAssert ) at the run-time ( eval ) level. The biggest disadvantageof that is we have to do a full evaluation to deduce some facts about programs; considering the assertion example ofthe factX program, it has to actually evaluate the factorial to conclude something. This motivates the need for anadditional evaluation strategy that will allow us to deduce facts about programs without doing a full evaluation.Some programming languages, like Python, don’t have a compile step and the eval function we provided is kind ofequivalent to evaluating programs in Python. But some programming languages do have a compile step, like C orHaskell, and this compilation step can be beneficial in that it can do additional different checks, e.g., type checks.That’s what we’ll do here - implement a ”compile”-time check (just another evaluation strategy) using some of therules in Hoare’s logic, and this check can be used to check the validity of a program, before fully evaluating it.We list the rules of Hoare logic, some of which are outlined in the original paper[7]. In this paper, we only provide animplementation for some of them.(H-Skip) { P } skip { P } (H-Assign) { P [ E/x ] } x := E { P } { P } S { Q } , { Q } T { R } (H-Sequence) { P } S ; T { R }{ B ∧ P } S { Q } , {¬ B ∧ P } T { Q } (H-Conditional) { P } if B then S else T { Q } { B ∧ P } S { P } (H-While) { P } while B do S {¬ B ∧ P } P → P , { P } S { Q } , Q → Q (H-Consequence) { P } S { Q } We represent the Hoare triple as a product of a command, a precondition (
Bexp ) and a postcondition (
Bexp ). In thiscase, the design decision is that pre/postconditions will be in the language of
Bexp , but they can also be in a differentlanguage (regardless of that
Bexp is used in
Command ). data HoareTriple = HoareTriple Bexp Command Bexp Triples should not be constructed with
HoareTriple , rather through the functions/rules that we provide next.
The Haskell implementation of the Hoare skip rule can be represented as follows: hoareSkip :: Bexp -> HoareTriplehoareSkip q = HoareTriple (boptimize q) CSkip (boptimize q)
A P
REPRINT
We rely on the optimizing functions to further simplify calculations. Note that the validity of the precondition is notbeing checked (since that would require evaluating
Aexp / Bexp with a context); the proof merely states that assumingsome precondition, a command produces some postcondition. > hoareSkip (BEq (ANum 3) (ANum 3)){TRUE} ; {TRUE}
Let
Q[E/V] denote the expression Q in which each free occurrence of V is replaced with E . Given an assignmentcommand V := E , it should produce the triple where the precondition is
Q[E/V] and the postcondition is Q , for any Q . hoareAssignment :: Char -> Aexp -> Bexp -> HoareTriplehoareAssignment v e q =HoareTriple(boptimize (substBexp (boptimize q) (aoptimize e) v))(CAssign v e)(boptimize q) There are several ways how the function substBexp can be implemented. We list a few:• One way is to do a full
Aexp / Bexp evaluation; we can do this, since these languages are strongly normalizing,compared to our imperative language. However, this evaluation can still take some time, and it requires acontext in addition.• Another way is to specify some concrete set of mathematical rewrites that can be applied, based on theoriginal languages (
Aexp / Bexp ).In this paper, the substitution will contain a concrete set of mathematical rewrites, though the reader is encouraged totry different implementations and spot the advantages and disadvantages of each. substAexp :: Aexp -> Aexp -> Char -> AexpsubstAexp (AId x) e v = if x == v then e else AId xsubstAexp (APlus x y) e v = APlus (substAexp x e v) (substAexp y e v)substAexp (AMinus x y) e v = AMinus (substAexp x e v) (substAexp y e v)substAexp (AMult x y) e v = AMult (substAexp x e v) (substAexp y e v)substAexp x _ _ = xsubstBexp :: Bexp -> Aexp -> Char -> BexpsubstBexp q@(BEq x y) e v = BEq (aoptimize $ substAexp x e v) (aoptimize $ substAexp y e v)substBexp q@(BLe x y) e v = BLe (aoptimize $ substAexp x e v) (aoptimize $ substAexp y e v)substBexp (BAnd b1 b2) e v = BAnd (substBexp b1 e v) (substBexp b2 e v)substBexp (BNot b) e v = BNot (substBexp b e v)substBexp q _ _ = q This implementation will rewrite a variable with an arithmetical expression within a boolean expression. We can nowdeduce some proofs, such as: > hoareAssignment ’X’ (ANum 3) (BEq (AId ’X’) (ANum 3)){TRUE} X := 3; {X == 3}
Given the precondition
TRUE ( before optimization), the command X := 3 implies the postcondition
X = 3 . For the Hoare sequence rule, given two Hoare triples, the postcondition of the first triple must be equivalent to theprecondition of the second triple (for some definition of equivalent). hoareSequence :: HoareTriple -> HoareTriple -> Either String HoareTriplehoareSequence (HoareTriple p c1 q1) (HoareTriple q2 c2 r)| boptimize q1 == boptimize q2 = Right $ HoareTriple (boptimize p) (CSequence c1 c2) (boptimizer)| otherwise = Left "Cannot construct proof"
A P
REPRINT
As with eval , the
Either data type is used to distinguish between provable and not provable terms. Several commandscan be chained as follows: > let c1 = hoareAssignment ’Y’ (ANum 1) (BAnd (BEq (AId ’Y’) (ANum 1)) (BEq (AId ’X’) (AId ’X’)))> let c2 = hoareAssignment ’Z’ (AId ’X’) (BAnd (BEq (AId ’Y’) (ANum 1)) (BEq (AId ’Z’) (AId ’X’)))> hoareSequence c1 c2Right {1 == 1 && X == X} Y := 1; Z := X; {Y == 1 && Z == X}
For the Hoare conditional rule, similarly to the Hoare sequence rule, we rely on boptimize for checking whether thecorresponding values match. hoareConditional :: HoareTriple -> HoareTriple -> Either String HoareTriplehoareConditional (HoareTriple (BAnd b1 p1) c1 q1) (HoareTriple (BAnd (BNot b2) p2) c2 q2)| boptimize b1 == boptimize b2 &&boptimize p1 == boptimize p2 &&boptimize q1 == boptimize q2 = Right $ HoareTriple (boptimize p1) (CIfElse b1 c1 c2)(boptimize q1)hoareConditional (HoareTriple (BAnd p1 b1) c1 q1) (HoareTriple (BAnd (BNot p2) b2) c2 q2)| boptimize b1 == boptimize b2 &&boptimize p1 == boptimize p2 &&boptimize q1 == boptimize q2 = Right $ HoareTriple (boptimize p1) (CIfElse b1 c1 c2)(boptimize q1)| otherwise = Left "Cannot construct proof"hoareConditional _ _ = Left "Cannot construct proof"
For the purposes of example, we start by considering the command: > CIfElse (BEq (AId ’X’) (ANum 0)) (CAssign ’X’ (APlus (AId ’X’) (ANum 1))) CSkip(If (X == 0) Then (X := X + 1;) Else (;));
To construct a Hoare triple for this command using hoareConditional , we have to construct two proofs:• { X = 0 ∧ P } X := X + 1 { Q } • { X = 0 ∧ P } T { Q } To be able to construct these proofs, we will add another substitution rule: substBexp q@(BEq (AId x) (ANum 0)) (APlus (AId x2) (ANum y1)) v| x == x2 && x2 == v && y1 > 0 = BNot (BEq (AId x) (ANum 0))| otherwise = q
This substitution rule states that whenever x is zero, increasing it by a non-zero value will produce the postconditionthat x is no longer zero. We also expand boptimize to handle double negation properly: boptimize (BAnd (BNot (BNot b1)) b2) = BAnd b1 b2 With these additions, we can construct the two proofs as follows: > let eg1 = hoareAssignment ’X’ (APlus (AId ’X’) (ANum 1)) (BAnd (BNot (BEq (AId ’X’) (ANum 0)))(BEq (ANum 0) (ANum 0)))> let eg2 = hoareSkip (BAnd (BNot (BEq (AId ’X’) (ANum 0))) (BEq (ANum 0) (ANum 0)))> hoareConditional eg1 eg2Right {TRUE} (If (X == 0) Then (X := X + 1;) Else (;)); {! (X == 0) && 0 == 0}
Even though the implementation we provide is simple enough, it can still be used to prove some useful facts:8utorial on implementing Hoare logic for imperative programs in Haskell
A P
REPRINT > hoareAssignment ’a’ (APlus (AId ’a’) (AId ’b’)) (BAnd (BEq (AMinus (AId ’a’) (AId ’b’)) (AId’A’)) (BEq (AId ’b’) (AId ’B’))){(a + b - b) == A && b == B} a := a + b; {(a - b) == A && b == B}> hoareAssignment ’b’ (AMinus (AId ’a’) (AId ’b’)) (BAnd (BEq (AId ’b’) (AId ’A’)) (BEq (AMinus(AId ’a’) (AId ’b’)) (AId ’B’))){(a - b) == A && (a - (a - b)) == B} b := (a - b); {b == A && (a - b) == B}> hoareAssignment ’a’ (AMinus (AId ’a’) (AId ’b’)) (BAnd (BEq (AId ’b’) (AId ’A’)) (BEq (AId ’a’)(AId ’B’))){b == A && (a - b) == B} a := (a - b); {b == A && a == B}
That is, we start with the precondition that a == A && b == B and we reach b == A && a == B . We proved thatthis set of commands will swap the values between two variables. To make this proof more obvious, we can add twoadditional optimization rules that state a + b − b = a and a − ( a − b ) = b : aoptimize q@(AMinus (APlus (AId a1) (AId a2)) (AId a3)) = if a2 == a3 then AId a1 else qaoptimize q@(AMinus (AId a1) (AMinus (AId a2) (AId a3))) = if a1 == a2 then AId a3 else q The proof can be completed with hoareSequence as follows: > whenRight (hoareSequence swap1 swap2) (\x -> hoareSequence x swap3)Right {a == A && b == B} a := a + b; b := (a - b); a := (a - b); {b == A && a == B}
Compile-time, run-time, etc. are all about having evaluations at different levels. There is still computation going on,but the computation strategies at the compile-time level may be different from those at the run-time level. A full eval-uation of
Command can be expensive, and sometimes even not terminate, and we wanted a way to deduce propositionswithout doing a full evaluation. We showed how to achieve this, by implementing a subset of Hoare logic as a proof ofconcept. The evaluation strategies of a programming language directly affect how a programmer thinks. For example,if a programming language has a type system, the programmer will take advantage of it, subconsciously being aware ofthe (different) evaluation strategies for the type checker, in addition to the regular evaluation strategy for programs. Ifa programming language has the feature to verify properties about programs, such as Hoare logic, the programmer willbe aware of this different evaluation strategy and use it to write more correct software. Even though the mathematicalformulas for Hoare logic look simple, implementing them is a different matter. The implementation details cover stufflike ”what expressions do we want to support”, ”are we working with a strongly normalizing language”, ”what’s thelanguage that will represent propositions”, etc. while these details are hidden in the mathematical representation ofthese formulas.
The author declares that they have no conflict of interest.
References [1] Rustan M. Leino Dafny: An automatic program verifier for functional correctness
International Conference onLogic for Programming Artificial Intelligence and Reasoning, pp. 348-370. Springer, Berlin, Heidelberg , 2010.[2] Leonardo De Moura, Nikolaj Bjørner Z3: An efficient SMT solver.
International conference on Tools andAlgorithms for the Construction and Analysis of Systems. Springer, Berlin, Heidelberg , 2008.[3] Benjamin C. Pierce, Arthur Azevedo de Amorim, Chris Casinghino, Marco Gaboardi, Michael Greenberg, C˘at˘alinHrit¸cu, Vilhelm Sj¨oberg, Brent Yorgey Logical Foundations
Electronic textbook , 2020.[4] Bruno Barras, Samuel Boutin, Cristina Cornes, Judica¨el Courant, Jean-Christophe Filliˆatre, et al. The Coq ProofAssistant Reference Manual: Version 6.1 [Research Report] RT-0203, INRIA , 1997.[5] Boro Sitnikovski Gentle Introduction to Dependent Types with Idris
Leanpub/Amazon KDP , 2018.[6] Miran Lipovaca Learn You a Haskell For Great Good
No Starch Press , 2011.[7] Charles A.R. Hoare An axiomatic basis for computer programming