upvote
It took me probably 5 years of writing Clojure before it clicked. Once you get used to structural editing and repl driven development, it’s really hard to go back to syntactic languages.

It’s kind of like in treesitter style editing, where you can “swap these two arguments,” “select this function,” “wrap this in a try block” with a single keyboard command… but way more standardized and granular. Plus with the ability to execute anything you highlight

All that and then you realize you can store code as data (since it’s just a data structure) and run data as code.

I think most programmers don’t realize how arbitrary the difference is between code and data until they get used to using LISP.

reply
Spot on. For me, it clicked with Common Lisp, 15 years after I graduated from university. Now, Clojure is my daily driver. And it’s extremely difficult to explain to people. I’ve gotten to the point where I don’t even try. You’re right about all the things you mentioned. Once you discover structural editing, everything else seems primitive, on the level of cavemen playing with rocks. But it’s not just one feature that makes Lisp better. It’s all of it which interrelates and creates a powerful synergy (I hate that word, but in this case it’s appropriate) that just isn’t matched by anything else. There are other languages that have a similar vibe, notably Forth and Prolog, but they are often misunderstood, too. Honestly, that’s my real test of whether someone is a senior programmer: do they understand and at least have an appreciation for these languages, even if they don’t program in them everyday.
reply
The idea is that instead of having to learn tens of different syntactic constructs with subtle and often arbitrary differences, you just have parentheses and use them to build everything.
reply
This is such a undervalued benefit, once you've learned s-expressions, you can basically learn a bunch of languages without having to learn completely new syntax. It'll be slightly different, with different idioms and names, but a hell of a lot easier than doing the same across every "It's like C but 50% of the syntax is different actually" language out there, which is most of them.
reply
Is the syntax really the stumbling block for most languages? Would Rust's lifetimes or Swift's isolation rules be easier if they used more parens? Are the scoping rule differences between Emacs Lisp and Scheme easier to comprehend because the syntax is similar?
reply
Yes, a commonly occurring stumbling block for me is trying to use one language's syntax while actually programming in another, especially when it comes to all the Algol/C-like language, I probably mix things on a daily basis.

The concepts would be easier to grok up front if they just used normal function calls instead of "And now for this special syntax that only exists for this particular feature" which just adds more things to remember, instead of just the concepts themselves.

reply
Yes it is, because as soon as programmers step out of the most basic language level (which is kinda similar in most mainstream languages) there's a bunch of wildly different concepts, with wildly different ways of writing them. Writing them in isolation might be manageable, but it's combining them effectively that gets hairy very quickly, unless one is very experienced in said language. But then, translating that to OTHER languages becomes a bar that is too high!
reply
[dead]
reply
That just moves the subtle and often arbitrary differences to the functions and macros you use.
reply
The kind of subtle difference I’m talking about is that in C, you have to put a semicolon after struct {…} and do {…} while (…), but not after other curly brace constructs. What would be the analogue in Janet?
reply
The first one is pretty C-specific and for example doesn’t exist in Java. Similarly for the second one, the reason is that `while` is used for two different constructs, which for example isn’t the case in Rust. These are just accidental complexities stemming from unfortunate design decisions in C. Having a richer repertoire of syntactic constructs doesn’t by itself imply such complications.

My point was, replacing n syntactic constructs by n functions or macros doesn’t reduce the cognitive load of having to know how each construct works. To the contrary, one can argue that everything having the same syntactic form makes it more difficult to distinguish different classes of features.

reply
> The first one is pretty C-specific and for example doesn’t exist in Java.

It was just an example, most languages have quirks like this. I don’t know about Java, but in Rust you have the turbofish operator, whose necessity stems from using the less-than sign as both an operator and a delimiter.

> My point was, replacing n syntactic constructs by n functions or macros doesn’t reduce the cognitive load of having to know each of them.

The difference is that if you don’t know a function/macro, you can just read its documentation. If you don’t know a syntactic construct, where do you look?

Another advantage is that if you want to create new functionality similar to existing language features, it won’t stick out like a sore thumb. For example, you could create an until loop:

  (until (window-should-close)
    (draw-screen))
  # is equivalent to
  (while (not (window-should-close))
    (draw-screen))
In Rust, it would have to look completely different from a while loop:

  until!(window_should_close(),
    draw_screen());
  // is equivalent to
  while window_should_close() {
    draw_screen();
  }
reply
Probably depends on whom you are asking. For me the essence is (1) having functions or procedures as the basic building blocks, not classes. (2) Having all the utility and higher order functions you need to deal with the functions and procedures first idea. (3) Having a very powerful syntax, that allows great semantic editing and is never ambiguous. Oh and can actually be extended in useful ways, without having to wait for a committee to decide upon "the one syntax to rule them all".
reply
> can actually be extended in useful ways

I just made a library with [query syntax](https://codeberg.org/veqq/declarative-dsls) over various data structures a la sql:

    (import declarative-dsls/dataframes :as df)
    (def people (df/dataframe :name :age :job))
    (df/dataframe? people)

    (df/insert! {:name "Bob" :age 30 :job "Developer"} :into people)
    (df/insert! {:name "Alice" :age 27 :job "Sales"} :into people)
    (df/update! :set {:job "Engineer"}
             :where |(= ($ :job) "Developer")
             :from people)
    
    (df/save-csv people "people.csv" :sep "\\t")
    (def people2 (df/load-csv "people.csv" :sep "\\t"))
    
    (-> people2
       df/dataframe->rows
       df/rows->dataframe
       df/print-as-table)
Printing:

    job       age  name
    --------  ---  -----
    Engineer  30   Bob
    Sales     27   Alice
It also has datalog and minikanren (with s expr, sharing the same goals etc.) And it vectorizes like APL:

    (df/v + [1 2 3] 1 [1 2 3] 1) # returns: [4 6 8]
    (df/v + 1 {:column [1 2 3] :key [1 2 3]}) # returns: {:column @[2 3 4] :key @[2 3 4]}

    (df/v * [1 2 3] [[1 1 1]
                     [1 2 2]
                     [1 2 3]]) # returns: @[@[1 1 1] @[2 4 4] @[3 6 9]]
Or you can just use [J directly from Janet](https://git.sr.ht/~subsetpark/jnj):

    (jnj/j "3 4 $ i. 10") # returns: ((0 1 2 3) (4 5 6 7) (8 9 0 1))
    (jnj/j "$" [3 4] (range 10)) # returns: ((0 1 2 3) (4 5 6 7) (8 9 0 1))
The Joy Web Framework has a cool [db query dsl](https://github.com/joy-framework/joy/blob/master/docs/databa...) too: `(var account (db/find-by :account :where {:login (auth-result :login)}))`, used for a [web auth](https://codeberg.org/veqq/janetdocs/src/commit/848dcbd8e54ad...).

From my response, bigger than the article: https://lobste.rs/s/y0euno/why_janet_2023#c_lspe6n

reply
these little dsl's convey so much
reply
> studied them in school - but i quickly forgot and never got around

Because industry lied to you, promising "simplicity and riches". The industry didn't just overcomplicate programming. It institutionalized the complication. Why? Because complexity is a moat.

Complex frameworks need certified experts. Certified experts charge more. Companies built around expertise need the complexity to persist. So the complexity gets marketed as sophistication.

They've promised: "Java/C# will get you hired anywhere", but you're hired to write xml (these days yaml). "OOP models the real world", they said. The real world doesn't have abstract factory visitors. "Design patterns make you senior", but you only learned workarounds for language deficiencies. "Learn the framework, get the job". Framework dies, you start over. "Specialization is valuable". you're now hostage to one ecosystem.

A programmer who understands fundamentals is dangerous to this system. The fundamentals:

- a function transforms input to output.

- composition builds complexity from simplicity.

- types describe what's possible.

- effects should be explicit.

And then you realize that Lisp is the skeleton key. All that above is Lisp, or came from Lisp. Every language is either: Lisp with different syntax, or C with different syntax, or arguing between the two.

If you learn Lisp, you don't learn a language. You learn what languages are. You're no longer a consumer of a programming language or two, or a few. You are native speaker in all of them.

reply