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.
> 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
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)
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.
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...
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).
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.
example: we could have a `case` that works as follows and adheres to Scheme/Lisp style (using parentheses to clearly specify blocks):
OR we could also have a "convention" and treat test-conseq pairs implicitly, and save a few parentheses: 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:
again, we simply look for a "do" keyword (can even say it should be ":do") and run every expression after it sequentially.