The motivating example is how to type check this record:
record {
len: U16BE,
data: Array len U8
}
given that U16BE
is a binary type, different from the host integer type that the Array constructor expects:
Array : {0..} -> Type -> Type
This is an instance of the more general question of when and how to convert binary types into host types during type checking and evaluation.
Assume we have a function that maps the primitive binary types to suitable host types:
host : Type -> Type
host U8 = {0..<2^8}
host U16BE = {0..<2^16}
host U16LE = {0..<2^16}
We can extend it to fold over type constructors:
host (Array n t) = Array n (host t)
host (Record {...}) = Record {...host applied to all of its fields...}
host (Cond e t1 t2) = Cond e (host t1) (host t2)
Finally it is the identity function for every other type:
We can assume the existence of a related function that acts similarly on values:
host_value (t: Type) : t -> host t
There are several ways we can apply this type relation during type checking and evaluation, but they all seem to produce the same result.
1. Convert the whole type
If we apply the host function to the problematic record type it becomes much more reasonable:
record {
len: {0..<2^16},
data: Array len {0..<2^8}
}
This is indeed the type of the record we expect to receive after parsing is finished and it type checks successfully, but is type checking the converted type sufficient to ensure the validity of the original type? Also there is an awkward chicken and egg problem if we require the input to the host function to be a valid type, as that means we have to type check it before conversion, suggesting that the host function is more of a macro-style rewrite rule that operates on untyped terms.
2. Convert on lookup
Instead of converting the whole type in one go before type checking, apply the host function to every identifier that is looked up from the environment during type checking. This should be equivalent to converting the whole type, but interleaving the conversion with type checking allows it to only be applied to subterms which have already been type checked.
3. Dependent pair
Introduce the host type conversion into the dependent pair / record construction, so that instead of being {x:t, T(x)}
it becomes {x:t, T(host_value t x)}
. This may be a sounder theoretical basis than converting on lookup, even if it is identical in practice.
4. Rewrite expressions
Introduce the host type conversion by rewriting expressions, so that the problematic record type becomes:
record {
len: U16BE,
data: Array (host_value U16BE len) U8
}
If this inserts a call to host_value on every field reference then it seems equivalent to all of the preceding suggestions, but may be slightly less convenient to implement.
5. Subtype relation
A different strategy would be to add the host type conversion to the subtype relation:
This would solve the example problem, but perhaps it would have different implications to the approaches given above, depending on how subtyping can be used.