Motivation
My motivation to formalize a concurrency programming language using small-step semantics is to use this concept for a no deterministic programming language.
Imports
Importing from Agda standard library.
{-# OPTIONS --sized-types #-} open import Data.Bool open import Data.Nat open import Relation.Binary.PropositionalEquality hiding ([_]) open import Data.Product open import Data.List open import Data.Maybe open import Codata.Sized.Thunk open import Codata.Sized.Stream using (Stream; _∷_; repeat)
Defining the language
The programming language is a simple command that can be a natural number, two concurrency programs, or two programs that happen one next to the other. In the end, the main idea is that this program just returns a list of natural numbers.
infixr 6 _||_ infixr 7 _,,_ data Command : Set where print : ℕ → Command _||_ : Command → Command → Command _,,_ : Command → Command → Command
Small-Step
Now, it will be defined the small-step semantic of this programming language:
NextCommand = ℕ × Maybe Command
The command will be reduced to a natural number that it produced or maybe a new command if there is still a new program to reduce.
variable x m n : ℕ xs ys : List ℕ L M M' N N' : Command infixr 2 _—→_ data _—→_ : Command → NextCommand → Set where ξℕ : print n —→ n , nothing
The simplest reduction is when there is a simple program with just a natural number. This program will be reduced to it and there is no program remaining.
ξ||ₗ : M —→ m , just M' ------------------------------ → M || N —→ m , just (M' || N)
If there is a parallel program, the left program will be reduced by one step.
ξ||∅ₗ : M —→ m , nothing ---------------------- → M || N —→ m , just N
The new program after the left ends in the parallel program is just the right program.
ξ||ᵣ : N —→ n , just N' ------------------------------ → M || N —→ n , just (M || N')
It is the same thing of a previous step but for the right program.
ξ||∅ᵣ : N —→ n , nothing ---------------------- → M || N —→ n , just M
It is the same thing as a previous step but for the right program.
ξ,, : M —→ m , just M' ----------------------------- → M ,, N —→ m , just (M' ,, N)
When there is a sequence program, the left part is reduced once and the rest of the command is just the join of both of these programs.
ξ,,∅ : M —→ m , nothing ---------------------- → M ,, N —→ m , just N
When the left program ends, the rest of the program is the right one.
In this example, a parallel program can be reduced in two ways:
||prog = print 0 || print 1 _ : ||prog —→ 0 , just (print 1) _ = ξ||∅ₗ ξℕ _ : ||prog —→ 1 , just (print 0) _ = ξ||∅ᵣ ξℕ
Multi-step
Now, it will be defined the multi-step of the language.
It can be a zero-step, so a command M
can go to M
(M —↠ M
),
it can be multiple steps (that can be one, two, or any natural number)
or it can be a step that the command finishes.
infix 2 _—↠_ infix 1 begin_ infixr 2 _—→⟨_⟩_ infix 3 _∎ data _—↠_ : Command → List ℕ × Maybe Command → Set where _∎ : ∀ M ---------------- → M —↠ [] , just M _—→⟨_⟩_ : ∀ L {N} → L —→ x , just M → M —↠ xs , N --------------- → L —↠ x ∷ xs , N _—→⟨⟩_ : ∀ L → L —→ x , nothing -------------------- → L —↠ [ x ] , nothing begin_ : ∀ {N} → M —↠ N ------ → M —↠ N begin M—↠N = M—↠N
When the command evaluates to nothing, it finishes:
infix 2 _—⇀_ _—⇀_ : Command → List ℕ → Set L —⇀ xs = L —↠ xs , nothing
This is a small lemma that you can concatenate the result of two sequence commands:
,,-sequence : M —⇀ xs → N —⇀ ys → M ,, N —⇀ xs ++ ys ,,-sequence (_ —→⟨⟩ st1) st2 = _ ,, _ —→⟨ ξ,,∅ st1 ⟩ st2 ,,-sequence (_ —→⟨ st ⟩ st1) st2 = _ ,, _ —→⟨ ξ,, st ⟩ ,,-sequence st1 st2
Single-step
The single-step is just the small step that you hide the result of the small step in the type.
data SingleStep : Command → Set where singleStep : ∀ n L' → L —→ n , L' ---------- → SingleStep L
With the eval step, I prove that every command has a single step associated with that. If the program is parallel, it will consume a boolean from the stream and this boolean represents which part of the program will be evaluated. The false represents the left part while true represents the right.
eval-step : Stream Bool _ → (L : Command) → Stream Bool _ × SingleStep L eval-step sxs (print x) = sxs , (singleStep x nothing ξℕ) eval-step sxs (M ,, N) with eval-step sxs M ... | sys , singleStep n (just M') st = sys , (singleStep n (just (M' ,, N)) (ξ,, st)) ... | sys , singleStep n nothing st = sys , (singleStep n (just N) (ξ,,∅ st)) eval-step (false ∷ sxs) (M || N) with eval-step (sxs .force) M ... | sys , singleStep n (just M') st = sys , (singleStep n (just (M' || N) ) (ξ||ₗ st)) ... | sys , singleStep n nothing st = sys , (singleStep n (just N) (ξ||∅ₗ st)) eval-step (true ∷ sxs) (M || N) with eval-step (sxs .force) N ... | sys , singleStep n (just N') st = sys , (singleStep n (just (M || N') ) (ξ||ᵣ st)) ... | sys , singleStep n nothing st = sys , (singleStep n (just M) (ξ||∅ᵣ st))
Evaluation
For the definition of evaluation, it will necessary to be defined when an command was finished in the evaluation and the steps of the evaluation.
data Steps : Command → Set where steps : ∀ xs → L —⇀ xs ---------- → Steps L data Finished (N : Command) : Set where done : Steps N ---------- → Finished N out-of-gas : ---------- Finished N
A command is finished when it is a value or when there is no more gas available to calculate it. In Agda, every computation must terminate. So it is impossible to have an infinite loop. So when a command takes so much time to finish, it runs out of gas and there is no result.
The evaluation finishes when there is proof of multi-step for the value and it is already finished.
eval : ℕ → Stream Bool _ → (L : Command) → Finished L eval 0 sxs L = out-of-gas eval (suc gas) sxs L with eval-step sxs L ... | sys , singleStep n nothing st = done (steps [ n ] (L —→⟨⟩ st)) ... | sys , singleStep n (just M) st with eval gas sys M ... | out-of-gas = out-of-gas ... | done (steps xs st2) = done (steps (n ∷ xs) (L —→⟨ st ⟩ st2))
The evaluation takes gas and the command to evaluate. In the end, it returns the value with proof that the command evaluates this value.
Here, some examples of the evaluation:
rfalse = repeat false rtrue = repeat true evalf = eval 100 rfalse evalt = eval 100 rtrue _ : evalf ||prog ≡ done (steps (0 ∷ [ 1 ]) (print 0 || print 1 —→⟨ ξ||∅ₗ ξℕ ⟩ (print 1 —→⟨⟩ ξℕ))) _ = refl _ : evalt ||prog ≡ done (steps (1 ∷ [ 0 ]) (print 0 || print 1 —→⟨ ξ||∅ᵣ ξℕ ⟩ (print 0 —→⟨⟩ ξℕ))) _ = refl _ : evalf (print 0 ,, print 1) ≡ done (steps (0 ∷ [ 1 ]) (print 0 ,, print 1 —→⟨ ξ,,∅ ξℕ ⟩ (print 1 —→⟨⟩ ξℕ))) _ = refl