[HN Gopher] A different take on S-expressions
       ___________________________________________________________________
        
       A different take on S-expressions
        
       Author : tearflake
       Score  : 47 points
       Date   : 2025-06-15 11:25 UTC (3 days ago)
        
 (HTM) web link (gist.github.com)
 (TXT) w3m dump (gist.github.com)
        
       | phoe-krk wrote:
       | Lisp programmer here.
       | 
       | Traditional S-expressions, by their definition, ignore most of
       | whitespace; additionally, reading sexprs is always a linear
       | operation without the need to backtrack by more than one
       | character.
       | 
       | The suggestion from this post violates both assumptions by
       | introducing a 2D structure to code. To quote this post's
       | examples, it requires the multiline string in
       | (fst-atom """   trd-atom)                   00001
       | 00002                   00003                     """
       | 
       | to be fully read before TRD-ATOM. It also forces the reading
       | function to jump up and down vertically in order to read the
       | structure in                   * (
       | )           *   e (           ) (           )             *   q
       | m (     )     p (     )     *                 u   a a       o   a
       | 2       *                 l             w             *
       | 
       | The author also states that                   (eq (mul (a a))
       | (pow (a 2)))
       | 
       | is less readable than                   * (
       | )           *   *eq* (                   ) (                   )
       | *          *mul* (         )     *pow* (         )     *
       | *a* *a*               *a* *2*       *
       | *
       | 
       | Then there's the ending passage:
       | 
       |  _> we hope that the introduced complexity is justified by the
       | data readability expressed this way._
       | 
       | I cannot force myself to read this post as anything but a very
       | poor Befungesque joke.
        
         | tgv wrote:
         | A normal tree would be easier to read
         | eq            mul      pow          a    a    a   2
        
           | derriz wrote:
           | Turned 90, maybe?                 eq:         mul:
           | a           a          pow:           a           2
        
             | hyperhello wrote:
             | x*x == pow(x,2)
        
               | derriz wrote:
               | We have a winner!
               | 
               | Actually, I'd suggest a slight improvement: x*x = x^2
        
               | xigoi wrote:
               | x * x = x2
        
         | unstruktured wrote:
         | Thanks for restoring my sanity. Was quite confused of the value
         | added by the author.
        
           | tearflake wrote:
           | Sorry for the confusion. I must be a very disturbed person
           | because I kind of like what is explained there.
        
         | velcrovan wrote:
         | It gets worse/better. Since Racket allows you to hook your own
         | reader in front of (or in place of) the default reader, you can
         | have things like 2D syntax:                   #lang 2d racket
         | (require 2d/match)                   (define (subtype? a b)
         | #2dmatch           +----------+----------+-------+----------+
         | |   a  b   | 'Integer | 'Real | 'Complex |
         | +----------+----------+-------+----------+           | 'Integer
         | |             #t              |
         | +----------+----------+                  |           | 'Real
         | |          |                  |           +----------+
         | +-------+          |           | 'Complex |        #f        |
         | |           +----------+------------------+----------+)
         | 
         | https://docs.racket-lang.org/2d/index.html
        
         | f1shy wrote:
         | Yes that part must be a joke!
         | 
         | I've seen dozens of attempts to make S-Exp "better" even the
         | original M-Exp. I also did some experiments myself. But at the
         | end, I come back to goo'ol s-exp. Seems to be a maximum (or
         | minimum) found just perchance.
        
         | tearflake wrote:
         | Here is another example, an axiom from propositional logic:
         | (impl (impl p (impl q r)) (impl (impl p q) (impl p r)))
         | 
         | which, vertically indented in a transposed block, looks like
         | this:                   * (
         | )         *   i (               ) (                       )
         | *   m   i p (       )     i (       ) (       )             p
         | m     i q r       m   i p q     i p r             l   p     m
         | p   m         m           *                 l     p           l
         | p         p           *                       l               l
         | l           *
         | 
         | which, using transposed lines within the transposed block,
         | finally looks like this:                   * (
         | )         *   *impl* (                               ) (
         | )   *         *            *impl* *p* (                )
         | *impl* (                ) (                )     *
         | *impl* *q* *r*                *impl* *p* *q*     *impl* *p* *r*
         | *
         | 
         | This time I won't make any judgements. Could be good, could be
         | bad, you decide.
        
           | exeldapp wrote:
           | Not sure if that example helps. You can make any programming
           | language hard to read without some basic formatting. The way
           | I would write the sexpr would be:                 (impl
           | (impl             p             (impl q r))         (impl
           | (impl p q)            (impl p r)))
           | 
           | It's clear when each section begins and ends and doesn't
           | require complex parsing rules.
        
       | stray wrote:
       | For very large values of "somewhat peculiar"...
        
         | tearflake wrote:
         | Changed the "somewhat" to "very" in the document, thank you.
        
       | TOGoS wrote:
       | This is fine and interesting, but what I think is lacking in
       | S-expression isn't funky vertical syntax, but a way to directly
       | represent objects that are not lists. Otherwise one needs to
       | invent some representation on top of S-expressions (and then a
       | list isn't necessarily a list anymore; everything goes through an
       | additional layer of encoding/decoding, losing the elegance of
       | S-expressions), or use some extension syntax (usually involving
       | '#'), which varies from language to language and might not even
       | be interpreted by the reader (but logically expand to some list
       | expression that needs to be interpreted again later, so you're
       | not really any better off than with the first approach).
       | 
       | I kind of want something like, to borrow JSON-like syntax and
       | gloss over namespacing issues:                 (foo .
       | {type: listy-cons-cell          head: bar          tail: (baz
       | quux)})
       | 
       | ...which would be another way to say (foo bar baz quuz), but
       | would make it possible to represent any data structure you like
       | at the same level as atoms, strings, and lists.
        
         | drob518 wrote:
         | See Clojure's reader syntax:
         | https://www.clojure.org/reference/reader
         | 
         | You can have vectors, hash maps, and sets in addition to lists,
         | symbols, and keywords.
        
           | kgwxd wrote:
           | I don't get why anyone even tries after Clojure. They got it
           | 100% right. It's easier to read than anything else, and still
           | super simple to parse. Commas are whitespace, use them or
           | don't, where ever you want. Namespaced keywords are great.
           | The data structures themselves act as functions. It's just...
           | done.
        
         | sparkie wrote:
         | Kernel has first-class environments which aren't just lists,
         | but can be constructed from lists. Environments are
         | encapsulated, so we can't simply peek into them with car and
         | cdr - we can only obtain the value associated with a given
         | symbol by evaluating the symbol in that environment.
         | ($define! foo             ($bindings->environment
         | (bar "Hello World")                 (baz 1234)
         | (qux #f)))                          ($remote-eval bar foo)
         | ==> "Hello World"              foo
         | ==> #[environment]
         | 
         | We could perhaps make something a bit more friendly. Lets
         | create an encapsulated `struct` type which could give us the
         | contents as a plain list, or let us look up each field:
         | ($provide! ($struct struct? destruct $get)
         | ($define! (struct-intro struct? struct-elim)
         | (make-encapsulation-type))
         | ($define! destruct                 ($lambda (struct)
         | (cdr (struct-elim struct))))                      ($define!
         | $get                 ($vau (struct member) e
         | ($let ((record (car (struct-elim (eval struct e)))))
         | (eval member record))))
         | ($define! zip                 ($lambda (keys values)
         | ($if ($and? (null? keys) (null? values))
         | ()                          (cons (list (car keys) (car
         | values)) (zip (cdr keys) (cdr values))))))
         | ($define! $struct                 ($vau kvpairs env
         | ($let* ((keys (map car kvpairs))
         | (values (map ($lambda (pair) (eval (cadr pair) env)) kvpairs))
         | (record (apply (wrap $bindings->environment) (zip keys
         | values))))                         (struct-intro (cons record
         | values))))))
         | 
         | Example usage:                   ($define! foo
         | ($struct                 (bar "Hello World")
         | (baz (+ 12 43))                 (qux #f)))              ==>
         | #inert                          (struct? foo)
         | ==> #t         (pair? foo)                     ==> #f
         | (environment? foo)              ==> #f
         | (destruct foo)                  ==> ("Hello World" 55 #f)
         | ($get foo bar)                  ==> "Hello World"         ($get
         | foo baz)                  ==> 55         ($get foo qux)
         | ==> #f         ($get foo foo)                  ==> ERROR:
         | Unbound symbol: foo              foo
         | ==> #[encapsulation]
         | 
         | Kernel: https://web.cs.wpi.edu/~jshutt/kernel.html
         | 
         | Klisp (essentially complete implementation of Kernel):
         | https://github.com/dbohdan/klisp
        
       | fn-mote wrote:
       | Related but not the same at all, Racket has a 2D syntax (add on
       | mode) that gives a different way to program tables where the
       | output depends on two different inputs.
       | 
       | https://docs.racket-lang.org/2d/
        
       | jazzyjackson wrote:
       | I'll piggyback with my gruesome JSONification of S-expressions. I
       | kinda liked having two kinds of braces [straight] and {curly} to
       | differentiate arrays and objects, and I did have a event-loop-
       | based "parallel" scheduler working to process a tree as soon as
       | prerequisites were fulfilled. I might pick up the old project
       | again someday, I just got hung up on how I wanted to handle error
       | bubbling.
       | 
       | With a vertical script like japanese you could easily rotate the
       | whole program 90 degrees to the right (as shown at the bottom of
       | the landing page)
       | 
       | https://web.archive.org/web/20240904091932/https://lookalive...
       | {       "#!join": [         [           "A triangle with side of
       | ",           "#& side",           " and base of ",           "#&
       | base",           "has a hypotenuse of",           {
       | "#!sqrt": [               [                 {
       | "#!sum": [                     [
       | "#!multiply side side",                       "#!multiply base
       | base"                     ]                   ]                 }
       | ]             ]           }         ]       ]     }
        
       | drob518 wrote:
       | As a Lisp programmer, just no.
        
         | tearflake wrote:
         | Thank you for the criticism. Lots of lispers share your
         | opinion.
        
       | chc4 wrote:
       | These definitely are extensions that you could add to
       | S-expressions, no one can disagree there.
        
       | somewhereoutth wrote:
       | dispense with the parentheses:                 (eq (mul (a a))
       | (pow (a 2)))
       | 
       | becomes                 eq         mul           a a         pow
       | a 2
        
         | danielrico wrote:
         | Lispython
         | 
         | I should trademark this name.
        
         | tmtvl wrote:
         | That's Wisp, I don't care for it, but people who really like to
         | assign semantic meaning to precise counts of invisible
         | characters may find it interesting.
        
           | derriz wrote:
           | Absolutely no counting is required at all so I think your
           | joke falls a little flat.
           | 
           | Our visual system has the ability to detect implied straight
           | lines (and other simple geometric outlines) from very small
           | clues.
           | 
           | Therefore "seeing" the vertical lines implied by the
           | indentation is effortless - so it's immediately obvious which
           | elements belong to each other.
           | 
           | Indentation is an incredibly valuable "brain" hack that
           | manages to instantly communicate hierarchy, not something to
           | be sneered at.
           | 
           | We have no such innate ability to match parenthesis -
           | determining hierarchy in a jumble of open and close
           | parenthesis requires precise counting or, typically these
           | days, tool/editor/IDE support.
        
             | thaumasiotes wrote:
             | > Absolutely no counting is required at all so I think your
             | joke falls a little flat.
             | 
             | Really? How do you see the difference between "TAB" and
             | "SPACE SPACE TAB"?
        
               | xigoi wrote:
               | The solution to that is easy: do not use tabs.
        
             | XorNot wrote:
             | I also don't know why this is treated as controversial
             | either: the first thing every project does is declare a
             | canonical code formatting aka whitespace layout and start
             | rejecting patches which don't follow it.
        
         | agumonkey wrote:
         | there's a hybrid form (sweet-expressions ? i forgot), top-level
         | terms are parens-free                   eq (mul a a)
         | (pow a 2)              defun min (a b)           (if (a < b) a
         | b)
         | 
         | IIRC the hack to support this at read time was minimal, and it
         | made a big impact in terms of "mainstream appeal"
        
       | pkilgore wrote:
       | Is this.... is this a joke?
        
         | tearflake wrote:
         | I don't intend to be funny. Just a bit childish, but in a good
         | way :)
        
       | davesque wrote:
       | This is bonkers and I love it.
        
         | tearflake wrote:
         | Ikr? People should loosen a bit, why should everything be so
         | serious?
        
       ___________________________________________________________________
       (page generated 2025-06-18 23:00 UTC)