Arc Forumnew | comments | leaders | submitlogin
Interesting macro 'focus
10 points by drcode 5875 days ago | 15 comments
Hi- I've been using this one macro a lot lately and it seems very useful and "arc-like" to me.

It deals with the following common idiom:

  (let x nil
       (...traverse some complicated data...
           ...
              ...
                 (= x ...))
       x)
Here, we're searching some complicated data structure, looking for a certain item. When we find it, we assign 'x to it. Finally, we return x.

Here's my macro for capturing this idiom:

  (mac focus (var . body)
       (w/uniq (x y)
               `(withs (,x nil
                        ,var (fn ,y 
                                 (if ,y
                                     (= ,x (car ,y))
                                     ,x)))
                       ,@body
                       ,x)))
So basically, this macro is used for "focusing" on a specific data point in a complex data structure and retrieving it. It is a distant cousin of 'accum, but specialized for single items.

For instance, here we use it to find an odd number in a list:

  (focus f
         (map [when odd._
                    f._]
              *list-of-numbers*))
(of course, for a simple example like this you could use 'find, instead.)

One additional feature is that the function created (which would be 'f in the example above) is both a getter and a setter: If you don't pass the function a parameter it will return the previously assigned value.

This makes the 'focus command useful in many tricky situations. For instance, suppose you had a large list of numbers and you wanted to return the largest odd number in the list... With 'focus, this can be written as follows:

  (focus f
         (map [and odd._ (or (no (f)) (< (f) _)) f._] *list-of-numbers*))
This would be cumbersome to do without 'focus, (I think) expecially when dealing with a large list, where you don't want to create an intermediate list (such as by using 'rem first to strip out the even numbers)

BTW- I'd love to know if there's a way to address this idiom without my 'focus command. Please post in your comments alternate solutions that show why my macro is obsolete :)



3 points by absz 5875 days ago | link

I like it! Though it'd probably be better to use an optional parameter rather than a variadic function for what you're focusing on:

  (mac focus (var . body)
    (w/uniq (storage unbound)
      `(withs (,storage nil
               ,var     (fn ((o arg ',unbound))
                          (if (is arg ',unbound)
                            ,storage
                            (= ,storage arg))))
         ,@body
         (when (is arg ',unbound)
           ,storage))))
(And you'll notice that since arg is lexical, you don't need to make it a gensym.)

-----

4 points by drcode 5875 days ago | link

Your definition has a typo, I think- What you probably meant is:

  (mac focus (var . body)
    (w/uniq (storage unbound)
      `(withs (,storage nil
               ,var (fn ((o arg ',unbound))
                      (if (is arg ',unbound)
                          ,storage
                          (= ,storage arg))))
         ,@body  
         ,storage)))

-----

1 point by drcode 5875 days ago | link

I thought about that, but then thought it would prevent you from returning 'nil... but your solution handles that case.

I agree your definition is better.

-----

3 points by almkglor 5875 days ago | link

Similar to the 'breakable modifier:

  (breakable:map
    [when odd._
          (break _)]
    *list-of-numbers*)
Note however that the 'break function created by 'breakable is an early-out, i.e. it exits the 'breakable form.

I suggest you turn 'focus into say 'focusable so that the extra variable ref can be dropped and we can abuse the : syntax:

  (focusable:map
    [and odd._
         (or (no (focus))
              (< (focus) _))
         (focus _)]
    *list-of-numbers*)

-----

5 points by cchooper 5874 days ago | link

I really like the design of this because it wraps up an imperative pattern in a functional-looking guise. Nice work!

-----

4 points by rkts 5874 days ago | link

You've reinvented reduce.

  (reduce (fn (y x) (if odd.x x y)) xs nil)

  (reduce (fn (y x) (if (and odd.x (or no.y (> x y))) x y)) xs nil)

-----

4 points by drcode 5874 days ago | link

good point.

You're right from the standpoint that I hadn't noticed the similarity and should have. Your solution is probably the best yet for the example I gave.

But it's different in that reduce only works on simple lists- My main motivation for this type of command is for working with arbitrary and complex data structures, not just lists.

-----

3 points by rkts 5874 days ago | link

  (def greduce (iter)
    (fn (f xs y)
      (iter (fn args (= y (apply f y args))) xs)
      y))

  arc> (greduce.map + '(1 2 3) 0)
  6
  arc> (greduce.maptable (fn (y k v) (+ y v)) (obj 'a 1 'b 2 'c 3) 0)
  6
  ; etc...

-----

5 points by rkts 5873 days ago | link

I guess I should clarify this a bit. In other languages (e.g. OCaml), it's conventional for each data structure to provide an iterator and a fold/reduce function. In Arc, the convention seems to be to provide iterators but not folds. The above code shows that it's sufficient only to have iterators, and a left fold can be automatically derived.

Alternatively, we could have data structures provide a left fold and automatically derive an iterator:

  (def giter (fold)
    (fn (f xs)
      (fold (fn (y . args) (apply f args) nil) xs nil)))
Thus, (giter:greduce f) == f, and (greduce:giter g) == g.

-----

2 points by drcode 5873 days ago | link

thanks- This is interesting information for me.

-----

1 point by shader 5874 days ago | link

Interesting. I was just wondering recently what capabilities arc or cl had for traversing tree structures (since that is, after all what lisp is "all about") for doing something a la jquery; searching for arbitrary elements in a tree based on their name, or some attribute of their contents. I just haven't had the time to bother looking.

Seems like this would work for most cases, and it's pretty easy to make an arbitrary selector.

Did I miss anything? Are there any better options?

Also, how would you modify the data structure using this? I'm not too familiar with arc yet.

If you can modify it, I would presume it's done by editing-in-place via reference. How would you edit the tree via copying? i.e. return a new tree that's been modified?

-----

1 point by rincewind 5874 days ago | link

I used this in my m-expr reader, as a generalisation of do1:

  (mac w/res body
   " Define the result of this expression by calling the 'result' (captured!) 
     function inside. Calling result does not change the control flow, like in
     Delphi/Pascal."
     (w/uniq return
        `(withs (,return nil result [= ,return _])
                 ,@body 
                 ,return)))

  (def call-reader-w/delimiters (port start end fun)
   " Reads from port with fun, ensures that given delimiters are read before and 
     after, otherwise an error is raised"
     (w/res (expect port start 'delim)
            (result (fun port))
            (expect port end 'delim)))
Your odd-number example could be written:

  (best (fn (a b) (and odd.a (> a b))) *list-of-numbers*)
without an intermediate list

-----

1 point by drcode 5874 days ago | link

Yup, looks like 'w/res is very close to 'focus.

Your 'best alternative for my example is extremely clever, but doesn't work:

  > (= *list-of-numbers* (6 2 4 5))
  > (best (fn (a b) (and odd.a (> a b))) *list-of-numbers*)

  6
I knew something was wrong with it, but it took me like half an hour to realize the flaw :)

...it can of course be fixed with extra flagging (well, it'll still fail on only lists of even numbers by not returning nil) but then you'll lose the brevity advantage over my solution.

-----

1 point by rincewind 5874 days ago | link

sorry, I hadn't had access to an Arc then.

  (best (fn (a b) (and odd.a (or even.b (> a b)))) '(6 2 4 5))
as mexpr:

  best[[a;b]-> odd[a] and (even[b] or a > b); list[6;2;4;5]];

-----

1 point by drcode 5874 days ago | link

yes, still a pretty good solution.

-----