297 lines
12 KiB
Plaintext
297 lines
12 KiB
Plaintext
INTRODUCTION
|
|
=================
|
|
|
|
This system is derived from linear type theory, such that for a value x with type A:
|
|
|
|
(x : A) means that x must be used exactly once.
|
|
(x : ?A) means that x may be used at most once, or forgotten.
|
|
(x : !A) means that x may be used multiple times, but at least once.
|
|
(x : ?!A) means that x may be used zero or more times.
|
|
|
|
? is read "why not", and ! is read "of course".
|
|
|
|
Additive means "there are two possibilities, but you only get one". In other words,
|
|
in each branch of an additive relationship, all of the non-? variables in the
|
|
context must be disposed of.
|
|
Multiplicative means "you get both of these at once". In other words, all of the
|
|
non-? variables must be disposed of as a whole.
|
|
Conjunction means "both values exist at once".
|
|
Disjunction means "only one of these values exist at once".
|
|
|
|
Additive conjunction (a & b) means "you can use either a or b, it's up to you which".
|
|
Additive disjunction (a + b) means "I am going to give you an a or a b, and you must
|
|
be prepared to deal with either, but not both at the same time."
|
|
Multiplicative conjunction (a * b) means "I am going to give you an a and a b, and
|
|
you have to use both".
|
|
Multiplicative disjunction (a | b) means "you must be prepared to recieve both an a
|
|
and a b, but ultimately only one will be used". If that's a bit confusing, that's alright.
|
|
|
|
|
|
AXIOM
|
|
=====
|
|
|
|
a | -a
|
|
|
|
where -a represents the dual of a, and | represents multiplicative disjunction.
|
|
This is the axiom of the excluded middle, meaning to "a is true or not true".
|
|
Its implementation is explained in "multiplicative disjunction". There are no
|
|
further axioms.
|
|
|
|
|
|
Duals:
|
|
|
|
While they're a general construct, the dual is actually only used for the
|
|
definition of the axiom, so I'll specify it here:
|
|
|
|
-(-a) = a
|
|
-(a & b) = -a + -b
|
|
-(a * b) = -a | -b
|
|
-Top = 0
|
|
-1 = Bottom
|
|
-(!a) = ?(-a)
|
|
|
|
and vice versa in all cases.
|
|
|
|
|
|
Distributivity:
|
|
|
|
This isn't actually related to the axiom, but I might as well while I'm at it.
|
|
None of these are built-in rules, they are just helpful notions provable within
|
|
the language itself. These are the distributive laws:
|
|
|
|
a * (b + c) = (a * b) + (a * c)
|
|
a | (b & c) = (a | b) & (a | c)
|
|
a * 0 = 0
|
|
a & Top = Top
|
|
|
|
notice how "exponentials" convert "addition" into "multiplication"-- hence the names:
|
|
|
|
!(a & b) = !a * !b
|
|
?(a + b) = ?a | ?b
|
|
!Top = 1
|
|
?0 = Bottom
|
|
|
|
and vice versa in all cases in both sets listed.
|
|
|
|
|
|
STRUCTURAL RULES
|
|
================
|
|
|
|
The weakening and contraction rules are specified twice since
|
|
I hope to extend this into an ordered logic, so I have to include
|
|
the exchange-requiring equivalents. This is also true for constructors and
|
|
eliminators in the computational rules. All of these can be implemented only
|
|
once, with the second rule being implemented in terms of the first via a swap
|
|
that /doesn't/ apply an unordered constraint. If I do not extend this to an
|
|
ordered logic, you can simply ignore all of the second rules.
|
|
|
|
Weakening: a -> ?b -> a ### const
|
|
?a -> b -> b ### flip const
|
|
Contraction: (a -> !a -> b) -> (!a -> b) ### join
|
|
(!a -> a -> b) -> (!a -> b) ### join . flip
|
|
Exchange: (a -> b -> c) -> (b -> a -> c) ### flip
|
|
(or equivalently (a * b) -> (b * a))
|
|
|
|
(todo: do I need a dual exchange rule for &?)
|
|
|
|
|
|
COMPUTATIONAL RULES
|
|
===================
|
|
|
|
These are the introduction and elimination rules for compound terms in the
|
|
language. Introduction terms are labeled with an i, elimination terms with an e.
|
|
When multiple introduction/elimination rules are required, they are each labelled
|
|
with a number.
|
|
|
|
|
|
Additive conjunction (with):
|
|
|
|
Topi1 : a -> a & Top
|
|
Topi2 : a -> Top & a
|
|
additively (all of the context is used in either branch of the options a and b),
|
|
&i : a -> b -> a & b
|
|
&e1 : a & b -> a
|
|
&e2 : a & b -> b
|
|
|
|
The eliminators may be called "projections".
|
|
|
|
There is no elimination rule for Top, since it doesn't exist. It can only
|
|
be introducted in terms of the type signatures in Top1 and Top2, and the
|
|
only thing you can do with it is ignore it via the associated projection.
|
|
It doesn't have to exist because nothing using the projection for Top
|
|
can exist because it cannot be used for anything, nor weakened.
|
|
|
|
Additive conjunction can be interpreted as a pair of procedure pointers,
|
|
with the context enclosed inside. The projections are function calls that
|
|
choose which procedure to invoke to consume the context. A call to Top
|
|
is equivalent to nontermination, since Top doesn't exist.
|
|
|
|
The compiler could probably safely maybe automatically insert the
|
|
eliminators when the corresponding type is required, but I haven't totally
|
|
thought this through, and there are potential issues with e.g.
|
|
(a & b) & a, in the event that choice of a matters.
|
|
|
|
|
|
Additive disjunction (plus):
|
|
|
|
0e : 0 -> a
|
|
+i1 : a -> a + b
|
|
+i2 : b -> a + b
|
|
+e1 : (a -> c) & (b -> c) -> (a + b -> c)
|
|
+e2 : (b -> c) & (a -> c) -> (a + b -> c)
|
|
|
|
0, like Top, does not exist. It only exists as a fragment of the type
|
|
signature in disjunction introduction, because
|
|
+i1 (x : A) : forall b. A + b
|
|
and 0 is simply an instanciation of b, taking the entire forall thing
|
|
far too literally, as shown in its eliminator. It is impossible to
|
|
have an actual instance of 0, because by definition, it is the type
|
|
not selected when you create a +. In terms of elimination, all eliminations
|
|
of an 0 + a type are equivalent to (e1 0e), with the exception of the fact
|
|
that you have to destroy anything in your context that cannot be weakened.
|
|
This can be trivially implemented as using 0e to generate a function type
|
|
that directly consumes all of the terms in the context, and returning a c.
|
|
|
|
The eliminators are rather intuitive. The choice of & is necessary because
|
|
each option must entirely dispose of its context, as is in the definition
|
|
of &i.
|
|
|
|
A value of a + b is trivial: it's just a tagged union, i.e. an indicator
|
|
of whether it's a or b, and the value a or b.
|
|
|
|
|
|
Multiplicative conjunction (times):
|
|
|
|
1i : 1
|
|
1e1 : 1 * a -> a
|
|
1e2 : a * a -> a
|
|
*i : a -> b -> a * b
|
|
*e1 : a * b -> (a -> b -> c) -> c
|
|
*e2 : (a -> b -> c) -> a * b -> c
|
|
|
|
1 does exist, and in fact its sole introduction rule is "1 exists", but
|
|
it holds no information, so in practice it doesn't have to be stored.
|
|
Formally it has only one elimination rule, 1 -> <nothing>, but a function
|
|
can't return nothing, so instead you have to eliminate it via its definition
|
|
as the identity element.
|
|
|
|
The rest of it is pretty intuitive. In memory, it's just a pair of values.
|
|
|
|
|
|
Multiplicative disjunction (par):
|
|
|
|
Bottomi1 : a -> a | Bottom
|
|
Bottomi2 : a -> Bottom | a
|
|
Bottome : Bottom -> a
|
|
|i : ?a * ?b -> a | b
|
|
|e1 : ?(a -> c) * ?(b -> c) -> a | b -> c
|
|
|e2 : a | b -> ?(a -> c) * ?(b -> c) -> c
|
|
|
|
Multiplicative disjunction is by far the least intuitive operator, corresponding
|
|
to delimited continuations and functions.
|
|
|
|
Bottom does not exist. Like with additive disjunction, it's just part of
|
|
the type signature, and it simply exists to not get chosen.
|
|
|
|
As you can see, |e produces only one c despite having two options, both
|
|
of which are provided in |i. That's because |e is inherently
|
|
nondeterministic. |i and |e are both entirely weakened because neither
|
|
choice will necessarily be chosen, yet they both must exist in full. This means
|
|
the entire closure that they are in must be somehow weakened, since it's
|
|
not possible to dispose of a full value in both branches at once, because
|
|
that would constitute double use. The use of the * in the signature is to
|
|
emphasize the relationship to multiplicative conjunction and that behavior,
|
|
just as additive disjunction is defined in terms of additive conjunction.
|
|
|
|
A par value is implemented in terms of a pair of function calls. In the
|
|
eliminator, either function may be invoked, and when the value is used,
|
|
the eliminator may choose to forget the function halfway through and jump
|
|
to the other option instead, making it equivalent to a continuation, the
|
|
functional equivalent to a goto.
|
|
|
|
The main use of par is in the sole axiom of the excluded middle, which encodes
|
|
the equivalent to functions and delimited continuations. For reference, the type
|
|
of a function is defined as (a -> b = -a | b). Essentially, the axiom can't pull
|
|
a value of type A out of thin air, so it is forced to choose the -a branch.
|
|
-a is equivalent to a -> Bottom, and therefore can only be used by providing a
|
|
value of type a, so instead of actually providing that Bottom, the function
|
|
simply jumps to the branch that demanded an a in the first place and returns
|
|
that result. Functions are implemented the same way, only kinda in reverse:
|
|
it runs the side that claims to have an extra a it needs to dispose of, and then
|
|
once it gets that hole, it produces the a in the other branch and terminates.
|
|
Implicitly in either case the context is consumed, which is equivalent to the
|
|
function returning. As you can see though, these are simply different
|
|
perspectives on the same axiom.
|
|
|
|
If you don't get it, that's fine, because it's super confusing, and I don't
|
|
understand it either.
|
|
|
|
|
|
TYPECLASSES & MODULES
|
|
=====================
|
|
|
|
I haven't really figured out typeclasses. Basically I intend for them to be
|
|
existential types (via dependent sums maybe?), but I need to figure out how to
|
|
make it practical first.
|
|
|
|
Requirements:
|
|
* It must be possible to use the instanced values without having to unwrap the
|
|
class implementation manually first.
|
|
* It must be possible to use the function instances equally easily, and without
|
|
having to declare the names at each usage point.
|
|
* They must permit non-unique instances.
|
|
* Instances must be stackable, the outermost one being used unless the inner one
|
|
is exposed within one of the instance type-contextual manipulations (e.g. functor).
|
|
This is important especially since Copy should implement !, but it should be
|
|
possible to rc a copiable variable and have it behave as expected. Copy is
|
|
described in more detail in Mutation.
|
|
|
|
Modules are essentially the same thing as typeclasses, and have similar
|
|
requirements. The answer to both probably lies in the same place. Lightweight
|
|
modules that are as easy to use as typeclasses would be a major plus too.
|
|
|
|
I don't suspect this problem will be all that hard to resolve.
|
|
|
|
|
|
As far as ! and ? go, they are essentially typeclasses. @ below is a typeclass
|
|
too. Essentially, these are the introduction rules for ! and ?, though these
|
|
are not possible to derive without external functions (for obvious reasons),
|
|
and being typeclasses, it's not appropriate to give them introduction rules.
|
|
|
|
To specify a !, you need a class instance satisfying either the contraction rule,
|
|
or equivalently, the following:
|
|
!i : a -> !(() -> a)
|
|
|
|
To specify a ?, you need a class instance satisfying either the weakening rule,
|
|
or equivalently, the following:
|
|
?i : a -> ?(a -> ())
|
|
|
|
The reason these are essentially introduction rules is made more obvious if you
|
|
wrap the entire thing into a (-> !a) or (-> ?a) respectively.
|
|
|
|
|
|
MUTATION
|
|
========
|
|
|
|
@ means "mutable" to the compiler. Normal values are immutable, which means to
|
|
make changes, the entire object has to be deconstructed and reconstructed again.
|
|
Mutation allows direct modification to the data structure without concern.
|
|
Generally things are mutable by default, but this may not be the case with
|
|
external values, or internal values via pointers.
|
|
|
|
@ extends ?, which means a mutable pointer may be freed, but not !, so that
|
|
you cannot have multiple instances of a mutable pointer.
|
|
|
|
Losing mutation in favor of replication:
|
|
rc : @a -> !?a, introducing a reference-counted pointer
|
|
|
|
Some values may implement Copy, allowing deep duplication. All composite
|
|
structures may automatically derive Copy whenever both of their constituents
|
|
are !. Copy, of course, is an implementation of !, though using (rc) anyway
|
|
may be desirable for performance reasons, especially for highly composite
|
|
structures.
|
|
copy : Copy a => a -> @a
|
|
|
|
Generally, syntactic sugar should eliminate most of this crap such that the
|
|
programmer shouldn't have to deal with it most of the time.
|