Dependent and linear typing on representing hardware memory
Lets imagine that we'd already have a dependent linearly typed language, and I'd like to model computer memory and arrays with it.
There would be something like bytes. They'd behave like modular numbers with the domain ranging from 0 to 255.
0 :: byte1 :: byte...255 :: byte
Bytes can be freely copied around without constraints, as they are values. Next introduce reads and writes:
read :: byte ⊸ bytewrite :: byte ⊸ byte ⊸ 1
The reads and writes follow (offset,value) -pattern.
Now we've introduced memory reads and writes into a small address space.
Next we'd like to describe what an array is.
We could just start in plain code:
array_read :: byte ⊸ [byte]array_write :: byte ⊸ [byte] ⊸ 1array_size :: byte ⊸ bytearray_read offset = reads (read offset) wherereads 0 = []reads n = read (offset+n) : reads (n-1)array_write offset values =write offset (length values); writes (offset+1) values wherewrites offset [] = 0writes offset (head:tail) =write offset head; writes (offset+1) tailarray_size offset = (read offset) + 1
It loans bit of syntax from haskell to represent everything,
also we have things such as (;) :: 1 ⊸ 1 ⊸ 1 and 0 :: 1
plus addition/subtraction with bytes.
The semicolon is a parallel operator.
There are few problems in this simple example that glare out from it.
1. If we were modeling low-level programs this way,what would we do with lists?2. Technically these are not arrays,but dynamic arrays with the length written into them.3. All definitions are hopelessly recursive,we define the thing three timesyet haven't proven the definitions are consistent.4. We have no ways to determine whether parallel accessis causing a race condition or not.
The last point is interesting in particular. What's a sufficient proof that the above definition here is consistent?
If we write some value and then read it back, it should be the same value.
∏n:byte. (array_read n . array_write n) ≡ idIf we write some value and then read it back, in middle write anything into the byte outside the value, we should obtain the same value.
∏n,trash:byte. ( array_read n. array_write n ) ≡ id; write (n+array_size n) trash
That's how we would represent it,
but it'd appear that it's not easy
unless we know something more about the main memory itself.
For example, we don't know whether (write n . read n) ≡ id.
To model fixed-size arrays we'd have to do something like this:
array_read :: ∏len:byte. byte ⊸ Σlst:[byte]. (length lst ≡ len)array_write :: ∏len:byte. byte ⊸ ∏lst:[byte]. (length lst ≡ len) ⊸ 1array_size :: ∏len:byte. byte
Dependent types describe this kind of relations where a type is chosen by a value in the program. Technically it's building parts of the program only after we know what value is given into it.