1. What problem does this language solve? How can I make it precise?
2. How can I show it solves this problem? How can I make it precise?
3. Is there another solution? Do other languages solve this problem? How?
What are the advantages of my solution? of their solution?
What are the disadvantages of my solution? of their solution?
4. How can I show that my solution cannot be expressed in some other language?
That is, what is the unique property of my language which is lacking in
others which enables a solution?
5. What parts of my language are essential to that unique property?
From a skim of the Bel materials, I couldn't answer these questions. Maybe PG or someone else can take a stab at the answer?
Lisp dialects have as a rule been good at making programs short. Bel is meant to do the same sorts of things previous dialects have, but more so.
It's also meant to be simple and clear. If you want to understand Bel's semantics, you can read the source.
Making bel.bel shorter was one of my main goals during this project. It has a double benefit. Since the Bel source is a Bel program, the shorter I can make it, the more powerful Bel must be. Plus a shorter source is (pathological coding tricks excepted) easier to understand, which means Bel is better in that way too. There were many days when I'd make bel.bel 5 lines shorter and consider it a successful day's work.
One of the things I found helped most in making programs shorter was higher order functions. These let you get rid of variables, which are a particularly good thing to eliminate from code when you can. I found that higher order functions combined with intrasymbol syntax could often collapse something that had been a 4 line def into a 1 line set.
Statements such as these are very academic and concerning - to me - when it comes to new languages. And they make me wary of whether or not the language will ever move out of the land of theory and into practice.
Continuing with the "strings are cons lists" theme... Other, very notable languages have tried this in the past: Erlang and Haskell immediately come to mind. And - without exception - they all end up regretting that decision once the language begins being used for real-world applications that require even a moderate level of performance.
Lisp programmers (among which I count myself) are very fond of pointing to new languages and identifying all their "features" that were implemented in Lisp decades ago (so not "new"). And they also bemoan when a language design doesn't take a moment to learn from the mistakes of those that came before them. Strings as lists is very much in the latter case.
The above said, the idea of streams as a fundamental type of the language (as opposed to a base class, type class, or what-have-you) is quite intriguing. Here's hoping they are more than just byte streams.
The reality of 2019 is that strings are not random access objects -- they are either empty or composed of the first char and the rest of the chars. A list is the proper primary abstraction for strings.
That is, if "list" is not a data structure but a mathematical concept -- based on the concept of "pair." If I were a Clojure or Swift programmer -- and I'm both, I'd say there are protocols that embody Listness and Pairness, and an implementation would have multiple dispatch that allows an object to deliver on the properties of those protocols. There are other fundamental concepts, though, that deserve inclusion in a language.
Is something suitable as the first argument of `apply`? Functions obviously are, but to Clojure programmers, hash tables and arrays are (and continuations are to Schemers) just as function or relation-like as a procedure. This is so obviously a good idea to me that I am irritated when a language doesn't support the use of random-access or dictionary-like collections as function-like objects.
Which brings us to random-access and dictionary-like objects. Those should have protocols. And given that sets are 1) not relations and 2) are incredibly important, set-like and perhaps bag-like itself deserve support.
At minimum, a designer should think through the protocols they want to support in a deep way and integrate those into the language. Maximally, protocols would be first-class features of a language, which is what I'd prefer, because a protocol-driven design is so often so much more productive than an OO one.
PG's plans with respect to all of the above are what really interest me. IIRC Arc embodied some of these concepts (arrays-as-functions), so at the moment I'm content to watch him climb this ladder that he's building as he goes and see what comes of it.
[Edit: various typos and grammatical errors.]
Why is it preferable to couple the internal implementation of strings to the interface “a list of characters”? Also, since “character” can be an imprecise term, what is a character in Bel?
Exactly. How is unicode and UTF-8 treated by this language?
A good way to do it IMO, is for each character to be unicode grapheme cluster.
0. "This is not a language you can use to program
computers, just as the Lisp in the 1960 paper wasn't."
I would agree. However, for the sake of argument,
The width of a grapheme cluster depends on whether something is an emoji or not, which for flags, depends on the current state of the world. The combining characters (u)(k) are valid as one single grapheme cluster (a depiction of the UK's flag), which is not valid if the UK splits up, for example. This is only one single example, there are many others, that demonstrate that there is basically no 'good' representation of 'a character' in a post-UTF8 world.
The Bel spec contains the following:
A list of all characters. Its elements are of the form (c . b), where
c is a character and b is its binary representation in the form of a
string of \1 and \0 characters.
Also, Bel does have numbers, apparently implemented as pairs. Go figure.
But I am trying to read this in the spirit in which it's intended, as irreducible axioms. And so I don't think it's true that a character needs a representation from the perspective of Bel.
Consider if pg had approached this more like I would have, and started with integers and not characters as a built-in. Does an integer need a "representation"? At least, in a language that is not designed to execute programs in, like Bel? An implementation (with the additional constraints like width, BCD vs two's complement vs whatever, radix, etc) would need one, but the formal description would not.
Thanks for exploring the subject.
Can I ask why you claimed that it didn't have numbers or characters? It seems an odd thing to claim without actually having that knowledge in the first place.
Yes, well, until some limit, when the code becomes harder and harder and eventually too hard and concise, for humans to easily understand? :- )
I wonder if humans sometimes understand better, read faster, with a little bit verbosity. I sometimes expand a function-style one-liner I wrote, to say three imperative lines, because, seems like simpler to read, the next time I'm at that place again. — I suppose though, this is a bit different from the cases you have in mind.
Best wishes with Bel anyway :- )
How do Bel's higher order facilities compare to Haskell's? E.g. https://wiki.haskell.org/Pointfree
* How debuggable is it?
* Do most errors get caught at compile time, or do they require that code path to be exercised?
* How understandable are programs to new people who come along? To yourself, N years later?
* How error-prone are the syntax and semantics (i.e. how close is the thing you intended, to something discontinuous that is wrong, that won't be detected until much later, and that doesn't look much different, so you won't spot the bug)?
* How much development friction does it bring (in terms of steps required to develop, run, and debug your program) ... this sounds like a tools issue that is orthogonal to language design, but in reality it is not.
* What are the mood effects of programming in the language? Do you feel like your effort is resulting in productive things all the time, or do you feel like you are doing useless busywork very often? (I am looking at you, C++.) You can argue this is the same thing as programs being shorter, but I don't believe it is. (It is not orthogonal though).
* What is your overall morale of the code's correctness over time? Does the language allow you to have high confidence that what you mean to happen is what is really happening, or are you in a perpetual semi-confused state?
I would weigh concision as a lower priority than all of these, and probably several others I haven't listed.
I'm not sure how true that statement is, but my experience so far suggests that it is not only true a lot of the time, but that its truth is part of a more general pattern extending even to writing, engineering, architecture, and design.
As for the question of catching errors at compile time, it may be that there are multiple styles of programming, perhaps suited to different types of applications. But at least some programming is "exploratory programming" where initially it's not defined whether code is correct because you don't even know what you're trying to do yet. You're like an architect sketching possible building designs. Most programming I do seems to be of this type, and I find that what I want most of all is a flexible language in which I can sketch ideas fast. The constraints that make it possible to catch lots of errors at compile time (e.g. having to declare the type of everything) tend to get in the way when doing this.
Lisp turned out to be good for exploratory programming, and in Bel I've tried to stick close to Lisp's roots in this respect. I wasn't even tempted by schemes (no pun intended) for hygienic macros, for example. Better to own the fact that you're generating code in its full, dangerous glory.
More generally, I've tried to stick close to the Lisp custom of doing everything with lists, at least initially, without thinking or even knowing what types of things you're using lists to represent.
I agree that there is such a thing as writing a program that is only intended to be kind of close to correct, and that this is actually a very powerful real-world technique when problems get complicated. But the assertion that type annotations hinder this process seems dubious to me. In fact they help be a great deal in this process, because I don’t have to think about what I am doing very much, and I will bang into the guardrails if I make a mistake. The fact that the guardrails are there gives me a great deal of confidence, and I can drive much less carefully.
People have expressed to me that functions without declared types are more powerful and more leveragable, but I have never experienced this to be true, and I don’t really understand how it could be true (especially in 2019 when there are lots of static languages with generics).
> language in which I can sketch ideas fast
Can I ask, what are you programs about?
And sketching ideas? Is it ... maybe creating software models for how the world works, then input the right data, and proceed with simulating the future?
I agree with this idea to a degree. However, there are limits to this "relation". Imagine a sophisticated software that compresses program sources. Let's say it operates on the AST level, and not on the byte level, since the former is a little bit closer to capturing software complexity, as you mentioned somewhere else. Now, I know that that's almost the definition of a LISP program, but maybe we can agree that 1) macros can easily become hard to understand as their size grows 2) there are many rituals programmers have in code that shouldn't get abstracted out of the local code flow (i.e. compressed) because they give the necessary context to aid the programmer's understanding, and the programmer would never be able (I assume) to mechanically apply the transformations in their head if there are literally hundreds of these macros, most of them weird, subtle, and/or unintuitive. Think how gzip for example finds many surprising ways to cut out a few bytes by collapsing multiple completely unrelated things that only share a few characters.
In other words, I think we should abstract things that are intuitively understandable to the programmer. Let's call this property "to have meaning". What carries meaning varies from one programmer to the next, but I'm sure for most it's not "gzip compressions".
One important measure to come up with a useful measure of "meaning" is likelihood of change. If two pieces of code that could be folded by a compressor are likely to change and diverge into distinct pieces of code, that is a hint that they carry different meanings, i.e. they are not really the same to the programmer. How do we decide if they are likely to diverge? There is a simple test, "could we write any of these pieces in a way that makes it very distinct from the other piece, and the program would still make sense?". As an aspiring programmer trying to apply the rule of DRY (don't repeat yourself) at some point I noticed that this measure is the best way to decide whether two superficially identical pieces of code should be folded.
I noticed that this approach defines a good compressor that doesn't require large parts of the source to be recompressed as soon as one unimportant detail changes. Folding meaning in this sense, and folding only that, leads to maintainable software.
A little further on this line we can see that as we strip away meaning as a means of distinction, we can compress programs more. The same can be done with performance concerns (instead of "meaning"). If you start by ignoring runtime efficiency, you will end up writing a program that is shorter since its parts have fewer distinctive features, so they can be better compressed. And if the compressed form is what the programmer wrote down, the program will be almost impossible to optimize after the fact, because large parts essentially have to be uncompressed first.
One last thought that I have about this is that maybe you have a genetic, bottom-up approach to software, and I've taken a top-down standpoint.
Isn't that the definition of a compiler?
Busting a gigantic nut when I see a blank file
Many other languages have been introduced here (e.g. Hy) that actually solve new problems or have a deeper existential reasons, not just to "shorten stuff".
Sure, I'm not going to start programming in Bel but the thought processes in the design document are definitely something I could learn a thing or two from.
Adding s-expression syntax to Python solves an important program?
If a language compresses conceivably-desirable programs to a smaller AST size, then for all N, a higher fraction of size-N ASTs (compared to other languages) represent conceivably-desirable programs.
So "make your programs shorter" also implies "make bad behaviors impossible or extra-verbose to write". For example, failing to free your memory is bad code, and it's impossible(ish) to write in managed-memory languages.
It reminds me "The Zen of Python"
"There should be one — and preferably only one — obvious way to do it"
do you think you could find time make a table of content ? I like .txt files but a language spec is just a tad too long.
Or maybe for the lispers around have a short summary of what was inspiring you to make bel after arc ? what were your ideas, problems (beside making programs more expressive shorter).
A parse tree is not particularly comparable between languages; most modern languages make extensive use of powerful runtimes to handle complex things that are part of the OS. There is typically over a million lines of code behind a simple text entry field.
In my Beads language for example, i go to great lengths to allow declarations to have a great deal of power, but they are not executable code, and thus have hardly any errors. Lisp is not a particularly declarative type of language and is thus more error prone. The more code you don't execute the more reliable the software will be, so declarative programming is even better than Lisp.
Lisp derivative languages are notorious for their poor readability; hence the avoidance of Lisp by companies who fear "read-only" code bases that cannot be transferred to a new person. Lisp, Forth, APL, all win contests for fewest characters, but lose when it comes to the transfer phase. But like a bad penny, Lisp keeps coming back over and over, and it always will, because 2nd level programming, where you modify the program (creating your own domain specific language basically for each app), is the standard operating methodology of Lisp programmers. That one cannot understand this domain specific language without executing the code makes Lisp very unsuitable for commercial use.
Yes there are a few notable successful commercial products (AutoCad) that used Lisp to great results, but for the next 5-10 million programmers coming on board in the next few years, I laugh at anyone with the audacity to imagine that Lisp would be remotely suitable. Lisp is an archaic language is so many ways. It cannot run backwards. It has no concept of drawing; it is firmly rooted in the terminal/console era from which it sprang. With no database or graphics or event model, you have to use API's that are not standardized to make any graphical interactive products, which is what the majority of programmers are making.
Is it though?
I find at least 2 other languages more unreadable.
- Scala, with operators everywhere, you need to have a cheat sheet
- Perl, you know that joke that this is the only language that looks the same before and after RSA applied on it?
Lisp, on the other hand, is pretty readable, at least to me. I only used Clojure from the Lisp family and it had a great impact on how I think and write code in other languages. The result is more readability. For a long time MIT tought CS courses using Lisp and SICP is also a pretty amazing read.
MIT has replaced Lisp with Python, because even though they pushed forced it upon their students for decades they had to admit Lisp was archaic and not particularly readable. The Pharo IDE is arguably the most sophisticated IDE around today, but the fact remains that Lisp doesn't permit easy interchangeable parts, which is a major goal of the new programming languages being developed. Although very bright people can get very good at Lisp, the average person finds it extremely hard. Remember you are reading it from the inside-out which is highly unnatural to someone who reads books which read left-to-right.
So ignoring AST/character measures of shortness; is perl reaching optimality on the noise-to-meaning ratio?
Am I allowed to say this makes perl even more meaningful than other languages?
I'm not especially serious, but in the spirit of my own stupidity, let me suggest that this is how, finally, painters can truly be hackers
Words are called 'symbols' in Lisp. Making extremely expressive domain level constructs is reducing the code size a lot in larger Lisp programs.
> Lisp is not a particularly declarative type of language
Just the opposite. Lisp is one of the major tools to write declarative code. The declarations are a part of the running system and can be queried&changed while the program is running.
> Lisp ... all win contests for fewest characters,
Most Lisp since the end 70s don't care about character count. Lisp usually uses very descriptive symbols as operator names. Lisp users invented machines with larger memory sizes in the late 70s, to get rid of limitations in code and data size.
Paul Graham is favoring low-character count code, but this is not representative for general Lisp code, which favors low-symbol count code through expressive constructs.
> understand this domain specific language without executing the code
the languages will be documented and will be operators in the language. Other programming languages also create large vocabularities, but in different ways. Lisp makes it easy to integrate syntactic abstractions in the software, without the need to write external languages, which many other systems need to do.
For example one can see the Common Lisp Object System as a domain-specific extension to write object-oriented code in Lisp. It's constructs are well documented and widely use in Lisp.
> it/is firmly routed in the terminal/console era...
Use of a Lisp Machine in the 80s:
Interactive graphical systems were early used in Lisp since the 70s. There is a long tradition of graphical systems written in Lisp.
For example PTC sells a 3d design system written with a C++ kernel and a few million lines of Lisp code:
I wonder if psychology in this topic could inform programming language design as to a “natural” step-size of abstraction, or an optimal parse-tree measure which would correspond to an equivalent mental model.
Here is the relevant parts from the language document:
> Bel is an attempt to answer the question: what happens if, instead of switching from the formal to the implementation phase as soon as possible, you try to delay that switch for as long as possible? If you keep using the axiomatic approach till you have something close to a complete programming language, what axioms do you need, and what does the resulting language look like?
> I want to be clear about what Bel is and isn't. Although it has a lot more features than McCarthy's 1960 Lisp, it's still only the product of the formal phase. This is not a language you can use to program computers, just as the Lisp in the 1960 paper wasn't. Mainly because, like McCarthy's Lisp, it is not at all concerned with efficiency. When I define append in Bel, I'm saying what append means, not trying to provide an efficient implementation of it.
He believes languages are utterly defined by their type systems, saying in the LtU comment you linked: "Perl, Python, Ruby, PHP, Tcl and Lisp are all the same language". I'd assume that he would say the same about js, lua, etc.. AFAICT, he's quite knowledgeable about PLT, formal methods, static typing, category theory, etc., but he disregards
I'm not really interested in learning yet another dynlang that is missing all those.
This is not easy. The solution is certainly computable in other languages, and expressivity is subjective.
We can quantify it like this: a solution is highly expressive if it is given in terms of elements mostly from the problem domain.
For instance, if we have to "malloc" some memory and bind it to a "pointer", but the problem domain is finance, those things don't have anything to do with the problem domain and are therefore inexpressive.
Even if we quantify expressivity, the proposition that "this has the best expressivity for a given problem domain than all other tools, known and unknown" is mired with intractability.
> Evaluates x. If its value comes from a pair, returns a list of that
pair and either a or d depending on whether the value is stored in
the car or cdr. Signals an error if the value of x doesn't come from
> For example, if x is (a b c),
> > (where (cdr x))
> ((a b c) d)
That is one zany form.
1. How is this implemented?
2. What is the use of this?
3. What does (where x) do if x is both the car of one pair and the cdr of another, eg. let a be 'foo, define x to be (join a 'bar), let y be (join 'baz a), and run (where a).
> (set x '(a b c))
(a b c)
> (set (2 x) 'z)
(a z c)
> (set ((if (coin) 1 3) x) 'y)
(y z c)
Thanks for the new dialect!
Still knee-deep in the source. Gonna steal some of this.
What made you settle on (coin), is that a LISP trope? I flopped back and forth between naming it (coin) and (flip) in my own LISP before finally settling on (flip). I'd honestly like to divorce the name entirely from its physical counterpart.
(def flip (f)
(fn args (apply f (rev args))))
How about (bit) or (randbit)
where tells you what to set, and setting the cadr of (a b c) means setting the car of (b c).
When you say,
> But I also believe it will be possible to write efficient implementations based on Bel, by adding restrictions.
I'm having trouble picturing what such restrictions would look like. The difficulty here is that, although you speak of axioms, this is not really an axiomatic specification; it's an operational one, and you've provided primitives that permit a great deal of introspection into
that operation. For example, you've defined closures as lists with a particular form, and from your definition of the basic operations on lists it follows that the programmer can introspect into them as such, even at runtime. You can't provide any implementation of closures more efficient than the one you've given without violating your spec, because doing so would change the result of calling car and cdr on closure objects. To change this would not be a mere matter of "adding restrictions";
it would be taking a sledgehammer to a substantial piece of your edifice and replacing it with something new. If closures were their own kind of object and had their own functions for introspection, then a restriction could be that those functions are unavailable at runtime and can be only be used from macros. But there's no sane way to restrict cdr.
A true axiomatic specification would deliberately leave such internals undefined. Closures aren't necessarily lists, they're just values that can be applied to other values and behave the same as any other closure that's equivalent up to alpha, beta, and eta conversion. Natural numbers aren't necessarily lists, they're just values that obey the Peano axioms. The axioms are silent on what
happens if you try to take the cdr of one, so that's left to the implementation to pick something that can be implemented efficiently.
Another benefit of specifying things in this style is that you get much greater concision than any executable specification can possible give you, without any loss of rigor. Suppose you want to include matrix operations in your standard library. Instead of having to put an implementation of matrix inversion into your spec, you could just write that for all x,
(not (is-square-matrix x))
(= (* x (inv x))
(id-matrix (dim x))))
If you do stick with an operational spec, it would help to actually give a formal small-step semantics, because without a running implementation to try, some of the prose concerning the primitives and special forms leaves your intent unclear. I'm specifically puzzling over the `where` form, because you haven't explained what you mean by what pair a value comes from or why that pair or its location within it should be unique. What should
(where '#1(#1 . #1))
Representing code as linked lists of conses and symbols does not lead
to the fastest compilation speed. More generally, why should the
language specification dictate the internal representation to be used by
the compiler? That's just crazy! When S-expressions were invented in
the 1950s the idea of separating interface from implementation was not
yet understood. The representation used by macros (and by anyone else
who wants to bypass the surface syntax) should be defined as just an
interface, and the implementation underlying it should be up to the
compiler. The interface includes constructing expressions, extracting
parts of expressions, and testing expressions against patterns. The
challenge is to keep the interface as simple as the interface of
S-expressions; I think that is doable, for example you could have
backquote that looks exactly as in Common Lisp, but returns an
<expression> rather than a <cons>. Once the interface is separated from
the implementation, the interface and implementation both become
extensible, which solves the problem of adding annotations.
This paragraph contributed a lot to my understanding of what "separating interface from implementation" means. Basically your comment is spot on. Instead of an executable spec, there should be a spec that defines as much as users need, and leaves undefined as much as implementors need.
This seems to me like the proper way to have "separation of interface from implementation" and "everything is a list" at the same time. Yeah, everything is an (interface) list, but not necessarily an (implementation) list.
Typing can also be fine grained. So things can narrowly describe what they provide, and algorithms what they require. "I require a readable sequence, with an element type that has a partial order, and with a cursor supporting depth-one backtracking".
Object identity can also be decoupled from type. Ruby's 'refinements' permits lexically scoped alternative dispatch, so an object can locally look like something else. That, and just a few other bits, might have made the Python 2 to 3 transition vastly less painful - 'from past import python2'.
We are regrettably far from having a single language which combines the many valuable things we've had experience with.
I guess you could question its sanity, but the obvious way would be to say that any object (cons cell) produced by make-closure or (successfully) passed to apply is/becomes a "closure object", and car/cdr will either fail at runtime or deoptimise it into its 'official' representation.
> Some atoms evaluate to themselves. All characters and streams do, along with the symbols nil, t, o, and apply. All other symbols are variable names
The definitions of "ev" and "literal" establish that nil, t, o, and apply are in fact hardcoded and unchangeable. Did you consider having them be variables too, which just happen to be self-bound (or bound to distinctive objects)? nil is a bit of a special case because it's also the end of a list, and "(let nil 3 5)" implicitly ends with " . nil"; o might be an issue too (said Tom arguably); but apply and t seem like they could be plain variables.
P.S. It looks like you did in fact implement a full numerical tower—complex numbers, made of two signed rational numbers, each made of a sign and a nonnegative rational number, each made of two nonnegative integers, each of which is a list of zero or more t's. Nicely done.
HN: How do you get an intuitionistic understanding of computation itself? While Turing Machines kind of make sense in the context of algorithms, can I really intuitively understand how lambda calculus is equivalent to Turing machines. Or how Lambda Calculus can solve algorithms? What resources helped understanding these concepts?
I'm currently following http://index-of.co.uk/Theory-of-Computation/Charles_Petzold-... and a bunch of other resources in the hope I'll "get" them eventually.
My approach was to try and build a Lisp -> Brainfuck compiler. My reasoning was: Brainfuck is pretty close to a Turing machine, so if I can see how code that I understand gets translated to movement on a tape, I'll understand the fundamentals of computation.
It became an obsession of mine for 2 years, and I managed to develop a stack based virtual machine, which executed the stack instructions on a Brainfuck interpreter. It was implemented in Python. You could do basic calculations with positive numbers, define variables, arrays, work with pointers...
On one hand, it was very satisfying to see familiar code get translated to a large string of pluses and minuses; on the other, even though I built that contraption, I still didn't feel like I "got" computation in the fundamental sense. But it was a very fun project, a deep dive in computing!
My conclusion was that even though you can understand each individual layer (eventually), for a sufficiently large program, it's impossible to intuitively understand everything about it, even if you built the machine that executes that program. Your mind gets stuck in the abstractions. :)
So... good luck! I'm very interested to hear more about your past and future experiences of exploring this topic.
See this question that I asked (and later answered myself) around that time: https://softwareengineering.stackexchange.com/questions/2847...
Most of the project was figuring out things similar to that. You get to appreciate how high-level machine code on our processors really is! When things like "set the stack pointer to 1234" are one instruction, instead of 10k.
Specifically, I think that there exists a lisp with a set of axioms that split program execution into "compile-time" execution (facts known about the program that are invariant to input) and a second "runtime" execution pass (facts that depend on dynamic input).
For example, multiplying a 2d array that's defined to be MxN by an array that's defined to be NxO should yield a type that's known to be MxO (even if the values of the array are not yet known). Or if the first parameter is known to be an upper-triangular matrix, then we can optimize the multiplication operation by culling the multiplication AST at "compile-time". This compile-time optimized AST could then be lowered to machine code and executed by inputting "runtime" known facts.
I think that this is what's needed to create the most optimally efficient "compiled" language. Type systems in e.g. Haskell and Rust help with optimization when spitting out machine code, but they're often incomplete (e.g., we know more at compile time than what's often captured in the type system).
I've put "compilation" in quotes, because compilation here just means program execution with run-time invariant values in order to build an AST that can then be executed with run-time dependent values. Is anyone aware of a language that takes this approach?
Idris is not a Lisp and I've never used it, but it has dependent types (types incorporating values) and encodes matrix dimensions into the type system (I think only matrix multiplications which can be proven to have matching dimensions can compile). I think the dimension parameters are erased, and generic at runtime (whereas C++ template int parameters are hard-coded at compile time). IDK if it uses dependent types for optimization.
In fact, I think Julia is a great example of taking some good parts of scheme and building a more conventional (in terms of syntax anyway) language on top.
Common lisp macros? Pre-hygienic macros in scheme? Did I get you wrong?
The question is what is missing to implement _static_ type checking as macros and to allow the compiler to leverage the generated information.
Edit: Also, run-time independent evaluation would need to handle branching differently. For example, in this expression: (if a b c). If `a` is not known at "compile time" then this expression remains in the AST, and run-time independent value propagation continues into `b` and `c`. If `a` is known at "compile time" then only `b` or `c` remain in the AST depending on whether `a` is true or false.
I really like this approach and have wondered in recent years what a programming language designed with this approach would look like. There are a few that come close, probably including Haskell and some of the more obscure functional languages and theorem prover languages. Will be really interesting to see a Lisp developed under this objective.
>But I also believe that it will be possible to write efficient
implementations based on Bel, by adding restrictions. If you want a
language with expressive power, clarity, and efficiency, it may work
better to start with expressive power and clarity, and then add
restrictions, than to approach from another direction.
I also think this notion of restrictions, or constraint-driven development (CDD), is an important concept. PG outlines two types of restrictions above. The first is simply choosing power and clarity over efficiency in the formative stages of the language and all the tradeoffs that go with that. The second is adding additional restrictions later once it's more clear how the language should be structured and should function, and then restricting some of that functionality in order to achieve efficiency.
Reminds of the essay "Out of the Tarpit"  and controlling complexity in software systems. I believe a constraints-based approach at the language level is one of the most effective ways of managing software complexity.
Bel has four fundamental data types:
symbols, pairs, characters, and streams.
Then a bit further down it says:
(+ 1 2) returns 3
What am I missing?
(lit num (sign n d) (sign n d))
One nit with this definition is that it implies the car of a number would be a well-defined operation. For complex numbers, it would be natural for car to return the real component.
I admit, it was surprising you are defining numbers at all. It’s tricky to pin down a good definition that isn’t limiting (either formally or for implementations).
But I suspect that requiring that (car x) always be valid for all numbers might be much more tricky, in terms of performance.
I apologize if you have already explained that implementation isn’t a concern at all. I was just wondering if you had any thoughts for someone who is anxious to actually implement it.
EDIT: Is pi written as 31415926 over 1000000?
So cleverness in implementation won't translate into compliance, but rather into inventing declarations that programmers can use that will make their programs dramatically faster. E.g. if programmers are willing to declare that they're not going to look inside or modify literals, you don't have to actually represent functions as lists. And maybe into making really good programming tools.
As I said elsewhere, half jokingly but also seriously, this language is going to give implementors lots of opportunities for discovering new optimization techniques.
From a prototyping perspective it would be a waste of time and overly constraining to define what numbers are or what you can do with them. That’s best left to the implementation.
I can hear everyone groaning in unison with that, but trust me. Nowadays every implementation will choose some sensible default for numbers. If you transpile bel to JS, you get JS’s defaults. Ditto for Lua. But crucially, algorithms written for one generally work for the other.
In any case, I was mistaken. Numbers are defined.
The second thought is that lists smell like a type of stream. Successive calls to `next` on `rest` don't necessarily discern between lists and streams. The difference seems to be a compile time assertion that a list is a finite stream. Or in other words, a sufficiently long list is indistinguishable from an infinite stream (or generator).
I'm not sure you can have a lisp without lists, but they seem more like objects of type stream that are particularly useful when representing computer programs than a fundamental type. Whether there are really two fundamental types of sequences, depends on how platonic really really is.
All with the caveat, that I'm not smart enough to know if the halting problem makes a terminating type fundamental.
It's a little strange to have (id 'a) return nil. Also having car and cdr as historical holdouts when most of the naming seems to aim at an ahistorical stylistic.
Not very deep remarks, since I would need more time to digest.
Why Bel? What are the problems that Bel wants to solve? Is this an hobby project or something more serious?
Sounds serious to me. Why can't it be both?
> Don't be discouraged if what you produce initially is something other people dismiss as a toy. In fact, that's a good sign. That's probably why everyone else has been overlooking the idea. The first microcomputers were dismissed as toys. And the first planes, and the first cars. At this point, when someone comes to us with something that users like but that we could envision forum trolls dismissing as a toy, it makes us especially likely to invest.
so, lisp development came in two phases: formal phase -- that's where original 1960 paper described lisp from simple axioms and built on them -- and implementation phase -- formal step is of no use for us because it didn't have numbers, error handling, i/o, etc.
argument here is that the formal phase might be the most important phase, but that the second phase usually takes longer and is more practical. so what if one delays the second phase for as long as possible? what could be discovered and be useful for the implementation phase? that's the raison d'etre of bel i think.
i very much like it :)
- GLaDOS (Portal 2)
> (distinct-sorted '(foo bar foo baz))
(bar baz foo)
A language like Bel that does not have native hash maps or arrays and instead uses association lists would have to rely entirely on the compiler to find and perform these optimizations to be considered a usable tool.
The other concern I have is the lack of a literal associative data structure syntax (like curly braces in clojure) It seems that would negatively impact pg's goal of "code simplicity" quite a bit.
I'll note that if there are primitive arrays and the compiler optimizes arithmetic, the rest of the hash table can be implemented in Bel.
Also, maybe a Sufficiently Smart Compiler could prove that a list's cdrs will never change, store it in cdr-coded form, and treat it like an array (with the ability to zoom right to, say, element 74087 without chasing a bunch of pointers).
I hope I'll never stop coding passion projects myself.
John Carmack talked about this on Joe Rogan's show recently, about how he still codes and how Elon Musk would like to do more engineering but hasn't much time.
I wonder if Bill Gates ever codes anything anymore, I emailed to him ask once but never got a reply.
Tim Sweeney is a billionaire and still knee deep in code.
It's Saturday night here, and I'm going to go write some code. Unproductive, unprofitable, beautiful game engine code.
Hope all you other hackers get up to something interesting this weekend.
And often, he's way into whatever the latest language is. He builds games, software for organizing pills and bills, and has designed and rebuilt his own editor several times.
Honestly, the guy's my hero.
He makes (pretty cool) side-scrollers for this grandchildren, tools for himself. He sometimes complains that he's not sure what to build, but he figures it out. He also has a Mac and a Linux box (running Gentoo, I think). That keeps him busy!
He's written his own editor ("I want it the way I want it!") maybe four times in different languages, just because. He's always teasing me for being a vi user ("You're stuck in the 70s!")
That's awesome! I'd love to hear his thoughts :)
I'm coming up on 50, so all of my friends' parents are roughly the same age. The big crackup for me is that they're all worried about their parents falling for a phishing attack or having to update operating systems that haven't seen an update in ten years.
When I call my dad, he wants to get deep into compiler theory. Or talk about Rust. Or moon about how the world would have been so much better if Brendan Eich had just implemented Scheme. Or he'll talk about how much he loves TeX.
You know, he complains that he's not as sharp as he used to be. He's always warning me to take care of my body, because "mens sana in corpore sano!" But really, I just don't worry about him at all.
He's also one of the happiest people I know, and absolutely one of the most well read (I'm an English professor; I'm surrounded by well-read people. It's kind of amazing how many books he's read in his life).
Being a geek, he has his Apple watch set up to remind him to go walk around every hour, and that tends to keep it at bay. But he really can't work in the shop any more.
And honestly (just to brag on my dad a bit more) the way he said goodbye to all that struck me as really impressive. He loved woodworking, but he didn't weep and mourn or go into a depression when he couldn't do it anymore. He just shrugged and said, "Well, I guess it's on to my other hobbies!"
That's how I want to be.
It may be the reverse that happens here: he is wealthy enough to have time to play with Lisp.
For people making a living wage, some think “I don’t have the money to travel.” Then when they have the money, they think “I don’t have the time to travel.” They don’t really travel until they retire, and then they have trouble going anywhere that isn’t friendly to their knees and faltering eyesight, so they buy an RV.
Others travel in college from hostel to hostel. When they get work, they negotiate longer vacations to travel, or they deliberately become giggers so they can travel.
I met a couple in Thailand who climbed, they worked five months a year around the clock as nurses, and climbed seven months a year literally everywhere. They lived on the cheap so they would have more climbing days.
I have similar stories about people who ride bicycles and dive. If you are passionate, don’t wait for some magical day when you can fly the Concorde to go climbing in Europe with your friends (true story about some Yosemite dirtbags who came into illicit cash).
If you’re passionate about a thing, go do that thing. Now.
Does that work? I've tried to negotiate more vacation days with every job offer I've received, and it's never worked.
He got me a $5,000 raise, no extra vacation time. Said that he did his best and he was the type of person I'd absolutely believe.
Ever since then, in my mind, a vacation day has to be worth more than $1,000. Simple economics, right? Somehow giving me $5,000 maximizes shareholder value more than giving me 5 extra days off.
My next job was more generous with vacation. They used the accrual method with a maximum and there was an extended period where it had become difficult to take time off and they'd stopped putting our PTO balance on our paystubs... so at some point I track down where to find the info and realize I'd lost 10 days of vacation accrual.
I about lost my mind and HR couldn't understand why I felt like they'd cheated me out of more than $10,000.
Felt trapped in the job forever 'cause eventually I'm earning I think 27 vacation days per year, and 10 to start is pretty typical, maybe 15 if you're lucky... And as much as we might like to think everything is negotiable, reality on the ground is that very little besides salary can be successfully negotiated.
To negotiate more vacation days at hiring, you need multiple offers. Then you can tell them “I will accept your offer if it includes six weeks of vacation. Otherwise I’m going to Facebook”. It probably means negotiating a little less hard on pay since you need to focus to get it. But that’s probably fair.
I've used that exact line, and they didn't budge, so I went to Facebook. I guess everywhere I've applied is large tech companies. It might work better at smaller firms.
A vacation day is a paid thing. Unpaid time off is not. It's all money and productivity in the end, but in many companies it's easier to negotiate unpaid time off, than to negotiate extra vacation days. Even if the money works out the same in the end.
Perhaps it’s best, as another comment suggests, to always include the adjectives “paid” or “unpaid.”
Paid time off
Unpaid time off
Leave of absence
Quit and reapply when you get back.
Second would be maybe start asking about that when the offer letter comes in. At which point they are invested in you.
Google also has up to 5 weeks paid vacation in the US, starting at 3 weeks when you join. After 3 years, you move to 4 weeks, and after 5 years you max out at 5. Other countries basically have their government mandated time off rules, but 5 weeks plus unpaid plus holidays is pretty good.
Each week of unpaid time is thus about 2% of salary. That said, I'm definitely in the minority in using unpaid time off (though, even more strange for me is how many colleagues let their vacation accrual reach the maximum and even forfeit days...).
This is discussed in the Valley Uprising documentary as well (the whole doc is amazing and definitely worth your time).
On the other hand, John Long is a raconteur par excellence, and I recommend all of his books as entertaining reading about an important time in climbing history.
One thing Long wrote/claimed that is not mentioned in the linked post is that two of the dirtbags involved leveraged their haul into an ongoing drug-dealing business. Until they were found, shot dead, in their home.
That's the kind of thing people say because they assume it must be so. If you do that about unconventional things, which this software was/is, you'll simply reproduce the conventional view.
until you have enough money to make more money without adding much time..
I don't have any inside information, but looking at this old post of Joel Spolsky https://www.joelonsoftware.com/2006/06/16/my-first-billg-rev... I guess that Bill Gates is still coding.
Pretty much every evening and weekend, he's mucking around with something in FreeCAD and knee-deep in equations that I don't fully understand, because he genuinely loves this stuff, and doesn't want to ever become rusty.
He's 58 now, but I get the impression that he'll be doing this until his deathbed.
My takeaway from such stories is that Passion for work and keeping in touch with the path that lead them to success helps them stay relevant and successful.
Have fun with your unproductive beautiful coding :-)
He does (at least as of 2013): https://www.reddit.com/r/IAmA/comments/18bhme/im_bill_gates_...
Whilst I do extoll the virtues of working on "passion projects" with programming, sometimes you need to do something else so that you can go back recharged, and that's OK too.
Was this during bootstrapping, or is this still the case? Or in other words, do you now edit bel.bel directly or are there arc files you edit that compile into bel.bel?
I had to change Arc a fair amount to make this work.
Curiously, enough, though, I found doing development in Bel was sufficiently better that I'd often edit code in bel.bel, then paste a translated version into the file of Arc code, rather than doing development in the latter. This seemed a good sign.
... fast forward a decade later and I'm reading books on functional and logical languages for work. After the first chapter of The Little Schemer, I was at first blown away with the content, but then sad after I realised I had put off reading it late in my life.
If you're reading this comment and thinking Lisp and what's the point? Take a deep dive. It you're still questioning why, I highly encourage you to read The Little Schemer (and then all the others in the series). Scheme, Lisp, and now Bel, are a super power... pg's article was spot on.
(I’d love to be credibly told I’m completely wrong, because syntax aside, which I could get used to eventually, I rather like the model of Lisps and some of the features that supports.)
There was a systems type of Lisp called PreScheme that was closer to what you're envisioning. Carp also aims at no-GC, real-time use. Finally, ZL was C/C++ implemented in Scheme with both their advantages compiled to C. Although done for ABI research, I've always encouraged something like that to be done for production use with tools to automatically make C and C++ library bindings.
Tribe 1: You are a poet and a mathematician. Programming is your poetry
Tribe 2: You are a hacker. You make hardware dance to your tune
Tribe 3: You are a maker. You build things for people to use
I've always strived to write beautiful code - for things people find very useful - in ways that push boundaries for what people think is possible with the machines we use.
I have succeeded in balancing any two of the above, to terrible detriment of the third. Never been able to juggle the 3 at the same time and this bothers me a lot. The upside is this pushes me to learn a lot, downside is I'm never content with my craft.
I'd have loved to solve domain specific important problems with a domain specific language I craft with a LISP and have state of the art performance while doing so.
Programming is my poetry. Damn... that's a nice take
If Rust had the same features as most LISPs have, then using these features would make Rust programs as "slow" as LISP programs. That is generally true for most programming language comparisons. What also matters for final executable speed is the toolchain, of course. Any languages that compile directly using GCC or LLVM and allows for compile-time typing will be roughly in the same ballpark. If they use their own compiler, such as Chez or Racket, then they usually don't match the performance, because there is not enough manpower in their teams to implement all those nifty optimizations GCC and LLVM have. SBCL is probably the fastest LISP with its own compiler. (Or Allegro?)
To give an example, here are some CommonLisp features that Rust lacks: Full object system with inheritance and multiple dynamic dispatch, dynamic typing, hot runtime code reloading/recompilation, garbage collection.
According to "TechEmpower Web Framework Benchmarks" (which might not be perfect but at least give some indication), Clojure is one of the fastest language (in terms of handling responses per second) for building a JSON API with. Take a look at https://metosin.github.io/reitit/performance.html
I mentioned resource usage, and that includes memory usage. The JVM can be surprisingly fast for some types of code, but it’s never a memory lightweight.
I suppose I might as well mention another feature I value: ease of deployment. In Rust, I can produce a single statically-linked binary which I can drop on a server, or on users’ machines, or whatever as appropriate, and run it. For anything on the JVM… yeah, without extremely good cause I will consider dependency on the JVM to be a blocker, especially for a consumer product.
Nowadays you can either chose a Lisp that does compile to a single binary and doesn't require the JVM (like SBCL or something like that), or you could use Clojure but use GraalVM to get the single binary.
Anyways, I agree with your general point, Rust being in general faster. But I don't agree with the whole "Lisps are just generally slower and use more resources" thing, while it's certainly true in some conditions.
Startup you can measure, but the optimal tuning for faster general operation may start up slower, so now you might want both figures. And then you’re getting perilously close to development considerations, so once you’re doing that surely you should benchmark any required compilation, and so on.
Warmup is even harder to handle well, because it introduces another dimension; even if you were presenting just a single-dimensional figure (which you’re not quite), you’re now wanting to consider how that varies over time while warming up, so now it’s at least two-dimensional. But unless warmup takes ages, you’ll find it hard to get statistically significant figures, because you have so many fewer samples in each time slice.
Total installed size? Problematic because the number isn’t meaningfully comparable, as this is a minimal test, and a number in that scope gives no indication of how much is due to the environment (e.g. JRE, CPython, &c.), and how much further growth there will be as you add more features. People will also start arguing about what should count; if for example you count the delta from a given base OS installation, then you’re providing an advantage to something that uses the system version of, say, Python, and penalising something that requires a different and extra version of Python.
Memory usage? Suppose you pick two figures: peak memory usage, and idle memory usage after the tests. Both are easy to measure, but neither is particularly useful. Idle memory usage, similar problem to disk usage, and so the numbers aren’t usefully comparable. Peak memory usage is perhaps surprisingly the more useless of the two, because the increase in memory usage is strongly correlated with how many requests are being served at once—and so a slower contestant might artificially use less memory than a faster one; and so if you wanted those numbers to be comparable, you’d need to throttle requests to the lowest common denominator, which could then be argued as penalising light memory usage patterns.
So, there's a Lisp on top of Lua―compiled, not interpreted: https://fennel-lang.org
It's deliberately feature-poor, just like Lua: you use libraries for everything aside simple functions and loops. And it suffers somewhat from double unpopularity of Lua and Lisp. But it works fine.
I will not finish ... I suck at lisp. But it works.
I chose this based off a random search on google of "scripting language." You can do the same and would get the same result.
It's not a rigorous term, because (you are right) its a property of the implementation not the language itself, despite the name.
But I don't think Julia has relevance to discussion of the speeds of interpreted Lisp.
Basically a scripting language interpreter runs source code files, called scripts.
Lisp does that, too. But many Lisp implementations compile the code and often to machine code, since the runtime includes an incremental compiler. The incremental compiler can compile individual expressions to memory.
Thus when you use a Lisp system, running a script just looks like running an interpreter, even though the code gets compiled incrementally.
Lisp uses the function LOAD, which can load a textual source file and which can then read, compile, execute expressions from the file. Typically this is also available via command line arguments.
Like in SBCL one can do:
sbcl --script foo.lisp
I don't think this is correct. Scripting aren't rigorously defined, of course, but it would be reasonable to say they are defined by practice (i.e. a scripting language is practical for scripting).
Most compiled languages knock themselves out of contention in this definition because the startup and/or compile times are too long to be practical. But I've used luaJit in scripting contexts before, so that at least muddies the waters. Also common lisp (a variant that was always compiled).
I've always understood it to mean interpreted and from a quick google search there are at least a sizeable number of people who agree.
I think now that there are a lot of high level compiled languages, it makes less sense.
Sure, flag me, but my comment was relevant to the discussion.
It's about as "compiled" as Luajit is.
I totally get you with Lisps and performance, but I was more talking about the beauty and elegance of code written in them. Algorithms written in Lisps seem to be pieces of art.
And that's just Lisps... having a look at logic programming languages like Prolog and Mercury, it's amazing to see just how compact yet understandable an algorithm can be. They're beautiful!
But as for performance, take a look at Mercury - its a declarative logic programming language with built in constraint solving, which compiles down to C, and its performance is great. We've rewritten some Mercury in Rust, and yes our Rust is faster, but not by orders of magnitude as you'd think.
You can read it online for free, and clone the github examples repo.
Lisp is for the other 99.5% of programming that doesn't require you to wring every last drop of performance out of your CPU.
Some languages are easier to be made faster, but I think JS almost proves there’s no such thing as a language incompatible with high performance by design.
You’d be hard pressed to conceive a more dynamic language and here we are, with crazy fast performance through many JIT tiers, interpreters, compilers, each of which would take years for a bright engineer to understand.
I’ve read the latest version of the specs of all these languages in detail.
I just find your question a little confusing. My house is only two floors, yet meets all my needs. A bigger house would just waste my time in cleaning, cost me more in maintaining, in heating, etc.
It's the same for programs. Target performance at all cost, and for a lot of programs you get a hard to maintain, non scalable, bug ridden, featureless program that took you way too long to build.
That's why for example I use Clojure and Rust. Rust gets the least use, because the programs I tend to write can manage fine with Clojure's performance. In fact, I mostly toy with Rust, because I really never needed to write anything higher performance.
So I'm just not sure what you mean. The world is full of useful programs that meets the needs of their users which aren't high performance. For all these programs, Lisp is a superpower.
- how to make lisp go faster than C: https://www.reddit.com/r/lisp/comments/1udu69/how_to_make_li...
- https://github.com/marcoheisig/Petalisp "Petalisp is an attempt to generate high performance code for parallel computers by JIT-compiling array definitions. It is not a full blown programming language, but rather a carefully crafted extension of Common Lisp that allows for extreme optimization and parallelization."
Might answer your question 
On https://benchmarksgame-team.pages.debian.net/benchmarksgame/... at present, the fastest SBCL implementation, which seems to include these optimisations, is at 2.0, with other SBCL implementations being slower, the first one (whatever that means) being 8.0; meanwhile, the Rust implementations range between 1.0 and 1.2. The SBCL implementations all use at least eight times as much memory as the Rust implementations, too.
I think this demonstrates my point pretty well, actually.
No, that isn't required.
On the contrary SBCL is quite insistent that the arithmetic be made safe.
I'm not even a Lisp newbie, but with help from SO I've been able to tweak those spectral-norm programs to make the SBCL compiler happy without destroying the performance:
I fail to see the reason for you presenting the facts in this manner especially the "stupidly dangerous things part" which is of course totally inaccurate. (safety 0) has its uses (e.g. like Rust unsafe).
Your conclusions are not indicative of real world performance.
What exactly is it designed to attract attention to?
> … should not be taken seriously.
> … fully memory safe.
Here's the problem, the programs are explicitly required to use function calls but:
;; * redefine eval-A as a macro
Maybe there are situations for which it is "realistic" — the point is that we don't know.
What would actually be suitable for comparing language performance?
mainstream world is damaging to my soul
The magic is actually in how Lisp changes the way you think.
What work has you doing that?
Same offer goes for anyone who's working on something they hope will interest HN. Just don't be disappointed if you don't hear back for a long time—and as a corollary, don't send it just a couple days before you plan to publish. We have terrible worst-case latency!
I work in devops and that replaced a horrible mess of god knows how many containers with a straight forward, boring and reproducible single large instance of Guix SD.