Arc Forumnew | comments | leaders | submitlogin
The trouble with arc's if statement (slidetocode.com)
2 points by akkartik 4012 days ago | 15 comments


4 points by zck 4012 days ago | link

I wonder if the author thinks the traditional if statement is flawed in the same way.

Most of my if blocks _are_ a single s-expression for each branch. I guess it depends whether you prefer to write functionally or imperatively. pg stated somewhere that, if the choice is forced, he will make the functional version of the code shorter, rather than the imperative version of code.

I also find it very interesting that the author doesn't indent the code at all. I hope it's just to make the point in this article, and now how ey normally writes code.

-----

3 points by fallintothis 4012 days ago | link

Most of my if blocks _are_ a single s-expression for each branch.

I was going to say...

  (def if-with-do (expr)
    (and (caris expr 'if)
         (some [caris _ 'do] expr)))

  (def accum-expr (accumf testf exprs)
    (map [ontree (andf testf accumf) _] exprs))

  (def keep-expr (testf exprs)
    (accum a (accum-expr a testf exprs)))

  (def count-expr (testf exprs)
    (summing s (accum-expr s testf exprs)))

  (defs count-ifs-with-do (exprs) (count-expr if-with-do exprs)
        count-ifs         (exprs) (count-expr [caris _ 'if] exprs)
        ifs-with-do       (exprs) (keep-expr if-with-do exprs)
        ifs               (exprs) (keep-expr [caris _ 'if] exprs))

  arc> (each file '("arc.arc"
                    "strings.arc"
                    "pprint.arc"
                    "code.arc"
                    "html.arc"
                    "srv.arc"
                    "app.arc"
                    "prompt.arc")
         (prn "=== " file)
         (let exprs (readfile file)
           (prn (count-ifs-with-do exprs) "/" (count-ifs exprs))))
  === arc.arc
  11/120
  === strings.arc
  3/18
  === pprint.arc
  5/5
  === code.arc
  0/0
  === html.arc
  4/24
  === srv.arc
  4/21
  === app.arc
  9/53
  === prompt.arc
  2/6
I also find it very interesting that the author doesn't indent the code at all.

I have a feeling (hope) that that was unintentional---like the blog formatting is just eating whitespace or something. Also, the first "Arc if" has a missing right parenthesis and uses = for comparison. :)

-----

3 points by zck 4012 days ago | link

Nice code. Some of my lying-around arc files:

unit-test.arc 2/12

All of my project euler solutions: 4/98

-----

2 points by fallintothis 4012 days ago | link

I was hoping other people would investigate their code! :) My personal arc directory filtered by hand for completeness (I have a lot of half-finished projects lying around), plus some stock Arc stuff I forgot in the last post:

  (each file (lines:tostring:system "find . -name \\*.arc")
    (prn "=== " file)
    (let exprs (errsafe:readfile file)
      (prn (count-ifs-with-do exprs) "/" (count-ifs exprs))))

  === ./transcribe/transcribe.arc
  1/1
  === ./qq/qq-test.arc
  1/3
  === ./qq/qq.arc
  0/10
  === ./macdebug/macdebug.arc
  7/17
  === ./bench/nbody.arc
  0/0
  === ./bench/pidigits.arc
  1/1
  === ./ansi.arc
  1/1
  === ./news.arc
  16/99
  === ./sscontract.arc
  0/0
  === ./blog.arc
  0/0
  === ./libs.arc
  0/0
  === ./profiler/profiler.arc
  0/2
  === ./vim/gen.arc
  0/11
  === ./hygienic.arc
  0/3
  === ./trace/trace.arc
  1/4
  === ./contract/test.arc
  0/1
  === ./contract/contract.arc
  0/9
  === ./contract/special-cases.arc
  0/2
  === ./contract/reconstruct.arc
  0/2
  === ./contract/defcase.arc
  0/1
  === ./contract/test/canonical.arc
  0/4
  === ./contract/test/ssyntax.arc
  0/4
  === ./contract/ssyntax.arc
  0/5
  === ./contract/util.arc
  0/2
  === ./quick-check/quick-check.arc
  1/8
  === ./arity/arity.arc
  0/8

-----

1 point by akkartik 4012 days ago | link

Nice trick, passing accum functions around!

-----

3 points by akkartik 4012 days ago | link

You're right about blocks, so let's just focus on single-line 'if's. I can't think of a single non-lisp that allows adjacent condition and action without an intervening token. C and Java require parens, Go and Perl require curlies, Python requires the colon, Ruby requires a newline or semi-colon.

  if (condition) action;     # C, Java
  if condition { action; }   # Go, Perl
  if condition: action       # Python
  if condition; action; end  # Ruby
The 'else' token is a further separator. Never in these languages will you ever have two either-or expressions side by side. They're separated by either 'else' or 'else if' or 'elif'. Perl and Ruby even sometimes use 'if' as a separator.

  action if condition        # Perl, Ruby
Am I missing any counter-examples? I think the biggest error in this article is to blame Arc, when it's just extending the logic all lisps have always had. If you permit such adjacencies as (if condition action) and (cond ((condition action))), then Arc's choices don't seem so unreasonable.

(Though Clojure's mixture of Arc 'if' and optional types is a whole new level of unholiness.)

-----

3 points by rocketnia 4011 days ago | link

"Though Clojure's mixture of Arc 'if' and optional types is a whole new level of unholiness."

Is this what you meant instead? "Though Clojure's 'let', which mixes Arc's 'withs' with type hints, is a whole new level of unholiness."

Even so, I think Clojure's 'let doesn't actually have any[1] added complexity when it comes to type hints. It might look like the list is bunched into groups of either two or three depending on whether a type hint is present...

  (let [^String x "x string"
        y 2]
    (body-goes-here))
...but that's not a quality of 'let. That's a quality of the ^ syntax. An occurrence of ^ consumes the next two s-expressions, just like ' consumes the next one s-expression:

http://tryclj.com/

  > '[^String foo "foo"]
  [foo "foo"]
  > (count '[^String foo "foo"])
  2
  > '[^String]
  java.lang.RuntimeException: Unmatched delimiter: ]
  > '^String foo
  foo
  > (meta '^String foo)
  (:tag String)
So the bindings of a 'let are consistently bunched into groups of two s-expressions, just like Arc's 'withs.

[1] Of course, the type hints are actually used for optimization at some point, so the complexity of parsing them has to go somewhere. This is exactly as complex as destructuring: Arc's 'withs syntax supports destructuring, but it doesn't need special-case destructuring logic because it just translates down to 'fn. As it happens, Clojure's 'let syntax also supports destructuring, and it probably uses the same general technique.

-----

2 points by akkartik 4011 days ago | link

Yes! Turns out I didn't notice the switch from if to let.

I have to say, though, I have no sympathy for the argument that it's still groups of two s-expressions. As a reader it's still more onerous to have to mentally group:

  ^a b
compared to:

  'a
So the presence or absence of parsing complexity feels irrelevant.

-----

2 points by fallintothis 4012 days ago | link

I can't think of a single non-lisp that allows adjacent condition and action without an intervening token.

I mean, isn't this requirement mostly because those languages are infix anyway? Parsing gets easier with explicit ways of separating things. I could easily imagine shift/reduce conflicts or what-have-you cropping up when you try to eliminate the requirement for, say, parentheses around conditionals in Java.

For example, in some hypothetical infix language that doesn't require conditional separators (parens, braces, then, etc.), would

  if x==10-x ...
be

  if (x == 10) -x ... // maybe this language doesn't use the "return" keyword,
                      // so the conditional is returning negative ten.
or

  if (x == (10 - x)) ...
?

Because Lisps use s-expressions, "intervening tokens" (per se) are unnecessary. As you say, Arc's choices don't seem so unreasonable, considering that.

-----

2 points by akkartik 4012 days ago | link

Yeah, that's a good point. Non-lisps use keywords and punctuation for readability and to make parsing tractable, and the two reasons are often hard to separate in any single design decision.

To summarize my position: I have some sympathy for the specific argument that multi-branch 'if's are harder to read in lisp than in traditional languages[1]. But this affects any arrangement of parens, whether traditional 'cond' or arc 'if'.

[1] I see I said so to you before, at the end of http://www.arclanguage.org/item?id=16838.

-----

2 points by thaddeus 4012 days ago | link

  These parentheses weren't decorative though, 
  they exposed the structure of the code, and they are a
  minimal price to pay for extra clarity.
Well clarity is dependant upon the reader and, more specifically, how the reader reads.

My path to clarity is as follows: I first scan code at a high level, often referred to as speed reading[1] where I find having more parentheses simply gets in the way. The second is using fixation[2] where I stare at a small block of code. With both Arc and Clojure I have no trouble moving from scanning to fixation then to understanding (clarity). It could be that the author struggles with his/her reading method. Chances are the author learned with traditional L.I.S.P (Lots of Infuriatingly Stupid Parentheses) and therefore the newer lisp dialects are working against his/her read training. I however started with Arc & Clojure and I therefore experience the same lack of clarity when reading the traditional LISP dialects. To each their own.

1. http://en.wikipedia.org/wiki/Speed_reading

2. http://en.wikipedia.org/wiki/Fixation_(visual)

-----

3 points by steeleduncan 4011 days ago | link

Original blogger here. Apologies about the lack of indentation, I'm not sure how I missed that, it is a markdown conversion thing.

The point I was making in the blog is expressed more eloquently in [1], but to summarise: the difference between LISP and the countless languages that have risen and fallen during LISP's lifetime is that LISP directly represents the parse tree. By disposing of those parentheses in the (if ) statement, Arc has introduced the need to count the terms in the list to understand the sense of the code, in other words to parse it. Clearly the same is true of Python, C++, Haskell and many other languages I respect, but by placing a barrier between the code and the parse tree, Arc has abandoned the principle that makes LISP a global, rather than a local, minimum in the history of programming languages, and I think this is a mistake.

[1] http://ayudasystems.tumblr.com/post/71327185334/do-i-really-...

-----

3 points by rocketnia 4011 days ago | link

It sounds like what you like to see are s-expressions that fit these patterns:

  homogeneous list of any number of X
  fixed-length heterogeneous list of (X Y Z)
  expression... which fits one of these patterns:
    atom
    call to X with args Y
Arc and Clojure make this more complicated by adding more cases, and I'll highlight Clojure's cases here:

  map from X to Y
  homogeneous vector of any number of X
  vector alternating between X and Y, with no excess (like a map)
  call to 'cond with args alternating between (expression) and
    (expression), with no excess
  call to 'case with args starting with (expression), followed by
    alternations between (anything) and (expression), perhaps with
    (expression) as excess
  function body which might begin with a docstring and/or a metadata map
  destructuring syntaxes
  ...
Between Arc and Clojure, I'm pretty sure Arc is intrinsically harder to auto-indent, because it doesn't distinguish between different cases using different kinds of brackets. Racket's a great example of what I mean; parens are used when the subsequent lines should be indented like function arguments, and square brackets are used otherwise.

For structured editing -- what you pursue -- we probably want even more static structure than what we want for auto-indentation.

I don't want to prod you to spend your time writing tools for niche languages or designing niche languages of your own, if that's not what you already want to do, but I'd like to ask what kinds of hypothetical languages you would like best....

Would you be eager to work with a lisp-like language where the AST has a few built-in notions of homogeneous lists, heterogeneous tuples, function calls, etc.? For instance, Lark defines an alternative to s-expressions that's more tailored to the ways the syntax is actually used (https://github.com/munificent/lark).

On the other hand, would you be eager to work with a language where the AST has an endless variety of possible cases, which can be extended by any programmer who wants to write an editor widget? Racket does something related to this, because it has an expressive pattern language for writing macros, and macros written this way generate good parse errors automatically (http://docs.racket-lang.org/syntax/Parsing_Syntax.html).

Personally, I've been kinda dreaming about both of these approaches to structured editing for a long time, but I'm still working on the run-time and link-time semantics of my language designs, so I've been unambitious when it comes to editing-time semantics. :)

-----

2 points by thaddeus 4011 days ago | link

Fair enough, you're not saying anything wrong here, but I still do not agree. This really this is about the trade offs each of us are willing to make.

i.e Programmatically[1] yes you might need to count terms, but for code readability I do not count the terms I let code indentation guide me.

I can certainly see how this code indentation factor may be seen as too free in form or structure to be appealing to many, but having less parentheses is a huge readability/enjoyability win. A win that, at least for me, leads to huge gains in productivity.

Also, you do have the option to pretend the token count is even:

  (def example (x)
    (if (is x 1) "One"
        (is x 2) "Two"
        'else  "OOPS"))
  
It's up to you. In my mind power & flexibility are the big draws to LISP. If I wanted to be directed by the language as opposed to empowered by it I would just use C.

1. Once I've had to create a custom macro where I might have had to count the terms, but it's a rare event.

-----

2 points by akkartik 4011 days ago | link

Welcome! I can see how lispers wouldn't like the arc approach, but I don't see how it makes arc more like non-lisps. What traditional language requires counting positions? I usually associate that with lisps.

-----