Hacker News new | ask | show | jobs
by dan-robertson 2783 days ago
Well it seems that OP needn’t bother as this problem is already solved. You wrote (loop (print (eval (read)))). How does that go with a screen reader? Like:

Bracket loop bracket print bracket eval bracket read bracket bracket bracket bracket?

The key thing I think is balancing and shifting parens.

  (loop (print (eval)) (read))
Is quite hard to sound different from

  (loop (print (eval (read))))
And more crucially what about the difference between these two:

  (let ((x (f))) (g x) (g x))
  (let ((x (f)) (g x)) (g x))
(I think writing these on a single line makes them written more like they might be spoken)

On the other hand I think being able to modify the syntax structurally is a big advantage.

Going back to the topic of the OP I think a useful thing is putting functions after their arguments, and optimising to have single arguments or most non-main arguments being typically small. For example something more like

  read
  | eval
  | print
  | loop
And maybe some way to do arguments like

  read stdin 
  | eval some-environment 
  | print :pretty
  | loop
3 comments

”Bracket loop bracket print bracket…”

A screen reader need not literally read what’s on screen. It typically doesn’t with prose, where punctuation isn’t spoken, but affects timing and intonation, and abbreviations often are expanded (iOS speaks “Dr. John St.” as “Doctor John Street”, for example, but “St. John Dr.” as “Saint John dee-ar”. MacinTalk used to know that ‘Dr’ means ‘Drive’)

So, it need not do that here, and could say

“call read, eval it, print the result, and call loop on print’s output”

, using intonation or voice to indicate the difference between content read from the screen and text describing it.

Farfetched? Maybe, but take a look at what screen readers do with html.

A screen reader that knows lisp semantics could go even further, and replace loop by repeat forever or something like it.

Requiring a sufficiently smart screen reader seems a bad way to go. 30 years on from CLtL we still don’t have a sufficiently smart compiler and I would argue that that problem is easier. One thing is that this works “better” with one-argument functions. Consider:

  (with-open-file (*standard-output* foo)
    (print x))
  (print y)
Vs

  (with-open-file (*standard-output* foo)
    (print x)
    (print y))
How do you read the first? As “print x, now with-the-file foo-open-as-standard-output it; then print y”? Or maybe “with foo opened as standard output, print x; now print y”?

This seems more reasonable but how do you write the second example? Like “with foo opened as standard output, print x; also print y”?

There are reasonably large semantic differences in such cases and many programming languages, including lisp, express them with subtle hard-to-pronounce differences in indentation or parenthising of expressions.

The language in question already has a dataflow/pipeline style by default, so expressions are generally read from left to right like this, except for some standard syntactic sugar like infix operators and flow control blocks like “if” and “match”:

    read eval print loop

    stdin read
    some_environment eval
    pretty print
    loop

    [1, 2, 3] [4, 5, 6] \(+) zip_with
    do (each_index) -> i, x {
      if (x = 7) {
        "Lucky seven!" say
      } else {
        ["Value at index ", i show, " is ", x show]
          concat say
      }
    }
> Bracket loop bracket print bracket eval bracket read bracket bracket bracket bracket?

It would say "left paren" and "right paren." It's not terribly hard to keep a stack in your head of how many close parens you're expecting in some context. Manually managing brackets in long or deeply nested forms (in any language) is a pain whether you can see or not. As you said, pseudo-structural editing is a big advantage.

> (I think writing these on a single line makes them written more like they might be spoken)

Screen readers can announce indentation levels.