Computing by evaluating expressions and declarations
let foo x y = (let z = y + 1 in z - y)
Recursive functions
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
Immutable data structures
let rec map f xs = match xs with
| Nil -> Nil
| Cons hd tl -> Cons (f hd) (map f tl)
Unnamed functions (lambdas expressions, first-class values)
map (fun x -> x + 42) (Cons 1 (Cons 2 (Cons 3 Nil)))
avoid mutable state and other “side-effects” when possible
fold_right (+) (map incr xs) 0
this pure point-free style considered better than:
let sum = alloc 0 in
foreach xs (fun x ->
sum := !sum + x + 1
)
but of course, can't always avoid side-effects
module Hello
open FStar.IO
let main = print_string "Hello F*\n"
$ fstar.exe --no_extract ... --odir out --codegen OCaml Hello.fst
Extracting module Hello
...
ocamlfind opt ... out/Hello.ml -o Hello.exe
./Hello.exe
Hello F*!
$ocaml
...
# 42;;
- : int = 42
# let x = 2 in x + 1;;
- : int = 3
# "Hello";;
- : string = "Hello"
# let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)));;
val factorial : int -> int = <fun>
# List.map (fun x -> x + 42) [1;2;3];;
- : int list = [43; 44; 45]
# List.map (fun x -> x + 42) ["one";"two";"three"];;
^^^
Error: This expression has type string but an expression was expected of type int
Stack.fsti
module Stack
val stack : Type0 (* type stack *)
val empty : stack
val push : int -> stack -> stack
val is_empty : stack -> bool
val pop : stack -> option stack
val top : stack -> option int
StackClient.fst
module StackClient
let main =
let s0 = Stack.empty in
let s1 = Stack.push 3 s0 in
let s2 = Stack.push 4 s1 in
Stack.top s2
Stack.fst
module Stack
let stack = list int
let empty = []
let push x xs = x :: xs
let is_empty xs = match xs with
| [] -> true
| x::xs' -> false
let pop xs = match xs with
| [] -> None
| x::xs' -> Some xs'
let top xs = match xs with
| [] -> None
| x::xs' -> Some x
StackClientBad.fst
module StackClientBad
let main =
let s0 = Stack.empty in
let s1 = Stack.push 3 s0 in
2 :: s1
[hritcu@detained code]$ fstar.exe StackClientBad.fst
./StackClientBad.fst(6,9-6,11) : Error
Expected expression of type "list (?32566 s0 s1)";
got expression "s1" of type "Stack.stack"
Refinement types
type nat = x:int{x>=0}
Function types (together with refinements)
val factorial : nat -> nat
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
Dependent function types
val incr : x:int -> y:int{x < y}
let incr x = x + 1
Quiz: can you find other types for incr
?
The F* functions we saw so far were all pure/total functions
Tot
marker = no side-effects, terminates on all inputs
val incr : x:int -> Tot (y:int{x < y})
let incr x = x + 1
val factorial : nat -> Tot nat
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
Quiz: how about giving this weak type to factorial?
val factorial : int -> Tot int
let rec factorial n = (if n = 0 then 1 else n * (factorial (n - 1)))
^^^^^
Subtyping check failed; expected type (x:int{(x << n)}); got type int
factorial (-1)
loops! (int
type in F* is unbounded)
<<
)
<
(negative ones unrelated)
decreases e
)
val ackermann: m:nat -> n:nat -> Tot nat (decreases %[m;n])
let rec ackermann n m =
if m=0 then n + 1
else if n = 0 then ackermann 1 (m - 1)
else ackermann (ackermann (n - 1) m) (m - 1)
val ackermann: m:nat -> n:nat -> Tot nat
We might not want to prove all code terminating
val factorial : int -> Dv int
Some useful code really is not always terminating
val eval : exp -> Dv exp
let rec eval e =
match e with
| App (Lam x e1) e2 -> eval (subst x e2 e1)
| App e1 e2 -> App (eval e1) (eval e2)
| Lam x e1 -> Lam x (eval e1)
| _ -> e
let main = eval (App (Lam 0 (App (Var 0) (Var 0)))
(Lam 0 (App (Var 0) (Var 0))))
./Divergence.exe
F* encapsulates effects
val factorial : int -> Dv int
type tau = x:int{x = factorial (-1)}
^^^^^^^^^^^^^^^^^^
Expected a pure expression; got an expression ... with effect "DIV"
ML effects are implemented primitively:
ML
effect: non-termination, state, exceptions, input-output
F* automatically lifts effects as needed (Tot<Dv<ML
)
(1+2) + factorial (-1) : Dv int
val swap_add_sub : r1:ref int -> r2:ref int -> St unit
let swap_add_sub r1 r2 =
r1 := !r1 + !r2;
r2 := !r1 - !r2;
r1 := !r1 - !r2
let main =
let r1 = alloc 1 in
let r2 = alloc 2 in
swap_add_sub r1 r2;
print_string ("r1=" ^ string_of_int !r1 ^ "; " ^
"r2=" ^ string_of_int !r2 ^ "\n")
...
r1=2; r2=1
Exercise: write a more natural implementation for swap
val factorial : nat -> Tot nat
val factorial : x:int -> Pure int (requires (x >= 0))
(ensures (fun y -> y >= 0))
Tot
is actually just an abbreviation
Tot t = Pure t (requires True) (ensures (fun _ -> True))
Pure
, ST
)
int
)
val factorial : x:int -> Pure int (requires (x >= 0))
(ensures (fun y -> y >= 0))
Pure
, more complex for ST
and ML
val swap_add_sub : r1:ref int -> r2:ref int -> ST unit
(requires (fun h' -> r1<>r2))
(ensures (fun h' _ h ->
let x1 = sel h' r1 in
let x2 = sel h' r2 in
equal h (upd (upd h' r1 x2) r2 x1)))
let swap_add_sub r1 r2 =
r1 := !r1 + !r2;
r2 := !r1 - !r2;
r1 := !r1 - !r2
St
is actually just an abbreviation
St t = ST t (requires True) (ensures (fun _ _ _ -> True))
module FStar.Heap
type heap
val sel : #a:Type -> heap -> ref a -> Tot a
val upd : #a:Type -> heap -> ref a -> a -> Tot heap
val equal : heap -> heap -> Tot bool
assume SelUpd1 : sel (upd h r v) r == v
assume SelUpd2 : r<>r' ==> sel (upd h r v) r' == sel h r'
module FStar.ST
val (!): #a:Type -> r:ref a -> ST a
(requires (fun h -> True))
(ensures (fun h0 x h1 -> equal h0 h1 /\ x==sel h0 r))
val (:=): #a:Type -> r:ref a -> v:a -> ST unit
(requires (fun h -> True))
(ensures (fun h0 x h1 -> equal h1 (upd h0 r v)))
General-Purpose Functional Programming
SMT-based semi-automated verification tool
Proof assistant for writing/checking math proofs
Reasoning about user-defined effects
Relational verification
Compiling a subset of F* to C (KreMLin)
Securely compiling F* (full abstraction)
F* tutorial (https://www.fstar-lang.org/tutorial/)
Please try to solve exercises (at least 1a, 2a, 3c)
#fstar
Slack channel on http://fpchat.com/.
You can also send me your questions by email
If you're still stuck you can have a look at the solutions
in 1 weeks: Verifying Stateful Programs in F*
in 2 weeks: Verified Low-Level Programming Embedded in F* (Jonathan Protzenko)
in 3 weeks: From “Hoare Logic” to “Dijkstra Monads for Free”
in 4-5 weeks: Verifying Crypto Implementations in F* (Karthik)