Steel – An embeddable and extensible Scheme dialect
github.com
github.com
Another issue I have with evil is that it changes a lot of Emacs' default bindings, making it hard to do certain tasks. Some operations simply don't work at all. The kakoune package doesn't do this - at least not in insert mode.
> It's not very fast though and has some conventions that feel archaic
Sadly, multithreading is an afterthought for Emacs. There is just too much legacy stuff to make it easy. The language design is also from another era. The default dynamic binding feels very alien when almost every language everyone knows is lexical binding by default. On the other hand, scheme feel very modern due to very careful language design. But the effort to switch Emacs to scheme didn't find much steam.
There is one aspect where none of the new editors (hx with scheme and nvim with lua)can match Emacs. Emacs is entirely written in elisp with the C parts acting merely as libraries. The extension language for other editors is just an addition to their core editing code.
It is, but it's usable. I'm actually amazed that, even after three major versions, the built-in threading is not used by the community.
Yes, the threads currently are not usable for number crunching in the background. And yes, there are bugs, and trying to do many things from the background thread doesn't work, sometimes in unexpected ways. You can still block the main thread from the background thread since some things block the event loop, no matter where they were started.
But, the threads do give you independent control flows. Whatever you cannot do in the background, you can offload to the main thread with a timer and a queue of lambdas.
The built-in threads are very, very bare-bones - it's around 15 functions, for threads, mutexes, and condition variables. They are very limited by their "mostly cooperative" nature. However, with a bit of sugar, they are usable for at least one thing: async processes and network communication.
In a background thread, you can "block" to wait for a child process to do something. It's natural and requires no macrology (async.el...). The same is true for network communication. You can block and wait for a response while the rest of Emacs does whatever. With just two functions, you can write code without blocking as if you used `call-process`. Sequential actions - call this, wait for it to finish, call that, wait for it to finish, etc. - can now be coded in a sequential way, without having to worry about callbacks, sentinels, and a poor-man FSM implementation that invariably appears in Elisp that doesn't use threads.
The threads built into Emacs, currently, are closer to green threads or coroutines, functionally, than to OS-level threads. But that's still a huge help in a bunch of important and pervasive scenarios. It's really strange that nobody seems to realize this.
With threads (as they are), the Continuation Passing Style compiler macro (in generator.el), and dynamic modules (for actual parallelism where needed) Emacs now has everything it needs to make it non-blocking by default. Of course, that would entail rewriting everything on top of these abstractions, so it's unrealistic - but for new code and packages? I think we're just one package (along the lines of dash, s, etc.) away from convenient concurrency and parallelism in Emacs. The problem, of course, is that someone needs to design and code that package...
I'd say inertia. Emacs is large enough for people not to be aware of "simple" things, so the new threading primitives.. might take a few more years.
Many dynamic languages have bodged on threading over the past decade. (I don't think dynamic languages are intrinsically unthreadable or anything, but their interpreters were pretty deeply based on not having threads.) What that has shown is that 90%-effective threading is useless, and 99%-effective threading is superficially appealing but always, always blows up at any sort of scale.
You really need threads that don't come with all those caveats.
I expect it will get there, but another thing we've learned from previous efforts is that telling the community it's ready before it's ready causes "$LANGUAGE threading" searches to be filled with posts telling people how bad it is, even years and years after it has actually been fixed. It's probably a blessing in disguise it's not something the community is pervasively trying to use.
I think the only sensible way to offer parallelism in Emacs is to exclude the "shared-state" part, the way Racket (places) and OCaml do it. I think Python also tries to do it with subinterpreters? Let another instance of the interpreter run in the same process and communicate via message passing. That's probably still a huge undertaking, but at least it seems more viable than going through the whole codebase and adding locks everywhere...
Still, the "threads" in Emacs, as incomplete and half-baked as they are, can be useful. And if nobody uses them, there's no incentive for the developers to improve them. So I think we need at least some early adopters if we want the threading support in Emacs to get better.
- async/await camp with C# and F#, Swift, Rust, Python, TS/JS (yes, I know, event loop)
- Erlang/Elixir and BEAM family
- virtual threads and/or coroutines - Go, Java, Kotlin
Generally, I don't feel like in 2023 concurrency and parallelism are problematic areas anymore aside from existing aged stacks. From a perspective of mainly .NET ecosystem resident, it has been a shitshow outside of it for a long time indeed with many architectural choices throughout the industry paying for the Java sins (e.g. Kafka) and imposing limitations that seemed nonsensical and embarrassing even 7 years ago.
I'd say concurrency is largely a solved problem, yes; limited parallelism (e.g., with message passing) also mostly works. We don't need to worry about the "C10K problem" anymore. But shared (mutable) state parallelism is, I think, still far from solved - if it can ever be "solved", which is a pretty big assumption :)
Case in point, the recent async updates, native compilation and more. They often leave something to be desired but are nevertheless huge upgrades.
- curiosity
- seeing if I'm missing anything
- haven't had time to get tree sitter stuff configured, and wanted to see how good it is
- see if I want to move back to meow from vanilla (tons of previous vim, evil experience)
- Speed, even though emacs with the emacsclient is pretty much instant
- Font support, for some reason the terminal with Emacs sometimes doesn't render as well as it should
- Some keybindings issues in the terminal like Ctrl-Backspace not working properly.
- See if there are good workflows/defaults I could use in Emacs
A. Most WebAssembly implementations are bigger than Helix itself.
B. WebAssembly is immature.
C. Lisp is perfectly fine. Even if you don't like it, it's not the end of the world if you have to use it to configure Helix.
B. There are probably more computers running WebAssembly loads today than Lisp ones.
C. Lisp is a mature language, almost too mature. I used it extensively in the '80s and it served me well then, but that was 40 years ago. At least have the decency to use a more modern language like Lua, which is what Neovim uses.
B. WebAssembly is immature for developing a plugin system because of the lack of a sufficient ABI: https://github.com/WebAssembly/component-model
C. There aren't any other languages that meet the criteria. Lua was a no-go from the start. The maintainers did not like the language, and it necessitated adding more C code to Helix which could complicate building even further. https://github.com/helix-editor/helix/discussions/3806#discu...
Saying "X is better than Y" or "Why not Z?" is not constructive at all.
I use AwesomeWM, which is basically Emacs of Window Managers, with Lua instead of Elisp. The code is very well written and documented, yet getting into it is much more complicated than if it was written in Python - even if that Python was written poorly.
> it's a tremendous foot gun
I've never seen this footgun in action with elisp in Emacs, lua in neovim or vimscript in vim. Is this anything more than hypothetical?
> Also a huge security hole.
If you put an editor in a position where its Turing-complete configuration is a security hole, you'll be in a lot more trouble than you imagine. Editors by definition are meant to modify stuff in a filesystem. With those privileges, it wont matter what the config language is. The plugins, even in webassembly, will cause serious issues.
And hell, even an "execution safe" configuration can contain malware if there's a parser bug.
At some point you have to choose who to trust and not to trust to write code that runs on your system, and all you can really do is try to verify that they did in fact write it, and run untrusted code in isolation from sensitive data.
There are way more configs out there than there are software projects.
I think the real problem is around being able to trust your entire system. It'd help much more to have a better capability system so my rouge text editor can't upload my photos or credit card info from my browser profile to the internet, but today things kind of work because of tons of well intended and behaved people collaborating.
[0] https://index.scheme.org/filterset/r7rs_small/%28scheme%2520...
[1] https://index.scheme.org/filterset/r7rs_small/%28scheme%2520...
[2] https://index.scheme.org/filterset/r7rs_small/%28scheme%2520...
However, the current state of guarantees around it is not at the point where Turing-completeness is the biggest issue. You can have a simple config, but the rest of the system is still unverifyiable and running unknown blobs.
I think the reasonable way out of it is through restricted capabilities. We won't get a fully verifyiable system we can inspect anytime soon. Probably not before the dark days of mandatory and somewhat provable ad impressions.
1. Parameters and Syntax Parameters (Syntax parameters make macros more powerful)
2. Turing complete macros (not just syntax-case)
3. Typed Racket
I almost used https://gamelisp.rs/ for a project but the nightly feature it needs broke and it's no longer maintained, glad to see something similar arise! You might want to consider adopting their choice of VecDeque as a list replacement, I think it makes a lot more sense than naive linked lists on modern machines.
1. I do support parameters now, syntax parameters not yet. I would like to! But Racket has a pretty hefty head start on me so it'll take some time.
2. Right now I have syntax-rules macros, I also have defmacro style macros that get used internally in the kernel during expansion, but haven't yet opened them up for user space yet. Syntax case will be coming soon hopefully.
3. The odds of me being able to come up with an implementation to match typed racket pound for pound is pretty low. I have toyed with using contracts as types (where possible), with medium/promising success in certain situations. I have a soft spot for racket and have been modeling behavior after it, however it will take time to be able to create a macro system powerful enough to match it. It wouldn't be impossible to create an alternative syntax to just lower to steel after type checking, but I haven't put time into that.
On the list type - the list in use currently is an unrolled linked list https://github.com/mattwparas/im-lists, which I've found to yield much better performance for iteration than the naive linked lists. When possible, the vm does some in place mutation on the lists as well when consing, which helps performance as well. I also can hot swap for a vlist, but at the moment have stuck with unrolled linked lists.
Just in case you haven't come across it:
There is a portable implementation of `syntax-case` by R. Kent Dybvig and Oscar Waddell ready to plug into your expander.
I assume, you mean `syntax-rules` here.
If I understand correctly, it's pretty easy to simulate a paper tape with `syntax-rules` macro - that is `syntax-rules` macros are already "Turing complete".
https://okmij.org/ftp/Scheme/macros.html#turing-completeness...
But ... I know what you mean - in practise `syntax-case` or `syntax-parse` would be lovely to have.
I am currently going through SICP, and I am also interested in Rust, so this project is a great discovery! Maybe I can contribute to it also.
I haven't _directly_ used scheme professionally except for some steel scripts for automating some work flows and some racket programs for spark query plan analysis. I'd like to work in scheme more in my professional work, but for now I'm quite happy just working on it for fun.
Contributions are welcome! Feel free to either join the discord and ask questions there if you want a more chat based place, or open a discussion on github if you'd like to learn more. I have it on my TODO list to set up a matrix chat, just haven't gotten around to it - so apologies for having discord as the only chatroom.
Functions generate a hash based on a unique id generated for the function, plus the hash of any captured variables, and a hash of the pointer address to the function). That is off the top of my head though so I could be missing some details.
Hashing maps is tricky! With a sufficiently deep hash map you can run into problems since that invokes an equality check as well - at least how I handle it, is that you just attempt to naively hash the keys and values of the hash map, to create a hash code for that object. If the equality check ends up with a sufficiently large depth, eq returns false so we don't stack overflow.
I always feel like I'm missing something. In the example scripts and code snippets, I can see that you can define functions, that you can use lists, mathematical operations, you can build some algorithms, you can print text, but it never goes further than that.
I've only used languages like Python, Rust, C, Java, JavaScript, and they all have a very similar vibe, you have a std lib, which can interact with many things, you can build UIs, networking libraries and all that. And I could probably start using any language that is "similar" to these.
But I could never use one of these scheme/lisp languages, as I can't really grasp them.
Sorry, this comment is all over the place, because I can't really explain what's going on in my head when I see languages like this. I'd call myself a proficient programmer, but every time I look at these languages, it feels like as I've never seen code once in my lifetime.
Any help or hint at what I'm missing, is appreciated.
So, although I haven't used Steel, it looks like the advantage you'd get from using it is the opportunity to take advantage of features it provides like transducers and contracts, which are feature common to some other Lisps as well.
So, just like choosing any other language, it boils down to a series of tradeoffs.
Perhaps what you are missing is the practical part. For that you should look to the two types of Lisp in widespread usage: Common Lisp and Emacs Lisp. Common Lisp is a general-purpose language with a very large standard library and rich set of third-party libraries. Emacs Lisp is a complete language, but only really used to build text editing like stuff for Emacs. There's tons of real, effective code out there in these languages.
Or perhaps you are confused about the functional programming part. This is only strongly associated with Scheme as other Lisps support other paradigms like object-oriented programming. Functional programming is a thing that takes a while to understand, although my theory is it's actually more natural and it's only because you already learnt imperative programming, which is thoroughly unnatural, that you find it odd. Once grasped it will help you with other languages that support functional programming like JavaScript and Python.
They say learning Lisp will make you a better programmer even if you never use it again. I tend to agree with this.
I think that's it. Thanks, I'll take a look at common lisp, especially at projects written in it.
And also this blog post (which is a much smaller time commitment):
the language is malleable. because it is homoiconic. so while developing software, you are simultaneously writing a domain specific language for your problem. because macros.
in the end, if you like your craft, you end up with a "language" that is very suitable for solving the problem you have at hand, with very little noise.
the downside is, probably most others will not understand your code so lisps have heavy bias towards "solo hackers". BUT... if that is important to you, you can code towards understandability. so much so that you can make it very hard for others to make mistakes when using the public api.
so with most programming languages, you program within the constraints of the syntactic rules of the language. with lisps, you define the language you want to approach the problem, that language comes out by itself iteratively. for some, that is a joy. others don't care for it.
I think you must be fixating on syntax, or have been seeing some code that is advanced or (it exists) a very confusing example by some academic to demonstrate some curiosity.
Except for idiomatic recursion (which you don't have to use), Scheme semantics should initially look familiar to a Python or JavaScript programmer, like a subset of that, just with a different syntax. (Scheme nuances are much better designed, but to a new programmer semantics will look like a subset.)
And the Scheme syntax is one of the simplest ever, once you understand it.
What should instead be confusing is a language with very different semantics, like lazy evaluation, or an OOPL with complicating dispatch rules to reason about.
The syntax itself doesn't _really_ matter, it just makes it easy to do so - functions and syntax visually look the same, so it makes it easy to build.
Its not for everyone, but I think its worth exploring for a little bit. Similarly I think its worth really learning any language just a bit, if not to just expand your tool kit. The parenthesis do disappear at a certain point and you learn to read it, but if its not your thing that is fine.
Try moving the left parenthesis of each expression over to the right by one and it may become clearer
(my-fn 1 2)
my-fn (1 2)
Racket has all of that: https://docs.racket-lang.org
Common Lisp too: https://common-lisp.net/libraries
Clojure too: https://www.clojure-toolbox.com
(Or you could even just build anything within Emacs with Emacs Lisp: https://en.wikipedia.org/wiki/Emacs_Lisp)
> And I could probably start using any language that is "similar" to these.
Then you probably could start also using any of the Lisps mentioned. Most likely you are just hung up on the surface syntax, which does not take long to get used to.
I hope the second and third glances will be good too.
> We were not out to win over the Lisp programmers; we were after the C++ programmers. We managed to drag a lot of them about halfway to Lisp.
Guy Steele on Java
But the reason why Rust is such revolutionary computer science and, quite possibly, the most interesting thing to happen to PL design in decades is because with safe Rust you get all the memory-safety advantages of Java or Lisp, without a GC because the borrow checker statically guarantees object lifetimes. So Rust programmers don't need to be dragged halfway to Lisp the way C++ programmers were in the mid-90s, because Rust has the same memory-safety guarantees with none of the drawbacks of GC.
Safe Rust does not protect against memory/resource leaks when reference cycles are present. To avoid those, tracing GC is still needed - and most likely unavoidable in the general case. Note that avoiding reference cycles is a global concern that can't be localized to any single part of the program, so trying to ensure this statically is roughly as hard as proving that a random piece of C/C++ code does not corrupt memory.
You can of course stick to tree-like allocation patterns where object lifecycles nest cleanly, and that's what the borrow checker is all about. You can also use arenas/regions, and future Rust versions will hopefully make those easier to use.
Java came out before the STL and smart pointers were widely adopted.
This is a strange comment.Java 1.0 was released in Jan 1996. (Java wasn't very useful before 1.1) However, Stepanov proposed STL to ANSI/ISO committee in Nov 1993, and HP released a working version to the Internet in Aug 1994.
"[W]idely adopted" is an editorial term. It is meaningless without some backing evidence. ("Never been worse" has a similar sentiment.) How do determine what counts?
I worked on enterprise C++ for years in the mid-2000s that didn't use any smart pointers. There are many huge, old enterprise C++ projects that don't use STL or smart pointers. And, there are still many huge old Java enterprise projects that use shitty cast-from-Object-type to pass around typed data, instead of generics, or something better. Not much being said here!
the most interesting thing to happen to PL design in decades
"[D]ecades" is a wild overstatement. In the last 10 years, I would vote for LLVM, which greatly improved the velocity of (experimental) programming language development. Would Rust have developed so quickly without LLVM? Probably not. Look at the speed of development in Rust, Swift, Zig, and many others that use LLVM as their backend. It is night-and-day compared to 20 years ago in a GCC-only open source compiler world. I remember the bad old days where GCC was the elephant in the room, but so hard to add and maintain frontends, that few did it. Rust has the same memory-safety guarantees with none of the drawbacks of GC
I never saw this before. Are there any counterpoints?Borrowing in Rust means objects exist in two states: owned (which get dropped when they fall out of scope, like in C++), and borrowed, which means some other scope owns the object and we only have a reference to it. The compiler verifies that a borrow cannot outlive the actual object, so it statically prevents use-after-free errors.
Rust also has a distinction between constant and mutable values, and statically checks that any mutable references are exclusive, and that immutable references are only shared with other immutable references. With this it helps prevent race conditions or other such mistakes.
Finally, Rust also actually has smart pointers in case you truly don't know when an object won't be needed anymore, although the names are a bit different than in C++; there's Rc/Arc for reference counting (like shared_ptr), Box for owning pointers (like unique_ptr?), and RefCell, that's like a runtime borrow checker.
Apart from these features that prevent use-after-free and aliasing, Rust also has a feature called 'unsafe' with which you can bypass all these and e.g. work with raw pointers. Unsafe is generally used sparingly (and if not, attracts a lot of criticism, like happened to actix-web), and the safe abstractions on top also provide more pedestrian safety features like bounds checking. You can skip bounds checking on e.g. a Vec, but doing so actually requires you to drop into unsafe yourself, since the get_unchecked function is marked unsafe in the stdlib.
Small interesting side note: I'm pretty sure Rust is actually doing very little new things, PL-design wise. It's more of a realization of theory that has been around for years if not decades.
- When garbage collection is the optimal or near optimal memory allocation pattern for your program.
- When you care less about minimizing memory usage than minimizing implementation complexity, development speed, and compile time speed.
- When your program deals with cyclic data structures.
- When you want green threads (like Goroutines)
I can say that I would rather use Lisp to write any program that manipulates tree or graph structures simply because it has a garbage collector.
The link to the paper seems dead unfortunately from this blog post https://nim-lang.org/blog/2020/12/08/introducing-orc.html
I could see how it works as a drop in replacement for Rc
https://trout.me.uk/gc/ has two Recycler papers if you want more details.
As with /lisp/ I tend to grab my own copies of papers I think I'll want to refer to later in case the original URL vanishes in a puff of bitrot :)
In the game example I gave performance is important, but what's also important is consistency. Interactive apps rely on a steady framerate so what you want to avoid is accumulating garbage across multiple frames, then doing a single large collection pass.
In other words, it's better to do a bit of GC every frame than a bunch at once and risk stuttering.
(Note that doing GC over large object graphs will nonetheless involve significant overhead, even with efficient implementations as seen here; GC is not at all a silver bullet, and should be avoided if at all possible. The actual point of GC is to enable computing over highly general, possibly cyclical object graphs - if that doesn't apply, other memory management strategies can be used instead.)
I borrowed the R5RS test suite from chibi here - https://github.com/mattwparas/steel/blob/master/cogs/r5rs.sc... - only a few of these tests don't yet pass. Something like 135 pass, 4 fail, 20 skipped since I haven't implemented the primitives yet.
I've only tested against a few SRFIs so far, but am also attempting to run the R7RS benchmark suite https://ecraven.github.io/r7rs-benchmarks/ - So far you can view the progress here https://github.com/mattwparas/steel/tree/master/r7rs-benchma...
There are some more that aren't yet checked in. I plan to get a document up with the exact state of compliance soon.
The biggest difference right now is that, like Racket, Steel lists are immutable, so there I need a compatibility layer when running portable scheme.
Chibi is an impressive scheme implementation, and it will take a long time before Steel can hit the same level of compliance as Chibi. There is not a Chibi equivalent in native Rust that I am aware of. There are other embedded scripting languages for Rust that are pleasant - but no schemes of the maturity of Chibi. So in that regard, I'm hoping to offer a compelling scheme in native Rust to make integration with Rust applications relatively easy and painless.
I also don't have a particularly strong need to be 100% completely compliant with the scheme specs. The plan is to have compatibility layers so that portable scheme code can be used, however there are things about scheme that I think Racket (for example) improved on, and I'd like to explore that as well as much as I can.
Use cases are generally for either configuration, scripting, or plugins - so scripting in games, or adding extensions to your text editor without having to use FFI or RPC + serializing a bunch of data. The advantage it has over using dynamic libraries (in general) is it runs in the same process, and can access the internal data structures directly without a lot of ceremony involved. The downside is that it is typically not as fast as native code unless a JIT is involved.
Javascript is an example of an embedded scripting, where the browser is the host application.
(defun factorial (x)
(if (zerop x)
1
(* x (factorial (- x 1)))))
could be rewritten as defun factorial (x)
if (zerop x)
1
* x (factorial (- x 1))I've written thousands of lines of Scheme using my own preprocessor, and it's my favorite code to look at. I prefer Haskell, for incredible ease of parallelism and a deeper mathematical foundation.
The two features I look for in a reduced parenthesis syntax (like looking for the bone marrow in a beef stew recipe) are:
1. Some constructions begin doubly parenthesized. One needs a symbol to represent the missing object one parenthesis in. I use $.
2. One can write more expressive lines with a flavor of open paren that autocloses at the end of the line. I use |.
So when people complain loudly about parentheses in Lisp that just tells me they probably never made any real effort to learn it, and are actively opposed to trying.
It's not a productive starting point for a discussion.
I don’t like the parenthesis. I’m not flaunting anything. Is it “flaunting” to calmly and respectfully share an opinion? To you it’s trivial, to me it’s not.
It seems like you’re interested in creating conflict with people that don’t like a thing that you like. Which, I would cast this behavior as unprofessional, tbh.
Unsure if it means they are or are not dummies, though.
I question people's judgement who can't look past the syntax when there is a very good, and interesting technical reason behind them.
Though it's just more powerful in Lisp precisely because the code is just lists in a language designed around working with lists.
So you can actually leverage this property in Lisp without the code becoming inscrutable for it, which in my experience doesn't usually happen in other languages.
The list of things that are interesting is endless, though. I see something I’m turned off by, I move on. There are plenty of valuable things to spend time on.
I have heard of M-expressions but never of an actual implementation. Scheme also seems to have some SRFIs involving alternate syntax that's whitespace dependent, like SRFI 119 (wisp), SRFI 110 (sweet-expressions or T-expressions), or SRFI 49.
Maybe it’s a personal issue, idk. But it’s not out of ignorance nor is code readability a trivial point.
I see something like:
some-var: I64 := a * b - c ^ d ^ e;
...and my brain gives out, while I find: (let ((some-var (- (* a b)
(^ c d e))))
(declare (type I64 some-var))
...)
...to be much more readable, YMMV.Most lispers I know code in eMacs or other smart editors that provide auto code formatting and colored parens. But why not just make the indentation (or whatever that you really rely upon) the actual syntax, so there CANNOT be hidden bugs of that sort?
Edit: to expand on this, I think it is no coincidence that most lisps remain untyped to this day. Strong typing is about having the compiler enforce type rules so you the developer can’t fuck it up. Weak typing is more convenient, but ultimately a source of bugs. Lisp has, effectively, weak syntax. I don’t like weak syntax for the same reasons I don’t like weak typing.
That's Python. When whitespace matters, any aesthetic reformatting mistake can change the program's meaning. With s-expressions this cannot happen. A lisp code parser is completely deterministic regardless of where the newlines, spaces, and tabs occur. You can remove all the newlines from a 10,000-line Lisp program and the compiler will parse it exactly the same as if it were formatted aesthetically.* You can also write a simple program that takes that godawful one-line program and reformats it aesthetically however you like--the meaning won't change.
IOW in Lisp the aesthetics of the source code do not determine its meaning; aesthetics and meaning are orthogonal properties and you are free to adjust the two independently. This is also somewhat true in languages like C, but rather than several special-case punctuation characters, in Lisp there's only one: The parenthesis. Lisp is thus similar in spirit to HTML where semantics and layout are [mostly] independent.
* With a few obvious exceptions like EOL comments, and newlines that are part of quoted strings.
Why would you just randomly change indentation? On the contrary, I don't want the indentation to say something else than the code actually does.
> You can remove all the newlines from a 10,000-line Lisp program and the compiler will parse it exactly the same as if it were formatted aesthetically.
But a human won’t. And that’s a problem.
Because the user may want the development environment to display snippets of code in various places: REPL, debugger, code browsers, inspectors, various editor types, ...
In a Lisp system the code can be data and text. Code formatters can reformat code depending on user preferences, device types (color, font, ...), view sizes, ...
In Lisp often code gets generated (for example via 'Macros') and this code will be automatically layouted in various view (different widths, different fonts, different detail). Code can be small or large, the system may abbreviate parts, which one can expand, if necessary.
Source Code is not necessary static text in a file system. Code can just be list-based data structures and layout is fluid.
In Common Lisp the formatted output of code is also user extensible/customizable, a 'pretty printer' is a part of the language spec:
https://www.lispworks.com/documentation/HyperSpec/Body/22_ba...
If we read a text in a book reading the device, one can also change for example font size and the thing will relayout the text accordingly.
With round brackets on the other hand, well... I think my brain just works with round brackets. Like my mind is coded in Lisp or something, I dunno. It would explain why I'm so slow: GC pauses. My mind should really be ported to SBCL or something.
re typing: Coalton brings Haskell-like typing on top of CL. https://github.com/coalton-lang/coalton/ Other lisps are typed: typed racket, Carp… and btw, SBCL's compiler brings some welcome type warnings and errors (unlike Python, for instance).
That's my point, is all.
As for readability, I don't think it's trivial at all. I just don't think syntax has all that much to do with it. It plays a role, but a very minor one compared to overall code quality. In other words it's syntax that's mostly a trivial point, not readablity.
One undeniable advantage of Lisp in terms of syntactic readability though is that other languages always end up piling on more syntax over time as the language gets older. That's by far my least favourite thing about Rust for instance, even though I do love the language. There's always some new syntax, keyword, or position that an existing keyword can suddenly go in. The longer I go without actively using Rust, the more work it is to start again, because I have to go learn all this new stuff now. And syntax always takes a while to feel intuitive, at least for me. But it's still a lot easier than grokking new semantics and paradigms. I still don't have a good handle on async rust.
If someone told me to go read some ALGOL 68, I could probably do just fine because there's nothing semantically unfamiliar about it compared to something like C. But if someone gave me some Haskell written as sexps I would be utterly lost despite the syntax being perfectly comfortable to me. Because I never quite grokked typed FP.
Lisps are not write-only. They're easy to read precisely because there is no inscrutable syntax. Every pair of parentheses has a first element, which tells you what they're doing. The parentheses denote a scope in which to look for arguments.
Conventional style guides also make the readability practically a trivial issue because you end up indenting arguments such that the structure of the program is reflected in the indentation, because each S-expression within a parenthesized S-expression is itself an AST node. Writing readable Lisp is just a matter of reflecting the AST's structure in the indentation, which most people do in other languages anyway (to some degree).
> the advantages of lisp have long since been obtained by other languages.
Clearly not, since most other languages are still not S-expression-based. (Though, admittedly, some other advantages have been copied in other languages.)
Was met with skepticism, esp around the engineering/maintenance overhead, until I told them the parser took me a couple hours to write and was only a hundred lines of code or so
1. That it's unambiguous enough that my editor can format it correctly every time (provided the code is correct, obviously). Indentation-based languages like Python fail this.
2. That its elements and keywords are distinct enough that my editor can apply colours on it. I sometimes feel that S-expression languages fail at this because of their small number of distinct keywords, but that might not be true.
Besides these two points, if the language is not a joke language, it's probably fine.
if a:
print("foo")
if b:
print("bar")
Or wait, maybe the copy paste didn't work exactly right and "if b" was supposed to be inside "if a" if a:
print("foo")
if b:
print("bar")
This is not a huge problem, but problems with syntax never are (in non-joke relatively modern languages). But it's still something I prefer that languages fix in their syntax if I get to have a choice.Names are important. Our inheritance is wit, self-awareness, and irony; names that puncture ego and power and that appeal to joy: C, C++, GNU, Rust, Google, Yahoo!, Vim, Git, awk, etc. Others are beautiful, evocative images, like Apple and Amazon. Names communicate our culture and ideals to each other and to the next generation.
Careless, thoughtless names like Microsoft, IBM, etc. (ok it's ironic and self-deprecating, but without self-awareness or wit!), etc. should be hated and banned. Egotistical BS, especially Tolkien plagiarists who assert they are supernatural, should be tarred and feathered and paraded around town (with wit and irony).
(Plenty of names fall in some middle ground.)
If Steel is just a derivative of 'Rust' [edit: it is not, see the response below], it misses the self-awareness. Someone naming their development product - designed to build great structures - 'Rust' is engaging in a little self-deprication, joy, and self-awareness. Naming the derivative project 'Steel' possibly misses all that; there's a reason the original wasn't named Iron or Steel or Carbon Fiber. But maybe there's more to the name.
1. Guy Steele (along with Gerald Sussman) created scheme, and Steel is close to Steele, just drop the e.
2. Steel is a scheme, and I observed that scheme names have a tendency to be named things crime related: Scheme, Racket (racketeering), Larceny, etc - Not a scientific analysis at all, but I found it funny at the time that Steel sounds like "steal".
3. You made the observation, Steel sounds like something that would be associated with Rust.
Beyond that, I just liked the name. No SEO involved, and arguably probably should have picked something more searchable, but I didn't start making it with the intention of there being a lot of users, it started as a project for school.
(I always try and cram as many simultaneous jokes as possible into project names - and have a soft spot for lisp - so I wish you much joy and whatever level of success is most fun for you)
All of the work done by the guile folks is incredible, I wouldn't mind the name clash at all if there is a Guile steel that comes about.
Is this a joke?