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: |
a79e82cbae87c42d0001f66abdc1bbc1 |
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
Changes to intro/chrome.wiki.
︙ | ︙ | |||
41 42 43 44 45 46 47 | 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. | | | | 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 | 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). | | | > | | | | | | | | | | | < | | | | | | | | | | | > | 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 | 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 | | > | 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 |
︙ | ︙ |