Hacker News new | past | comments | ask | show | jobs | submit login

Speaking of Lisp, I'm developing a programming language that is similar to other Lisps, but I noticed that we can sprinkle a lot of macros in the core library to reduce the number of parentheses that we use in the language.

example: we could have a `case` that works as follows and adheres to Scheme/Lisp style (using parentheses to clearly specify blocks):

    (case name
        (is_string? (print name))
        (#t         (print "error - name must be a string"))
    )
OR we could also have a "convention" and treat test-conseq pairs implicitly, and save a few parentheses:

    (case name
        is_string?    (print name)
        #t            (print "error ...")
    )
what do you think about this? obviously we can implement this as a macro, but I'm wondering why this style hasn't caught on in the Lisp community.

Notice that I'm not saying we should use indentation—that part is just cosmetics. in the code block above, we simply parse case as an expression with a scrutinee followed by an even number of expressions.

Alternatively, one might use a "do" notation to avoid using (do/begin/prog ...) blocks and use a couple more parentheses:

    (for my_list i do
        (logic)
        (more logic)
        (yet more logic)
    )

again, we simply look for a "do" keyword (can even say it should be ":do") and run every expression after it sequentially.



A bunch of lisps do that. Clojure for instance or arc use conventions like that to reduce the number of parentheses.

See e.g. cond in clojure: https://clojuredocs.org/clojure.core/cond


> Clojure for instance or arc use conventions like that to reduce the number of parentheses

Clojure also reduces parentheses by introducing more characters for various data-structures (good and bad, many would say), like [] for vectors, which are for example used for arguments when defining functions

    (def add-five [a]
      (+ a 5))


In an old programming language project of mine, I wanted to use something similar to S-expressions for most of the grammar because they are very universal and flexible, but I didn’t like having too many parentheses, so I extended the syntax by allowing semicolons as an alternative.

So, in pseudocode:

  (print (+ 1 2))
  (exit 1)
becomes:

  print (1 + 2); // "+" is the 2nd place because it's a method call on "1"
  exit 1;
Both variants are valid; semicolons are just syntactic sugar to reduce the number of parentheses. In the AST, they represent the exact same expressions.

For function bodies, I also introduced an extension to make lambdas stand out more from the rest of the code by using curly braces:

  (0 to 10) loop ^(i) {
    print i; // Internally transformed to (print i)
  }

  // clarification:
  // 1. The "to" method of the 0 object creates a Range object, which has a "loop" method that accepts a lambda.
  // 2. The ^ symbol is just a shorthand for "lambda".
As you can see, this wasn't a Lisp dialect (and I'm no expert at Lisps), but I liked the idea of having a universal expression structure in the AST, however I eventually ended up extending it with two additional modifications that made it more readable (at least for me)


Your for example is basically Common Lisp's `loop`

    (loop for i in my-list
      do (logic)
         (more logic)
         (yet more logic))


if you think that there are too many parens in lisp then you should fix your editor.

the parens are just a textual representation of a tree. once you have a proper editor, then the eliminated parens actually become an obstacle.


Imagine if we had a Lisp with proper types, we could do something like...

  (typecase name
    (String (print name))
    (otherwise (print "error: name must be a string!")))
And if that Lisp had some kind of Object System, we may even go further and have stuff like...

  (defmethod namecall ((name String))
    (print name))

  (defmethod namecall ((name Number))
    (print "I am not a number, I am a free man!"))


In case anyone is missing the snark here, all of tmtvl's code above is in fact perfectly valid Common Lisp.


clojure's multi-arity addresses this to some extent but pattern matching like we know it in Haskell is very weak in Lisps...


In case the sarcasm was missed, the latter code was actual common lisp code to pattern match a generic function, and the former is nearly-there when it comes to common lisp code


Why "nearly"? Given how symbols are uppercased upon read in CL, 'string and 'String are actually the same symbol. I just checked: `(typecase "" (String 2) (otherwise 3))` returns 2, as expected. I'm less sure about the `defmethod`s, but I think they'll also work.

Plus, the capitalized type name reminded me of Coalton[1], which is an internal DSL for CL that implements ML-style type system. There, the dispatch on a type (conventionally named by a capitalized symbol) happens statically.

[1] https://github.com/coalton-lang/coalton


Because on the phone with one hand I couldn't check if I didn't forget some detail in typecase which I use way less often than defmethod :)


Fair! :) The HyperSpec is full of easily forgettable details that almost never matter, until they do :D There are even undefined and implementation-defined things there, which can trigger C++ PTSD on a bad day...


Wild!


Actually, this reminds me very strongly of Rebol [0] and related languages (e.g. Red [1]). They work as you describe at the end: whenever a function is encountered, the interpreter inspects the following arguments, without any need for parentheses. Since these languages are homoiconic like Lisp, it’s possible to use functions as if they were macros (achieving it at runtime using a rather strange ‘binding’ system).

[0] https://en.wikipedia.org/wiki/Rebol

[1] https://www.red-lang.org/


Just install an editor plugin (parinfer I like now) to fix that easily. It's a pleasure. It's not hard to make your own really; it's, like you say, only cosmetic. You can format your code for reading in the same way with a trivial plugin; it is a really nice thing that you can very rapidly make something nicer tailored for you while still benefitting from the performance / debugging / etc in sbcl. For my dayjob, I work a lot with Typescript parsing and rewriting and, while the ts (and ts-morph and so on) are pretty nice, it's really a quite horrible experience vs what we have in lisp/scheme.


It's just a bunch of macros to this, no ?

There are a lot more hairy/pressing issues given how old the CL spec is (the type-system esp. comes to mind).


You can do a lot with creating your dsl/tooling and something like Coalton really. More than enough imho.




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: