S-Juvix
Juvix
juvix-meeting-notes
Juvix as a language is type theoretic in nature. In particular, it
wishes to leverage dependent type theory to make safe and fast
validity-predicates. In order to properly achieve this goal, it is
important to make a high level language that is fit for the domain. Thus
the following properties become of the utmost importance for Juvix.
Points 1.
and 2.
are mostly covered by the dependent type theory,
with parts of 2.
being an implementation detail of the QTT
logic
with practical compromises in the IR to allow impure data structures.
Even point 3.
is somewhat satisfied if we ignore the boilerplate that comes
in trying to use Haskell/Idris style abstractions when it comes to
validity predicates. (See the link as to where this falls apart quite a
bit)
However, as we begin to write an implementation with compiling to a type
theoretic IR
, we have noticed a lot of trouble in development
The lack of Core being able to extend itself, leaves the requirement
for a more complex front pass
There are only a couple of ways we can do this in Haskell
Tagless Final
to make changing a big AST lessTrees that Grow
to simulate small changes within ann
changes inThe issue with 1)
is that it incurs a code cost of the length
of the structure you are working over. This means that for any
unchanged part of the syntax, we must map it to itself recursing
down.
The issue with 2)
is that it becomes O(n)
(find link to
oleg's FTP site) to do any transformation which requires
matching on arguments. Along with complications of forming the
observation
type in Haskell, make it a less compelling case
than other options.
The issue with 3)
is that it only automates forming the
data-type itself, not any pass. Thus to make a transformation,
it takes as much code as 1)
. With 5)
the situation may
improve but we will discuss it when we discuss 5)
.
The issue with 4)
is that we now have to bundle n
individual
passes into one macro pass. This greatly simplifies the issues
with the other passes, as we have very fixed layers of data
types that we can scrutinize, however this leaves any proving or
reasoning about the passes themselves much harder.
The issue with 5)
is that we are leveraging on a system that
splits information evenly at the type level and the term level.
We can demonstrate this phenomenon by using to
and from
directly to see the representation this would take even for some
very basic terms.
Here we can see that for the constructor Other
, we have 3 L1
structures and two M1
structures wee have to get rid of before
we can get to the data. Worse if we look at the type level, we
can see that the entire ADT is reserved through this. So if we
wanted to create a pass that got rid of the boilerplate,
translating from one ML data type to another, we can not just
call to
and from
as the types are incompatible, even if we
changed the subset of cases we wish to change. Thus we'd have
to reconstruct the M1's
and L1's
to get back a proper typed
representation. This means that we have to compute on the Rep
representation of the type during the pass, calling to
once
all cases have been run, effectively ending up with the same
amount of boilerplate at 1.
One approach that might work is combining this with 3)
to
properly generate these transformations. However this would take
more template Haskell to properly experiment with.
The issue with 6)
is that we've expressed the passes as a
bona fide macro-expansion, therefore proving that they can
indeed be done within a language. Thus instead of working on
getting the language to a state where it can support such
expressions and centralizing work upon this effort, we put extra
complexity on getting a large frontend language to meetup with
the small core. Thus, infrastructure becomes an issue as we need
proper tools to properly conduct these transformations.
There is a large disconnect between Core and an ergonomic frontend
puts extra cost upon the transformation.
Appendix A
we can see that Core expresses a total of 36What Core outputs is not an effective compilation target.
We can see this in that many high level ideas go through core
such as
Lambdas
Pattern Matching in global function definitions
List style records (not put together in an array
representation for compilation)
Deconstructors expressed in terms of higher order functions.
What this means is that we have a core language that then
requires yet another series of passes to properly compile to a
suitable backend.
In order to tackle these complexities to development and to uphold the
ideals of Juvix Ι propose the language that will become Juvix in time,
S-Juvix.
S-Juvix is an un-typed Lisp with the following properties:
point 2.
combined with 1.
covers the awkwardness of making a
compiler for it, while also satisfying fast output code.
Point 3.
is a direct answer to the complexity issues we've faced in
development, while also pushing for the ability to make ergonomic
contract code.
Lastly, point 4.
is of special importance as this is what will allow
the language to be typed by any direction what our type theory takes us.
It further lets our type theory try out various ideas without throwing a
wrench in the rest of the compiler.
With the preface to the language lay out, let us introduce the full
language standard
S-Juvix is a rather standard low level lisp, offering very little
builtin definitions. Below we will briefly describe all components of
the base language. For a more complete specification, view Appendix B
.
There are of course more functions (mainly in regards to manipulating
builtin data-structures), however we can notice that the language itself
is quite small. Even the given base forms are quite low level, with the
def
forms lacking even optional arguments to take!
Thus to make a function that takes any amount of arguments, we'd have
to write.
Which is not very ergonomic, as our main way to define a function is not
able to take any arguments, we can derive it with the functions derived
in Appendix D
.
Now, we can see that the factorial function is defined like we'd expect
in a high level language.
Even the function/macro defmacro
is defined from macro
which is
defined from def
. Due to how this language can grow, we can from a
small core, derive a full blown ergonomic high level language.
Typing is traditionally done by making sequent calculus rules and
deriving the syntax one wants from there. Likewise we can apply typing
rules upon the primitives of this language. Thus if we can do this, then
all that remains to be seen, is how would we extend the language with
enough information to support explicit declarations and places for the
typechecker to insert type information.
Thus I present one example strategy for including type-signatures with
global definitions
In this example, we abuse the fact that each global definition has a
meta-data slot, we use this to refer to the new signature. We also
define the signature as a normal declaration. We can see an example
usage here
Thus, the syntax of the language can be extended properly with a typing
system that Juvix Core will manipulate.
More examples of this kind of extension can be found in Appendix C.
Appendix D
to give us enough definitions to