ARGON
Check-in [a79e82cbae]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Minor improvements to the CHROME intro
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a79e82cbae87c42d0001f66abdc1bbc131485e7b
User & Date: alaric 2018-01-06 11:11:47
Context
2018-01-31
23:15
Interesting paper on partial evaluation of interpreters check-in: cff8b86a04 user: alaric tags: trunk
2018-01-06
11:11
Minor improvements to the CHROME intro check-in: a79e82cbae user: alaric tags: trunk
2017-07-26
19:07
Subcontinuations paper check-in: b22e5f42be user: alaric tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to intro/chrome.wiki.

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
so functions close over their arguments.

Laziness can come with a performance cost, but the Haskell folks have
done lots of good work on strictness analysis. In exchange, you can
write conditional constructs without needing macros, streams, infinite
data structures, some performance <em>gains</em>, and easier semantics
for recursive expressions.
s
Uniqueness typing has <a href="http://home.pipeline.com/~hbaker1/Use1Var.html">many many
advantages</a>, so we'll
use that instead of nasty monads. But we can do monads too, when
they're useful.

Haskell-style typeclasses and generic functions are used to provide
polymorphism, rather than class- or prototype-based object
orientation.

Hygienic macros (with a module system based on the
[./carbon.wiki|CARBON] namespaces used for IRON symbols) are provided
for syntactic extensibility. However, they are "second-class values";
they are bound in the lexical environment just like functions and







|





|







41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
so functions close over their arguments.

Laziness can come with a performance cost, but the Haskell folks have
done lots of good work on strictness analysis. In exchange, you can
write conditional constructs without needing macros, streams, infinite
data structures, some performance <em>gains</em>, and easier semantics
for recursive expressions.

Uniqueness typing has <a href="http://home.pipeline.com/~hbaker1/Use1Var.html">many many
advantages</a>, so we'll
use that instead of nasty monads. But we can do monads too, when
they're useful.

Haskell-style typeclasses and generic functions are a nicen way to provide
polymorphism, rather than class- or prototype-based object
orientation.

Hygienic macros (with a module system based on the
[./carbon.wiki|CARBON] namespaces used for IRON symbols) are provided
for syntactic extensibility. However, they are "second-class values";
they are bound in the lexical environment just like functions and
70
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

144
145
146
147
148
149
150
metadata such as the position in the original input source code
expression (so error locations can be mapped back) and the lexical
environments in effect. Lexical environments are first-class objects;
in fact, they're CARBON knowledge bases containing name->value
bindings, type information, and other arbitrary declarations
(documentation, typeclasses, etc).

But for common cases, we'll implement <tt>syntax-rules</tt> style
rewriting macros as a library in terms of the core's low-level macros.


The language defines a range of primitives, for all the usual
arithmetic and other core functions that can't practically be
implemented otherwise. These primitives are what core language symbols
are bound to. They are a subtype of functions, in that they can be
applied directly, but unlike functions in general, they allow equality
comparison. A macro can tell if an application is a primitive, and
which one it is.

Bounded-space tail calls and first-class <a
href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.63.1754&rep=rep1&type=pdf">partial
continuations</a> provide for flow control, but I need to think more
about how that will interact with the uniqueness types. A continuation
may contain many closed-over unique values, so the continuation itself
<em>must</em> be a unique object. That's fine for implementing
threading with continuations, but what about <tt>amb</tt>, exceptions,
and so on?  Perhaps we need a means for capturing a non-unique
continuation that only works if the dynamic environment we close over
in the continuation contains no unique values (which can only be
checked at runtime, unless we constrain the language to make static
checking possible). Code that handles mutation by passing a chain of
unique values through it, but which contains try/throw/catch
exceptions, would need the <tt>throw</tt> to embed ALL unique values
currently in scope (but not in scope at the <tt>catch</tt>, which is
possible of the <tt>throw</tt> is within the lexical scope of the
<tt>catch</tt>) in the exception object to avoid leaving them in the
abandoned continuation; so invoking a continuation (and thus
abandoning the current continuation) would be forbidden if the current
continuation closed over unique values (and was therefore
unique). You'll need to explicitly destroy them, or pass them to the
new continuation.

There's no need for <tt>unwind-protect</tt> or <tt>dynamic-wind</tt>
in a pure language, thankfully.

Dynamically-scoped parameters are a boon in Scheme, so we'll have
them, too.

Static typing is important, as it can help to detect a wide range of
programming errors. However, it can't become a straitjacket. It's
useful in Scheme to be able to <tt>(read)</tt> arbitrary
s-expressions. Haskell's typeclass generics go a long way, but it
should be perfectly possible to have some bindings that we know
nothing static about - which are entirely dynamically typed -
alongside bindings with very narrowly defined static types, which can
be implemented fully unboxed in the compiled code, with everything
specialised, and type violations detected at compile time. So let's
have a type hierarchy that's a pointed complete partial order, giving
us an "Any" type we can use in those cases.

Dependant types are awesome, too. Let's have those while we're at
it. We're now wel into "Type system is Turing complete" territory, so
we'd better have function totality as part of the type system to keep
our type expressions halting.

<a href="https://www.idris-lang.org/">Idris</a> does a pretty epic job of making
such a type system work in practice, but I do find the Haskelly syntax
for types a bit obtuse at times; it's too pattern-matchy for me. The
way constraints are written in "design by contract" languages (eg,
Eiffel) are much more readable, to my eyes at least - and as far as I
can see, identical in expressiveness. Design by Contract and dependent
types are much the same idea, just expressed in different ways, so
let's bring them together; we can statically check the constraints
that are amenable to that, and leave anything else to run time
checking. It just becomes another aspect of gradual typing.


Currying is sometimes handy, but (I find) often conflates what's going
on; so let's not put currying in the core syntax. We'll still have
single-argument lambdas, but we'll pass tuples to them, thereby making
it practical to get at the tuple as a first-class value to implement
variadic functions. The tuples will normally be unique values, created
afresh, so the system will have little difficulty in keeping them off







|
|
>



















|
|
|
|
|
|
|
|
|
|
|
<




















|



|
|
|
|
|
|
|
|
|
|
>







70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
metadata such as the position in the original input source code
expression (so error locations can be mapped back) and the lexical
environments in effect. Lexical environments are first-class objects;
in fact, they're CARBON knowledge bases containing name->value
bindings, type information, and other arbitrary declarations
(documentation, typeclasses, etc).

But for simple cases, we'll implement <tt>syntax-rules</tt> style
template rewriting macros as a library in terms of the core's
low-level macros.

The language defines a range of primitives, for all the usual
arithmetic and other core functions that can't practically be
implemented otherwise. These primitives are what core language symbols
are bound to. They are a subtype of functions, in that they can be
applied directly, but unlike functions in general, they allow equality
comparison. A macro can tell if an application is a primitive, and
which one it is.

Bounded-space tail calls and first-class <a
href="http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.63.1754&rep=rep1&type=pdf">partial
continuations</a> provide for flow control, but I need to think more
about how that will interact with the uniqueness types. A continuation
may contain many closed-over unique values, so the continuation itself
<em>must</em> be a unique object. That's fine for implementing
threading with continuations, but what about <tt>amb</tt>, exceptions,
and so on?  Perhaps we need a means for capturing a non-unique
continuation that only works if the dynamic environment we close over
in the continuation contains no unique values (which can only be
checked at runtime, as it depends on the call chain). Code that
handles mutation by passing a chain of unique values through it, but
which contains try/throw/catch exceptions, would need the
<tt>throw</tt> to embed ALL unique values currently in scope (but not
in scope at the <tt>catch</tt>, which is possible if the
<tt>throw</tt> is within the lexical scope of the <tt>catch</tt>) in
the exception object to avoid leaving them in the abandoned
continuation; so invoking a continuation (and thus abandoning the
current continuation) would be forbidden if the current continuation
closed over unique values (and was therefore unique). You'll need to
explicitly destroy them, or pass them to the new continuation.


There's no need for <tt>unwind-protect</tt> or <tt>dynamic-wind</tt>
in a pure language, thankfully.

Dynamically-scoped parameters are a boon in Scheme, so we'll have
them, too.

Static typing is important, as it can help to detect a wide range of
programming errors. However, it can't become a straitjacket. It's
useful in Scheme to be able to <tt>(read)</tt> arbitrary
s-expressions. Haskell's typeclass generics go a long way, but it
should be perfectly possible to have some bindings that we know
nothing static about - which are entirely dynamically typed -
alongside bindings with very narrowly defined static types, which can
be implemented fully unboxed in the compiled code, with everything
specialised, and type violations detected at compile time. So let's
have a type hierarchy that's a pointed complete partial order, giving
us an "Any" type we can use in those cases.

Dependant types are awesome, too. Let's have those while we're at
it. We're now well into "Type system is Turing complete" territory, so
we'd better have function totality as part of the type system to keep
our type expressions halting.

<a href="https://www.idris-lang.org/">Idris</a> does a pretty epic job
of making such a type system work in practice, but I do find the
Haskelly syntax for types a bit obtuse at times; it's too
pattern-matchy for me. The way constraints are written in "design by
contract" languages (eg, Eiffel) are much more readable, to my eyes at
least - and as far as I can see, identical in expressiveness. Design
by Contract and dependent types are much the same idea, just expressed
in different ways, so let's bring them together; we can statically
check the constraints that are amenable to that, and leave anything
else to run time checking. It just becomes another aspect of gradual
typing.

Currying is sometimes handy, but (I find) often conflates what's going
on; so let's not put currying in the core syntax. We'll still have
single-argument lambdas, but we'll pass tuples to them, thereby making
it practical to get at the tuple as a first-class value to implement
variadic functions. The tuples will normally be unique values, created
afresh, so the system will have little difficulty in keeping them off
209
210
211
212
213
214
215
216

217
218
219
220
221
222
223
environments are eliminated as we move down the expression. When an
application turns out to be a macro application, the macro is applied
to the body syntax object, and the result (with a possibly modified
environment, if the macro chose to do so) replaces the subexpression
for further expansion. Macros may recursively invoke the expander on
their arguments, in order to manipulate the post-expansion code; if
the input to the expander had no free variables then this will be
fully reduced to the kernel language, but otherwise, it may only be

partially reduced (and the expansion of the result of the macro will
ensure that it is fully reduced). Where a macro creates new syntax
objects afresh, they will inherit the environment and location path
from the macro application syntax object; where it substitutes values
into a template defined in the macro body, then the nodes in that
template will keep their environment and location path from the macro
definition; and nodes passed through from the macro call will, of







|
>







210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
environments are eliminated as we move down the expression. When an
application turns out to be a macro application, the macro is applied
to the body syntax object, and the result (with a possibly modified
environment, if the macro chose to do so) replaces the subexpression
for further expansion. Macros may recursively invoke the expander on
their arguments, in order to manipulate the post-expansion code; if
the input to the expander had no free variables then this will be
fully reduced to the kernel language of lambdas and applications of
primitives and those lambdas to things, but otherwise, it may only be
partially reduced (and the expansion of the result of the macro will
ensure that it is fully reduced). Where a macro creates new syntax
objects afresh, they will inherit the environment and location path
from the macro application syntax object; where it substitutes values
into a template defined in the macro body, then the nodes in that
template will keep their environment and location path from the macro
definition; and nodes passed through from the macro call will, of