Imperative Programming - Algol
Haskell is a dialect Algol!
Modernized Algol (MA) - revised by Robert HarperPermalink
MA = PCF with a modalitymodality - distinguishes expressions from commands
τ=things in PCF|cmd(τ)expressions e=things in PCF|cmd(m)commands m=ret(e)|bnd(e,x.m)|dcl(e,a.m) (dcl a:=e in m)|set[a](e) (a:=e)|get[a] (get a)τ=things in PCF|cmd(τ)expressions e=things in PCF|cmd(m)commands m=ret(e)|bnd(e,x.m)|dcl(e,a.m) (dcl a:=e in m)|set[a](e) (a:=e)|get[a] (get a)aa’s are assignables not variables! xx’s are variables! Assignables are not a
form of an expression of it’s type. Assignables is a location in memory whose contents
has a type, where we write a1∼τ1a1∼τ1 (not a1:τ1a1:τ1). Assignables are really
indices to a family of getget, setset operations, they are not values, arguments,
or evaluated. They are just indices, and getget’s and setset’s are just capabilities
to get and set aa. We can define references, i.e. &a
in a real programming language,
as a pair <geta,seta><geta,seta>, which just a thing that gives you access to the capabilities
of getting and setting aa.
Types and expressions are “pure” - don’t depend on memory, whereas commands are “impure”.
StaticsPermalink
Γ⊢Σe:τΓ⊢Σe:τwhere ΣΣ is tye types of assignables, i.e. a1∼τ1,…,an∼τna1∼τ1,…,an∼τn.
Γ⊢Σm∼:τΓ⊢Σm∼:τIt means a well-formed command whose return values has type ττ
Γ⊢Σm∼:τΓ⊢Σcmd(m):cmd(τ)Γ⊢Σm∼:τΓ⊢Σcmd(m):cmd(τ)The above is the Introduction rule for cmd
Γ⊢Σe:cmd(τ);Γ,x:τ⊢Σm′∼:τ′Γ⊢Σbnd(e,x.m′)∼:τ′The above is the Elimination rule for cmd
Γ⊢Σe:τΓ⊢Σret(e)∼:τ Γ⊢Σe:τ;Γ⊢Σ,a∼τm′∼:τ′;τ mobile;τ′ mobileΓ⊢Σdcl(e,a.m′)∼:τ′It means I am declaring an assignable: I declare a, initialize it to e, and run the command m′. A type is mobile if the value of the type can be pulled out from the scope of the assignable. Example of mobile types: eager natural numbers, pairs/sums of mobile types. Example of not mobile types: functions (because the body of the function can use assignables even if the ultimate return value is nat), commands. This will be explained in later sections when we talk about the dynamics.
Γ⊢Σ,a∼τget[a]∼:τ Γ⊢Σ,a∼τe:τΓ⊢Σ,a∼τset[a](e)∼:τExercise: We have the following [Pre-]monad defined:
T(a):typer:a→T(a)b:T(a)→(a→T(b))→T(b)Show that you can define r and b for T(a)=cmd(a).
The important fact is that you start with the modality, then they can be formed into a pre-monad.
DynamicsPermalink
e valΣ e↦Σe′μ||m means a command m in memory μ. The notation is designed to connecto with concurrency. The idea is that we have a concurrent composition of a main program m running simultaneously with threads that govern the contents of each of the location.
μ||m finalΣ μ||m↦Σμ′||m′ cmd(m) valΣ frace↦Σe′μ||ret(e)↦Σμ||ret(e′) e valΣμ||ret(e) finalΣ e↦Σe′μ||bnd(e,x.m1)↦Σμ||bnd(e′,x.m1) μ||m↦Σμ′||m′μ||bnd(cmd(m),x.m1)↦Σμ||bnd(cmd(m′),x.m1) e valΣμ||bnd(cmd(ret(e)),x.m1)↦μ||[e/x]m1 μ⊗a↪e||get[a]↦Σ,a∼τμ⊗a↪e||ret(e)Exercise: define set
We have something called Stack Discipline invented by Dijkstra. The idea is that the assignables in Algol are stack alocated. When I do a dcl(e,a.m′), I declare an assignable in m′, I can get it and set it in m′. When m′ is finished it’s deallocated.
e valΣ;μ⊗a↪e||m↦Σμ′⊗a↪e′||m′μ||dcl(e,a.m)↦Σμ′||dcl(e′,a.m′)To rephrase the above in English: Start from lower left: I have a value e which is the initializer of the assignable a and I want to execute m in the presence of that assignable. What can I do? I go above the line and do: let’s extend the memory μ with a having the content e, and I execute m, and I will, in the process of doing that, maybe modify some outer assignables (turn μ to μ′), maybe modify some inner assignables (make a have the content e′ instead of e originally) and get a new command m′. Then I update the memory (from μ to μ′), and reset the world (from m to m′). In other words, I take the starting state where I declare a being initialized to e and execute m, once you take a step of execution of m in the situation of μ⊗a↪e, you might have done a set in a and updated that to e′! The resulting state is: I restart your program in the situation in which the initializer is not what it was (e) but what it becomes as result of the execution step (e′) and then proceed from there.
e,e′ valΣμ||dcl(e,a.ret(e′))↦Σμ||ret(e′)To rephrase the above in English: Start from lower left: I declare an assignable a and assign it to e, and I am returning a value ret(e′), what do I do next? The idea of the stack discipline is: when you finish executing the body of dcl then you get out of it!
Some issues with type safetyPermalink
In the above formula, in the lower left part, if e has type τ, then e′ has type τ′ in the context of Σ,a∼τ, but in the lower right part, e′ should also have type τ′, but with only Σ alone. In traditional Algol, we can only return nat, which means if a numeric value val type checks with an assignable present (Σ,a∼τ), it will type checks with the assignable absence (only Σ), only under some conditions!
So here is the question: Under what condition is the following statement true?
If e is a value of type nat and type checks with the assignable a, it will also type check in type nat without a.
Answer: Only if the successors are valued eagerly! if the successors are lazy, then the arguments of the successors are unevaluated expressions (they are no longer values val)! Algol only makes sense if the constructors of the successors are eager!
Here is an (clever) example of a successor of N doesn’t type check if lazy. I want a successor of something, which is an expression, that uses an assignable a. The goal is it will not type check outside of scope of a:
S((λx:nat.z)(cmd(get([a]))))Explanation: We have a constant function that takes in a nat and return 0 z. And to type check the argument cmd(get([a])) we have to eventually type check a, even though it doesn’t matter the ultimate return value of the function because it’s constant!
This is a perfect example of the interactions between language features. We might think that whatever I do with the PCF level whatever we don’t care lazy or eager and the command level is separated. This is wrong! The hard part of a language design is to make everything fit in a coherent way. This is way language design is hard!
So traditional Algol they make things work by restricting the return value only nat, and make nat eager. A better idea is that we can demand the result type of dcl mobile! And we also need to restrict that we cannot assign values that aren’t mobile, because we can assign the value from a return value. This is the explanation of the above “This will be explained in later sections”.
MA - Scoped AssignablesPermalink
ReferencesPermalink
We can define ref(τ) (immobile) as we mentioned briefly before in the following ways:
Concretely:
ref(τ)≜cmd(τ)×(τ→cmd(τ))Where cmd(τ) is just the getter and τ→cmd(τ) is the setter.
Then we can have:
getref(<g,s>)↦gsetref(<g,s>)↦sThe problem is, if you look at the type ref(τ), it only says it has getter and setters, but it doesn’t say the getter and setter are for the same assignable! So you can have a getter for a and a setter for b and you won’t know the difference. So then we can define it in another way:
Abstractly:
The type is still ref(τ), but we have the following elim rules:
getref(&a)↦get[a]setref(&a,e)↦set[a](e)MA - (Scope) Free AssignablesPermalink
All types are mobile. Previously because the stack discipline is causing us trouble about mobility, and we go back to to add some mobility rules in the statics. But there are other ways to fix this: let’s change the statics, make every type mobile, and we’ll make our dynamics fit that. Here are the new dynamics. (Also called scope-free dynamics, or in other words, assignables are heap allocated.)
γΣ{μ||m}↦γΣ′{μ′||m′}Note the above transition is unlabelled.
e valΣγΣ{μ||dcl[τ](e,a.m)}↦γΣ,a∼τ{μ⊗a↪e||m} e valγΣ{μ||ret(e)} finalPCF (FPC with recursive types rec) + commands above with free assignables ≈ Haskell :)
IssuesPermalink
In Algol, we have a clear distinction between expressions and commands; they are completely separated. There are many benefits of doing that. But in the “real world”, in doing that, we lose a lot of benign effects. For example efficiency, we lose:
- laziness/memoization
- splay trees - self-adjusting data structures
- …
While we are condemning benign effects, we do rely on them in real life.
Comments