PG announced his second Lisp variant: Bel. <http:/...
# thinking-together
w
PG announced his second Lisp variant: Bel. http://paulgraham.com/bel.html He’s taking an axiomatic approach to Lisp, as described here: https://sep.yimg.com/ty/cdn/paulgraham/bellanguage.txt?t=1570888282&amp;
❤️ 3
👍 2
d
Very nice!
v
I skimmed through it, still didn't understand is there any novelty
e
From my reading it is a back to basics Lisp. A very clever language from a great mathematician, but its over-reliance on recursion as the main power tool makes Lisp unsuitable for the future.
d
The power of Lisp has everything to do with the kind of underlying model it can be (and on that sense, it's inescapable). But it's not necessarily a good top-level model (aka "language" or "interface") for most things. But that's also true of programming language + code in general
d
I really like the idea of building up a full featured programming language from a small core. It's elegant. In theory, it means that creating a new from-scratch implementation of the language can be done quickly, with a small amount of code, since you only need to reimplement the core. I mostly associate this idea with Lisp and Forth: having a small, simple syntax makes it easier to have a small core.
s
I love seeing ambitious projects like this where the author is making the tool they’d like to use.
💯 1
b
Very cool! Also cool to see the shoutout to @Kartik Agaram ! Any thoughts you could share about Bel? "The code I wrote to generate and test the Bel source is written in Arc," So he was not writing Bel directly?
k
I actually asked the same question but didn't get an answer. I haven't seen Bel running either, just given feedback on early drafts of what you see.
I'd like to implement something like Bel in my machine code. With its emphasis on using little of the underlying platform it feels like a good fit for Mu. But I'm not sure when I'll get to that. I'm currently designing the C-ish level of the Mu stack.
👍 2
d
The minimalistic approach I use is like a Lisp/Scheme evaluator, but all operations are stored as functions (native if necessary), and the evaluator simply looks them up from a dictionary and invokes them. No operations are "built in" to the language
k
While the Bel interpreter shown here is built in itself, it only needs a subset of Bel. One suggestion I had was to show two interpreters, one for Bel0 and one for the full Bel. Implement both in Bel0. That would improve the exposition, I think.
👍 1
@Dan Cook Does your language have primitives that are not functions? Other languages have keywords. Lisp has special forms. Smalltalk has blocks. I suspect you can't avoid something like that.
d
Smalltalk is a bit different: the entire Smalltalk system is written in Smalltalk, all the way down. It's similar in this sense to C and Unix, where the C compiler and the operating system are written in C. I gather that the Smalltalk bootstrapping process involves a small, statically typed subset of Smalltalk with semantics similar to C, but I'm not familiar with the details. C was originally bootstrapped using a C compiler written in assembly language, but I bet nobody has bootstrapped C in that way since the 1970's.
k
It's not clear to me that Smalltalk is different. Since the substrate (machine code) isn't Smalltalk or C, it is by definition impossible that the entire system is written in Smalltalk all the way down. Even if nobody works on the bootstrap any more it's still worth spreading the knowledge about how the bootstrap works, precisely what subset of the language it uses, and how the semantics differ from the full language. Until we understand this we can't actually say how parsimonious Smalltalk's bootstrap is compared to other languages. (This may seem pedantic, but I think it's hugely important for the health of society: https://app.slack.com/client/T5TCAFTA9/C5T9GPWFL)
d
In a system like Bel, I gather that most Bel functions are written in Bel, but some Bel functions are 'primitive': they do not have source code written in Bel. In Smalltalk, nothing is primitive. The entire interpreter and virtual machine are written in Smalltalk. You can inspect the source code, you can debug it, you can change the way it works, using live coding within a running Smalltalk image. That's what's different, and that's what I meant by "written in Smalltalk all the way down".
k
@Doug Moen I see, you're right. It may be worth distinguishing between "built in Smalltalk" and "could be modified using Smalltalk". The latter property is indeed universal, and that's pretty rare. Maybe Self and Io share it, besides Smalltalk? My Wart had it as well. What I learned from it was that while it's cool to be able to modify plumbing all the way down, there are extremely subtle implications for correctness and performance that the environment as a whole isn't equipped to support debugging. And arguably couldn't possibly support. So it doesn't seem worth chasing or bragging about. Just stick to 5% or 1% that isn't introspectable. To do otherwise is status signalling at best.
Common Lisp supports replacing
if
with your own version. You have to crack open a package lock and whatnot, but you can do it. But trust me, you don't want to. The package lock is there for a reason.
d
@Kartik Agaram There are primitive values, lists, key-value objects, and native functions. The base operations are native, and other "functions" are just objects with "code", "args", and "context" properties. When an object-function is invoked, a new context (i.e. a lexical scope object) is created with the function's context as its parent, the arguments are set into that context, and then that context and the function's "code" are fed into the evaluator. However, when a function-object does not contain any "context" property, then the context of the caller is used as the parent context (aka dynamic scope). The practice is to embed such functions in calls to things like "if" -- which is very much like how SmallTalk blocks work. ["if", condition, {code:[...]}] So instead of macros, functions are used for code that might or might not be invoked. It's interesting to note that arrow-functions in JavaScript work similarly (i.e. they don't override "this" or "arguments", like other functions do)
s
@Kartik Agaram Io didn’t support rewriting the interpreter from within itself, but as it had no keywords and has code-as-data with a single AST node type, you can easily do a lot of dynamic things that may be difficult even in languages written in themselves (such as control structures).
💯 1
Taking on the responsibility of dealing with varied current and future machine architectures and OS conventions (calling, dynamic libraries, etc) seems like a lot of (never ending) effort that may not have much to do with what makes a language useful/interesting unless it’s goal is to be a systems language.
d
@Kartik Agaram I think we can separate the problematic "self brain surgery" method of changing the internals of a Smalltalk image, from the benefits of a language and environment that is able to fully self-represent and self-introspect itself. I think that your "status signalling" criticism applies only to the former. Here's a paper, "Bootstrapping Reflective Systems: The Case of Pharo", describing how Pharo solves the "self brain surgery" problem in Smalltalk while retaining the ability to fully describe itself and evolve itself. https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.452.6952&amp;rep=rep1&amp;type=pdf
👍 2
k
@Doug Moen I don't understand the distinction you're making. How can you change the lowest-level internals of a system without it being like brain surgery, whether self- or otherwise? There seems to be some border around what you mean by "fully self-represent" and "entire system is written", but I can't see where it is. But maybe this needs a separate thread in #C5U3SEW6A. It's not a huge disagreement that I feel a need to explore.
d
In Smalltalk, if you try to refactor critical low-level parts of the system, you can easily crash the system, because the code you are refactoring is running while you are performing the refactor. In Pharo, you accomplish this by making a copy of the system, and modifying the copy. It's described in the 2014 paper I linked. This paper mentions some limitations (eg, on making changes to how SmallInteger and the boolean values work), due to the original system and the copy sharing the same VM data structures and byte code format, but there's been a lot of new work on the Pharo bootstrapping system since then that I haven't read about yet. Maybe some of these limitations have been removed. It seems plausible that there is some boundary, but I don't know where the theoretical limits are.
🤔 1
I now see that one of the problems being researched here is how to upgrade a running software system without rebooting it. The 2014 "hazelnut" bootstrapping system for Pharo that I cited above has been replaced by the "espell" system, which is described as "a language runtime virtualization infrastructure".
k
Smalltalk definitely has a virtual machine, so it's not Smalltalk all the way down. That virtual machine was initially implemented manually for each machine. Squeak introduced a virtual machine written mostly in Smalltalk and generating C code. But in all cases, the VM is not part of the image and the running VM cannot be modified on the fly, unlike the Smalltalk classes built on top of it.
👍 1
d
@Konrad Hinsen Thanks for the clarification. I guess that the definition of a language primitive is: if you implement a primitive in the source language itself, you induce a meta-circular dependency on the language implementation. Non-primitives can be implemented in terms of primitives without creating this kind of circularity. Meta-circular definitions of language primitives are not just a party trick. They are necessary if you want to implement an operating system that runs on top of the bare metal, and if you also want to use that same language & operating system to develop newer versions of itself. The C compiler is written in C. @Steve Dekorte I agree with your sentiment. Implementing a language in itself, all the way down to the bare metal, is possible, but then you take on the job of supporting all modern hardware, which is a huge commitment.
j
Nada Amin's "collapsing towers of meta-circular interpreters" might be of interest- https://www.cs.purdue.edu/homes/rompf/papers/amin-popl18.pdf
d
@jamii Thanks for the link, it's quite relevant to my project.
j
Can anyone explain the appeal of saying things like numbers are just a certain type of list? (Btw: I’ve done set theory and such, so the idea of such an encoding is very familiar, it just leaves me cold, in both mathematical and programming contexts).
d
This entertaining presentation of the paper "What about the natural numbers?" makes a case for treating natural numbers more like lists, in a programming context. It even provides a practical motivation for "lazy natural numbers".

https://www.youtube.com/watch?v=jFk1qpr1ytk

j
Thanks, I can’t watch a video right now, but I’ll try to have a look when I can.
d
@Justin Blank Here's the original paper, it's quite readable: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.56.3442&amp;rep=rep1&amp;type=pdf
Also, the Idris language works this way. The natural numbers are defined in the standard library as
data Nat = Z | S Nat
. https://www.idris-lang.org/example/
e
Mathematica being the only commonly used symbolic system, has arbitrary precision available. Excel, the most commonly used programming tool offers protected arithmetic. COBOL, the most derided tool still in use, offers BCD arithmetic, where 0.1 + 0.2 does equal 0.3, (which it does not in IEEE floating point). There are a lot of choices for arithmetic, and it is a very interesting, and fundamentally very important design decision for any language. In my Beads language i offer physical units of measurement which stores the exponents of the fundamental units along with the magnitude of a number, so that when you multiply kg times meters you have a quantity with mass^1 • length^1, and this is maintained at runtime, so as to support catching mistakes in units both at compile time and more importantly at runtime, which per Van Snyder of JPL is the 3rd most common error in engineering and scientific software. I recently announced this feature to the scientist subgroup of Reddit and got heaps of abuse from scientists who claimed that they never make units mistakes, and couldn't possibly tolerate the additional overhead of storing such things. I was quite surprised by the negative reaction to this very clever implementation of a desired feature from the 70's, and it has taught me that violent opposition to any fundamental change is going to happen. Offering too many kinds of numbers in languages (like INT8 INT16, INT32, INT64) is a hangover from the 70's when you spent a lot of time figuring out how many bytes each field should take, and carefully trimmed field sizes so as to take minimum space. Those days are long gone, and in the JS world, you only have FLOAT64. Too bad IEEE floating point is so stupid, as pointed out in the wonderful talks by Douglas Crockford on its many intrinsic flaws. It is very hard to fix this nasty problem because of interchange issues, where really we need to change all at once to a better format. That might not happen for another 20 years at the rate we are going.
k
Here's a scientist who openly admits having had to deal with unit problems many times, in my own code and in the code of others. In fact, the problem mostly comes from different people tacitly assuming different conventions, so people who use only code they wrote themselves are perhaps in a better situation. Calling IEEE FP stupid is a sign of... stupidity. A lot of careful thought has gone into that specification, which is a good fit for what it was designed for (scientific computing). People who barely understand it and use it for accounting get the punishment they deserve. The real problem with IEEE FP is that it is commonly presented (in programming tutorials) as "real numbers" making people believe in properties that IEEE FP doesn't have.
d
Douglas Crockford's issue is that you can't safely use binary floating point for financial calculations, you should use decimal FP instead. See <http://dec64.com/>. Crockford claims that his DEC64 format is more efficient than IEEE Decimal floating point. He also proposes that DEC64 should be "the only number type in the next generation of application programming languages". That makes sense for spreadsheets, and languages used for financial calculations, but not for everything. No need to wait 20 years, DEC64 is available now on github. DEC64 is a terrible idea for things like Machine Learning, and 3D graphics (both of which use GPUs). DEC64 would be a bad choice for my project, Curv, which is an end-user programming language for 3D graphics. For these applications, you need efficient binary floating point. IEEE binary floating point is an improvement over the non-IEEE floats found in some older GPU hardware: it has better numerical properties. What might be even better than IEEE FP, for ML and 3D, is "posit" floating point. For a given number of bits, it uses those bits more efficiently and represents a larger range of numbers which are distributed in a more effective way along the number line. Also, IMO, posit arithmetic has better numeric properties than IEEE. <https://posithub.org/docs/BeatingFloatingPoint.pdf>. Posits are not a drop in replacement for IEEE float (your code may need to change), and they aren't competitive unless implemented in hardware (unlike DEC64), so we may be waiting a while. They make the most sense for new ML hardware being built. For a language like Idris, where you use dependent types to prove programs correct, you need a natural number type, Nat, which implements the Peano axioms directly. You need this so that you can perform inductive proofs over the natural numbers. You need other numeric types as well (Nat doesn't include negative numbers or fractions). In systems languages like C, C++ and Rust, you need multiple numeric types like int8, int16, int32 and int64, so that you can write high performance code that optimizes memory consumption and memory access patterns and uses the L1 cache efficiently. Systems programming is not "a hangover from the 70's". If you want to design a programming language that has only a single numeric type, and is competitive in all of these niches, then I have bad news for you.
👍 1
j
Thanks for the link. I don’t find it particularly convincing, but it is interesting.