Arc Forumnew | comments | leaders | submitlogin
The right way to indent anonymous function calls
4 points by akkartik 969 days ago | 9 comments
I've always been frustrated by the conundrum of how to indent calls with non-syms in function position.

  (def recstring (test s (o start 0))
    ((afn (i)
       (and (< i (len s))
            (or (test i)
                (self (+ i 1)))))
     start))
This works, but it's unsatisfactory. It takes a tad more effort to make sense of the dangling start. But there was never a better way. Now I think I have the answer: clojure's pipe operator!

  (mac -> (x f)
    `(,f ,x))

  arc> (-> 3 [* _ 2])
  6
As a bonus, it chains beautifully with wart's infix:

  list.1 -> cons? -> car
  => 1
Anyway, with pipe we now have the option of putting the long, hard-to-read form at the end:

  (def recstring (test s (o start 0))
    (-> start
        (afn (i)
          (and (< i (len s))
               (or (test i)
                   (self (+ i 1)))))))
Still to do: make it varargs along both dimensions (xs and fs).


2 points by thaddeus 968 days ago | link

The Clojure operator '->', as I know it, is called 'thread-first'. And in Clojure it used to inject a value (as the first argument) into a provided expression. It then returns a result which will get piped through, in the same manner, to any additional expressions supplied. There is also a thread-last '->>' which obviously injects the value as the last argument. It looks like you've decided that thread-last is more useful in Arc (which I would agree with) and have chosen the '->' operator.

I think that's a great idea.

For interest sake here are my conversions of thread-first and thread-last respecting Clojures implementation:

  (mac -> (x . forms)
    (if (single forms)
        (let form (car forms)
          (if (acons form)
              (let (f . args) form
                 (if args 
                     `(,f ,@(cons x args))
                     `(,f ,x)))
              `(,form ,x)))
        (let (form . more) forms
          `(-> (-> ,x ,form) ,@more))))


  (mac ->> (x . forms)
    (if (single forms)
        (let form (car forms)
          (if (acons form)
              (let (f . args) form
                 (if args 
                     `(,f ,@(join args (list x)))
                     `(,f ,x)))
              `(,form ,x)))
        (let (form . more) forms
          `(->> (->> ,x ,form) ,@more))))
In Clojure, however, the thread-first operator works really well when querying deeply nested tables/hash-maps while in Arc these do not. This really is just because of the difference in hash-map/table implementations.

So here is my preferred implementation for Arc:

  (mac -> (x . forms)
    (if (single forms)
      (let form (car forms)
         (if `(isa ,x 'table) 
             `(,x, form)
              (if (acons form)
                 (let (f . args) form
                      (if args 
                          `(,f ,@(join args (list x)))
                          `(,f ,x)))
                 `(,form ,x))))
      (let (form . more) forms
        `(-> (-> ,x ,form) ,@more))))
Which allows for:

  arc> (= animals (obj cat (obj toby (obj age 9))
                       dog (obj thaddeus (obj age 5))))
  ...          
  arc> (-> animals 'dog 'thaddeus 'age)
  5
I believe this will work for your afn/recstring example, but the other one would be a little different:

  arc> (-> [* _ 2] 3)
  6
[note: I had to install arc to give this a try - not having used it for probably a year or so. So if it's really hacky my apologies, but took me 10 mins just to re-learn how to write even really basic expressions, and I certainly don't remember being very good with macros.]

Cheers. Tim

-----

2 points by akkartik 968 days ago | link

Interesting! Clearly my knowledge of the clojure primitives was extremely vague; I wasn't even aware that they operated on forms rather than function values.

Arc could eliminate the ->/->> dichotomy in another way entirely, using the existing syntax for single-arg fns. The examples at http://clojuredocs.org/clojure_core/clojure.core/-%3E%3E would become (arc-ish pseudocode):

  (-> (range) [map [* _ _] _] [filter even? _] [take 10 _] [reduce + _])
  (-> 5 [+ _ 3] [/ 2 _] [- 1 _])
At the same time, the examples at http://clojuredocs.org/clojure_core/clojure.core/-%3E become:

  (-> "a b c d" upcase [replace _ "A" "X"] [split _ " "] first)
What's more, you could mix first and last because all the arguments are actual values rather than hacky s-exprs. I already used that in the second example above, if you look closely.

---

The one missing use case is this:

  (-> animals 'dog 'thaddeus 'age)
Arc just has a different, retro-email solution for that:

  animals!dog!thaddeus!age
Though it all unravels if one of the keys is a string. Ok, we could use some help here. But it feels like a separate mechanism; I would avoid mixing `(,form ,x) and `(,x ,form) in different paths of a single macro.

---

Hmm, even this isn't too bad:

  (-> animals [_ 'dog] [_ 'thaddeus] [_ 'age])
You don't save typing compared to:

  (((animals 'dog) 'thaddeus) 'age)
But the matching parens are closer together and so easier to read.

-----

2 points by thaddeus 968 days ago | link

I agree, mixing `(,form ,x) and `(,x ,form) is really ugly and I do like

  animals!dog!thaddeus!age
much better - I forgot arc could do that lol.

Also, if anyone were really caught up on the strings as keys hiccup, the idiomatic approach for arc would likely be this: http://arclanguage.org/item?id=13006. If one could only find a pleasant & available symbol.

-----

2 points by akkartik 968 days ago | link

"if it's really hacky my apologies, but took me 10 mins just to re-learn how to write even really basic expressions"

I thought the execution was fine! I only objected to the direction :p Only simplification I could see was to turn:

  (if args 
      `(,f ,@(join args (list x)))
      `(,f ,x)))
into:

  `(,f ,@args ,x)

-----

2 points by thaddeus 968 days ago | link

yikes.. lol.

Also, and I'm not sure, but I have a sneaking suspicion I might need to w/uniq those input args. I just didn't get that far into arcs' internals last night.

-----

2 points by fallintothis 968 days ago | link

Still to do: make it varargs along both dimensions (xs and fs).

What do you mean by this? Something like the following?

  arc> (-> 1 2 /)
  2
From reading Clojure's source (https://raw.github.com/clojure/clojure/c6756a8bab137128c8119...), I think the following is an Arc translation of their macro:

  (mac -> (x . rest)
    (if (no rest)
        x
        (let (expr . more) rest
          (if more         `(-> (-> ,x ,expr) ,@more)
              (acons expr) `(,(car expr) ,x ,@(cdr expr))
                           (list expr x)))))
But it seems pretty screwy:

  arc> (macex '(-> 1 2 (fn (x y) (/ x y))))
  (fn (-> 1 2) (x y) (/ x y))
Probably a type system thing with Clojure's idea of seq?.

I like the simplicity of something like this:

  (mac -> exprs
    (reduce (fn (l r) (list r l)) exprs))
but it fails to capture the (-> 1 2 /) use-case.

Maybe this?

  (def -> exprs
    (reduce (fn (l r)
              (let ls (check l alist (list l))
                (if (~isa r 'fn) ; but what about callable objs?
                    (cons r ls)
                    (apply r ls))))
            exprs))

  arc> (def flip (f) (fn (y x) (f x y)))
  #<procedure: flip>
  arc> (-> 1 2 + [fn (x) (+ x _)] '(1 2 3) (flip map))
  (4 5 6)
Though at this point, may as well just model a stack machine in general. Or just use Factor. :)

-----

1 point by akkartik 968 days ago | link

Yeah, if I had to choose one axis over the other I'd rather have multiple functions than multiple arguments.

Here's the current varargs version of -> in wart:

  mac (transform x ... fs|thru)
    if no.fs
      x
      `(transform (,x -> ,car.fs) ,@cdr.fs)
Now you can call it like this:

  (transform 3 :thru (fn(_) (_ * 2))
                     (fn(_) (_ + 1)))
I think I'd support multiple args by using :thru as a delimiter:

  (transform 1 2 :thru (fn(a b) (a * b))
                       (fn(_) (_ + 1)))
But really, there's no need:

  (transform 1*2 :thru (fn(_) (_ + 1)))

-----

2 points by akkartik 968 days ago | link

"Or just use Factor. :)"

I love how I could switch programming models for the one line in this example where it made sense: http://rosettacode.org/wiki/Rot-13#Wart

-----

2 points by zck 967 days ago | link

Skimming through the comments, I haven't fallen in love with the thread-first operator, but I'm unfamiliar with it. Perhaps I'd become more used to it in time.

I would sometimes do this:

  (def recstring (test s (o start 0))
       (let helper (afn (i)
                        (and (< i (len s))
                             (or (test i)
                                 (self (+ i 1)))))
            (helper start)))

-----