Arc Forumnew | comments | leaders | submitlogin
How the colon syntax could be a lot cooler (and other ideas)
14 points by drcode 6108 days ago | 15 comments
One feature I really love in arc is one that many people may not have noticed yet- It has a great feature for debugging an arbitrary form.

For Instance, suppose you are worried about a bug in the form handling the first division in the following example:

  (let x 4 
      (+ 1 2 
          (/ x 3)
          (/ 7 6)))
What you can do in arc that I think is awesome is "inline" a prn statement like so:

  (let x 4 
        (+ 1 2
            (prn:/ x 3)
            (/ 7 6)))
If you're a long time lisper, you'll see the value of this immediately, because intrumenting in a debugging statement usually requires 2 edits, since the intrumenting code will be in parens and will require you to write that annoying terminating paren, which the colon operator avoids. Adding/removing the terminating paren during debugging is a common causee of Lisp paren screwups.

Unfortunately, the colon operator is not very general... Why, for instance, can't I inspect the "x" variable, like so:

  arc> (let x 4 
              (+ 1 2 
                  (/ prn:x 3)
                  (/ 7 6)))
  Error: "/: expects type <number> as 1st argument, given: #<procedure>; other arguments were: 3"
Surely, it would be easy to make allow composition on literal numbers/strings/conses and make this possible?

Another wart in the colon operator is that I can't do this:

  (let x 4 
        (+ 1 2
            (/:prn x 3)
            (/ 7 6)))
If you run this (the prn and division have been flipped- we'll see the value of this later) the program will print the wrong anser, because the prn loses the "3" and doesn't pass it on. I would argue "prn", when called with multiple values, should return a list of all values- Which brings me to the issue of multiple values...

Suppose prn DID return a list of args, how would those args get fed into "/" properly? By a strange coincidence, I was going to post about the use of "@" outside of quasiquotes today, but someone else had the exact same idea today: http://arclanguage.org/item?id=1920

Here's what that feature would allow us to do:

  (let x 4 
        (+ 1 2
            (/ @(prn x 3))
            (/ 7 6)))
In this case, prn would return a list of '(4 3) and then they would be divided properly... Then, we could use the colon syntax as follows:

  (let x 4 
        (+ 1 2
            (/:@prn x 3))
            (/ 7 6)))
The colon here would tell the arc compiler "treat the returned list from prn as multiple values to compose into the division function"

If we had a feature like this, it would allow us to elegantly handle multiple return values (without the arguably ugly "values" system from scheme) And would greatly generalize the colon operator! For instance, imagine being able to do this:

  arc> (string:@map [+ "/" _] '("3" "4" "5"))
  "/3/4/5" (imagined answer)
To take this further, imagine we change "withs" to work like this:

  (withs (x y z   ;note all variable names are at the beginning
          1 2 3)
      (+ x y z))
Then, maybe, we could write stuff like:

  (def foo ()
      '(1 2 3))

  (with (x y z @(foo))
      (+ x y z))
Note how we could basically get "multiple-value-bind" as a freebee with support for an "@" operator!

Another feature that would greatly synergize with this line of thinking would be partial application support, already discussed previously on the forum:

  arc> (= g (map [+ _ 1]))
  <function>   ;this currently doesnt work :(

  arc> (g '(4 5 6))
  (5 6 7)
There may be problems I'm not seeing that would prevent this kind of stuff from working well... But if not, wouldn't an improved colon operator like this (along with the other ideas I've mentioned) open up all kinds of possibilities for briefer/cleaner code? I might even not miss Haskell's point free style all that much any more :)

-Conrad Barski



4 points by kens 6108 days ago | link

I noticed that colon syntax is broken when used with strings-as-functions:

  arc> (pr:"abc" 2)
  Error: "car: expects argument of type <pair>; given ()"
I was expecting that to work the same as (pr ("abc" 2))

-----

5 points by sjs 6108 days ago | link

Indeed, and it does when you call compose directly. The good news is that if you name the string it works as expected.

  arc> (= s "abc")
  "abc"
  arc> (prn:s 1)
  b
  #\b
The reader splits expressions such as (prn:"foo":[idfn _] 2) into (prn: "foo" : (make-br-fn (idfn _) 2). You could check for trailing : in the car or a leading : in the cadr, and if found then wrap strings and tables in id fns. Stash them (as well as literal fns and make-br-fns) under a uniq name. Finally, add the uniq name to the end of the head symbol and repeat if necessary. This would be done before expand-ssyntax. I'm not sure it's worth the effort.

I think the final result would be very cool, mostly to allow things like (prn:[_ 3] "hello").

-----

2 points by drcode 6108 days ago | link

Yeah, the colon operator has many limitations, I've noticed... I haven't been able to get it to work for any case that doesn't involve simple symbol names for functions...

-----

2 points by sjs 6108 days ago | link

These are interesting ideas. I really like the idea of using @ as an analog to Ruby's splat. After thinking about it for a few minutes I think I like (string:@map ...) too, though I think it's starting to look like line noise.

If you compose a function with a value what does that mean? Something like ((fn (y) (prn y) y) x)? In the example you gave I would still expect to wrap prn:x is parens to execute the resulting procedure.

-----

1 point by drcode 6108 days ago | link

you are correct about the parens on prn:x... I thought I had a clever trick from making them unecessary, but it didn't pan out when I sat down to work out the necessary transformations... creating decent language primitives is hard...

...your "line noise" comment also is a good point...

-----

1 point by sjs 6107 days ago | link

This stuff is hard. I looked into implementing general splicing functionality but it is not trivial. Reading @bar and translating it to (splice 'bar) is easy enough, but then you would have to examine the arguments of every sexp before evaluating it to handle the splice. I guess it's not particularly difficult but it would likely be a performance hit. Perhaps there is a clever trick to do general splicing.

-----

1 point by almkglor 6105 days ago | link

Probably a similar trick to `, . Need more hints?

-----

1 point by bogomipz 6107 days ago | link

prn:x should mean [prn (x _)] because x is no different from prn, and may very well be a function. Unless you can statically know the value of x, but statical analysis of variable content does not sound very lispy to me. While it's ok for optimization it is not ok for semantics!

-----

1 point by kennytilton 6107 days ago | link

Here is my CL hack to avoid parens juggling, automated or not:

  (defmacro ekx (ekx-id &rest body)
    (let ((result (gensym)))
     `(let ((,result (,@body)))
         (trc ,(string-downcase (symbol-name ekx-id))
              :=> ,result)
         ,result)))
(ekx weird-result + 40 2) -> weird-result :=> 42

I think my TRC function can be easily converted to standard CL. Of course, none of thus addresses the fancier tricks you talked about above, such as (+ prn:x 42).

-----

2 points by evanm 6108 days ago | link

Why bother?

The problem of having to add a terminating parenthesis is a non-existing one.

With emacs and slime-mode for example you add the parenthesis in pairs around any expression you wish, and you can remove them in pairs as well.

-----

1 point by kennytilton 6107 days ago | link

Still work compared to converting (in my case) (+ x 42) to (ekx hi-mom + x 42). Subtext: I have watched Emacs users edit code... reminds me of a Nascar pit stop.

-----

1 point by evanm 6106 days ago | link

do you mean (ekx hi-mom (+ x 42)) ?

if you are inside the (+ x 42), type C-, to move just before the parenthesis, then C-1 [ to insert parentheses around the expression, then type "ekx hi-mom ".

In short: C-, C-1 [ ekx hi-mom

-----

1 point by kennytilton 6104 days ago | link

No, the idea was to add/remove tracing without adjusting the parens (I hate typing), so (ekx xxx + 2 2) as strange as it looks does eventually expand in part to the form (+ 2 2).

-----

1 point by mec 6084 days ago | link

If you're trying to debug print the value of a variable can't you do:

  arc> (let x 4
         (+ 1 2
            (/ prn.x 3)
            (/ 7 6)))
  4
  11/2
As long as print returns the the value it prints (and it appears to) this should work fine.

-----

2 points by araujo 6107 days ago | link

About the partial application support , we could do something like:

  (= f [map [+ _ 1] _])
Though I am myself a Haskell coder, and I value partial application very much; I think that with this very same syntax we accomplish pretty much the same effect, without bloating and keeping the syntax clean and coherent enough with the base model of Arc.

Also, a Haskell-like notation like the one you proposed might be very tricky and difficult to read in a language without explicit typing notation.

-----