The Truth of a Procedure Lisa Lippincott Meeting C++, November 2018
Why don’t we routinely write down the reasoning behind our programs in a formal way, and have computers check it? The mathematical tools we use for proofs present a poor user interface for procedural programming.
Logic
Procedural Logic
A procedure is an embodied algorithm, conceived as a scheme by which events may be arranged in time, space, possibility and causality. Procedures are sentences. A sentence is a statement about the world, which may either be in agreement with the world (“true”) or be in disagreement with the world (“false”).
Sentence ( false and true ) or (( true or false ) and true )
true false false false true or true true and and and and and and or or or or or
🙃 makes a choice or or 😉 makes a choice and and and 💀 loses the game false true or true true 🙂 loses the game true false false
🙃 makes a choice or or This sentence is true: 😉 makes a choice and and 🙃 has a winning strategy. 💀 loses the game or true true true
or and and and or or false true or true true false and false true false false true
or and This sentence is true: This sentence is false: and or 🙃 has a winning strategy. 😉 has a winning strategy. or true and false true false
The code here is written in a fantasy C++, with extensions that make proofs fit into the code.
void foo() interface { void foo() void bar() … implementation interface …prologue { { … … …prologue … … … implementation; bar(); implementation; … … … … … …epilogue …epilogue } } … }
😉 🙃 🙃 void foo() interface { void foo() void bar() … implementation interface …prologue { { … … …prologue … … … implementation; bar(); implementation; … … … … … …epilogue …epilogue } } … } 💀 🙃 😉
😉 🙃 🙃 void foo() interface { void foo() void bar() … implementation interface …prologue { { … … …prologue … … … implementation; bar(); implementation; … … … … … …epilogue …epilogue } } … } 💀 🙃 😉
const int factorial( const int& n ) interface { claim n >= 0; claim usable( n ); implementation; claim usable( n ); claim usable( result ); }
claim statements are assertions const int factorial( const int& n ) interface that must hold for local reasons. { claim n >= 0; Yellow claims for reasons in this function; purple claims for reasons in other functions. claim usable( n ); implementation; 💀🙂 If a claim statement fails, claim usable( n ); the current player loses. claim usable( result ); }
An lvalue is usable if it may be const int factorial( const int& n ) interface used in the usual manner for its { cv-qualified type. claim n >= 0; claim usable( n ); Usable scalar lvalues — have a stable value (if not volatile), and implementation; — are modifiable (if not const). claim usable( n ); claim usable( result ); Class types may have more complicated } rules for usability.
If an operation is used in the const int factorial( const int& n ) interface procedure, its interface is part { of the game. claim n >= 0; claim usable( n ); We’ll start the game with the interface of operator>=( const int&, const int& ) . implementation; claim usable( n ); claim usable( result ); }
The current player const bool operator>=( const int& a, announces the value const int& b ) of each usable lvalue. interface { claim usable( a ); The value of a is six. 😉 claim usable( b ); And the value of b is zero. implementation; claim usable( a ); claim usable( b ); claim usable( result ); }
If the object hasn’t been const bool operator>=( const int& a, changed, the player must const int& b ) repeat the previous value. interface { claim usable( a ); The value of a is six. 😉 claim usable( b ); And the value of b is zero. implementation; a is still six, claim usable( a ); and b is still zero. claim usable( b ); 😉 claim usable( result ); And the result is true. } 💀🙂 Unexpectedly changing a value is penalized.
Lvalues asserted usable directly within the prologue provide the const int factorial( const int& n ) direct input to the function. interface { The result is true ; the claim succeeds! claim n >= 0; 😉 The value of n is six. claim usable( n ); implementation; The epilogue likewise describes claim usable( n ); claim usable( result ); the direct output. }
const int factorial( const int& n ) const int factorial( const int& n ) interface implementation { { claim n >= 0; int r = 1; claim usable( n ); for ( int i = n; i != 0; --i ) if ( can_multiply( r, i ) ) implementation; r *= i; else claim usable( n ); throw factorial_overflow(); claim usable( result ); } return r; }
☞ int r = 1; for ( int i = n; i != 0; --i ) for ( int i = n; i != 0; --i ) return r; if ( can_multiply( r, i ) ) r *= i; throw factorial_overflow(); for ( int i = n; i != 0; --i )
When substitutable is claimed, lvalues must have identical values. int::int( const int& a ) interface { 🙃 The value of a is one. claim usable( a ); implementation; a and *this are both one. claim substitutable( a, *this ); The value of a is one, and claim usable( a ); 😉 claim usable( *this ); *this is one. *this can be changed. }
☞ int r = 1; for ( int i = n; i != 0; --i ) for ( int i = n; i != 0; --i ) return r; if ( can_multiply( r, i ) ) r *= i; throw factorial_overflow(); for ( int i = n; i != 0; --i )
☞ int r = 1; for ( int i = n; i != 0; --i ) for ( int i = n; i != 0; --i ) return r; if ( can_multiply( r, i ) ) r *= i; throw factorial_overflow(); for ( int i = n; i != 0; --i )
inline Inline functions without const bool operator!=( const int& a, declared interfaces are const int& b ) played by the entering { return !( a == b ); player. } Sometimes showing what a function does is simpler than describing it. inline But this also makes the program brittle! const bool operator!( const bool& c ) { return c ? false : true; }
Branch directions are also part const bool operator==( const int& a, const int& b ) of the direct input and output. interface { The value of a is six, claim usable( a ); 🙃 claim usable( b ); and b is zero. implementation; The result is false; swerve right! if ( result ) claim substitutable( a, b ); The value of a is still six, claim usable( a ); b is still zero, claim usable( b ); 😉 claim usable( result ); and the result is false. }
inline Inline functions without const bool operator!=( const int& a, declared interfaces are const int& b ) played by the entering { return !( a == b ); player. } Sometimes showing what a function does is simpler than describing it. inline But this also makes the program brittle! const bool operator!( const bool& c ) { return c ? false : true; }
☞ int r = 1; for ( int i = n; i != 0; --i ) for ( int i = n; i != 0; --i ) return r; if ( can_multiply( r, i ) ) r *= i; throw factorial_overflow(); for ( int i = n; i != 0; --i )
can_multiply has a basic interface: usable input, const bool can_multiply( const int& a, usable output. const int& b ) interface { claim usable( a ); The value of a is one, and 🙃 claim usable( b ); the value of b is six. implementation; a is still one, claim usable( a ); and b is still six. claim usable( b ); 😉 claim usable( result ); And the result is true. }
☞ int r = 1; for ( int i = n; i != 0; --i ) for ( int i = n; i != 0; --i ) return r; if ( can_multiply( r, i ) ) r *= i; throw factorial_overflow(); for ( int i = n; i != 0; --i )
int& int::operator*=( const int m ) interface { claim can_multiply( *this, m ); claim usable( m ); claim usable( *this ); implementation; claim aliased( result, *this ); claim usable( m ); claim usable( *this ); claim usable( result ); }
If a function’s direct input is const bool can_multiply( const int& a, repeated, its direct output const int& b ) must also be repeated. interface { claim usable( a ); As before, the value of a is one, 🙃 claim usable( b ); and the value of b is six. implementation; a is still one, claim usable( a ); and b is still six. claim usable( b ); 😉 claim usable( result ); Like last time, the result is true. } 💀🙂 Announcing different direct output is penalized.
Lvalues are aliased when they int& int::operator*=( const int m ) interface refer to the same object. { The can_multiply claim succeeds! claim can_multiply( *this, m ); The value of m is six, and while claim usable( m ); 🙃 claim usable( *this ); *this is currently one, it can change. implementation; result and *this are the same object. claim aliased( result, *this ); m is still six; claim usable( m ); *this is now six and can change; claim usable( *this ); 😉 claim usable( result ); the result is six and can change. } 💀🙂 There is a penalty for not mentioning observable aliasing.
Recommend
More recommend