accessing deeply nested lists (XPath?)

Q&A's, tips, howto's
Locked
bairui
Posts: 64
Joined: Sun May 06, 2012 2:04 am
Location: China
Contact:

accessing deeply nested lists (XPath?)

Post by bairui »

I have a deeply nested tree of lists (which came from json (the i3-tag -t get_tree command from the i3 window manager, specifically)).

What is the best way in newLISP to drill down into various parts of this structure?

I have so far been using code like:

Code: Select all

((assoc "nodes" (((assoc "nodes" ((get_tree) (0 -1 (ref '("name" "VGA1") (get_tree) match)))) 1) 1)) 1))
but that feels horrible. I must be doing something very wrong. I am hoping for something beautiful and simple like XPath.

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Re: accessing deeply nested lists (XPath?)

Post by cormullion »

Can you use wildcards with match?

Code: Select all

(ref-all '(* ("cachePagesTrack" 200) * ) data match)
;->
((0 1 0 1 0 2 1) 
 (0 1 0 1 1 2 1) 
 (0 1 0 1 2 2 1) 
 (0 1 0 1 3 2 1) 
 (0 1 0 1 4 2 1) 
 (0 1 0 1 5 2 1) 
 (0 1 0 1 6 2 1))

bairui
Posts: 64
Joined: Sun May 06, 2012 2:04 am
Location: China
Contact:

Re: accessing deeply nested lists (XPath?)

Post by bairui »

Thanks, Cormullion.

I must have tried a hundred different ways to call (ref...) today with maddening ocassional successes and frustratingly more failures so coyly announced by newLISP simply as:

Code: Select all

nil
I used your code to have another go, and it worked. Here is my new (nodes...) function:

Code: Select all

(define (nodes beneath_name in_list)
  ((assoc "nodes" (ref (list '* (list "name" beneath_name) '*) in_list match true))1))
Perhaps there are still places where that can be cleaned up?

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Re: accessing deeply nested lists (XPath?)

Post by cormullion »

Looks OK - soon you'll be master of the black arts of match!

bairui
Posts: 64
Joined: Sun May 06, 2012 2:04 am
Location: China
Contact:

Re: accessing deeply nested lists (XPath?)

Post by bairui »

...does that come with a cape and hat? 'cos if there's a cape and hat, i'm in! ...ok, i'd settle for just the hat...

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Re: accessing deeply nested lists (XPath?)

Post by rickyboy »

bairui wrote:Perhaps there are still places where that can be cleaned up?
I don't know. Your latest one is pretty clear. You could try defining it as a macro -- to clean up the look of the exp-key, and then use (lookup ...) instead of ((assoc ...) 1).

Code: Select all

(define-macro (nodes beneath_name in_list)
  (letex ($B beneath_name $L in_list)
    (lookup "nodes"
            (ref '(* ("name" $B) *) $L match true))))
Here's your latest (for the purpose of a side-by-side comparison).

Code: Select all

(define (nodes beneath_name in_list)
  ((assoc "nodes" (ref (list '* (list "name" beneath_name) '*) in_list match true))1))
You could code it either way and it would be good; so I don't know how much of an improvement you would consider the macro to be. In the macro version, when you have to re-read your source code later, you can read the exp-key faster; however the macro has more lines than yours. I personally don't mind the line breaking -- it helps me to parse the code in my head as I'm reading it, but you might find that annoying. :))
(λx. x x) (λx. x x)

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Re: accessing deeply nested lists (XPath?)

Post by rickyboy »

If your "clean up" goal goes to minimizing the "code noise", then this next suggestion might be for you. It involves getting rid of the letex form in the body of the macro definition for nodes. If you do this, you can express your function/macro like this.

Code: Select all

(definm (nodes beneath_name in_list)
  (lookup "nodes" (ref '(* ("name" beneath_name) *) in_list match true)))
Do you like this better (as far as code readability goes)?

It just expands and evaluates to

Code: Select all

(lambda-macro (beneath_name in_list)
  (letex (beneath_name beneath_name in_list in_list)
    (lookup "nodes" (ref '(* ("name" beneath_name) *) in_list match true))))
with the help of this "secret sauce":

Code: Select all

(define-macro (definm)
  (letex ($MFORM (args 0)
          $LARGS (apply append (map (fn (a) (list a a)) (1 (args 0))))
          $LBODY (1 (args)))
    (letex ($MBODY (letex ($$LARGS '$LARGS $$LBODY '$LBODY)
                     (cons 'letex (cons '$$LARGS '$$LBODY))))
      (define-macro $MFORM $MBODY))))
:-)

[WARNING: I didn't test this code; so use it at your own risk. It's just for illustration purposes ... and for our fun and enjoyment.]
(λx. x x) (λx. x x)

bairui
Posts: 64
Joined: Sun May 06, 2012 2:04 am
Location: China
Contact:

Re: accessing deeply nested lists (XPath?)

Post by bairui »

Wow. That's brilliant, rickyboy! Thanks for taking the time to show and explain those ideas to me. That is exactly what I was looking for (but didn't know what it would look like) when I asked for places to improve my code. I am new to lisp, so knowing when to reach for a macro is still beyond my reach at this stage. I will study your code... after work today... and see if I can't +1 my macro-fu.

Some off the cuff observations before I do:

I like the look of the 'clean' (ref...) inside the macro form.
I don't mind splitting that onto its own line; it does aid readability.
I don't think I've tried (lookup...) so, more exploration needed there.
The macro generating macro is going to take me a while to pick apart. Awesome. :-D

cormullion
Posts: 2038
Joined: Tue Nov 29, 2005 8:28 pm
Location: latiitude 50N longitude 3W
Contact:

Re: accessing deeply nested lists (XPath?)

Post by cormullion »

I think rickyboy gets to wear the ninja costume this weekend...

Image

bairui
Posts: 64
Joined: Sun May 06, 2012 2:04 am
Location: China
Contact:

Re: accessing deeply nested lists (XPath?)

Post by bairui »

Ah... a snag I hit in the macro forms, rickyboy, is the resulting order of execution when called on a nested call of itself:

Code: Select all

(define (get_workspace_trees , x)
  (let (x '())
  (dolist (output (get_active_output_names))
    (push (list output (nodes "content" (nodes output (get_tree)))) x))
  x))
The code for the "content" block executes before the `output` block. Am I doing it wrong? Where to from here? o_O

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Re: accessing deeply nested lists (XPath?)

Post by rickyboy »

I'm not at a proper computer, which is to say that I'm not at a computer that runs newLISP, but try this:

Code: Select all

(define-macro (nodes beneath_name in_list)
  (letex ($B beneath_name $L (eval in_list))
    (lookup "nodes"
            (ref '(* ("name" $B) *) $L match true))))
That is, in_list needs to be evaluated in the letex binding to get the evaluation order you want.

I'll look at it later today. Btw typing this message in on an iPhone is oh so painful. Ahhhh!

In the meantime, I defrock myself and turn in my ninja costume in ignoble shame. Hahahahahahahaha!
(λx. x x) (λx. x x)

rickyboy
Posts: 607
Joined: Fri Apr 08, 2005 7:13 pm
Location: Front Royal, Virginia

Re: accessing deeply nested lists (XPath?)

Post by rickyboy »

OK, after some thought, here is my present position on the nodes application. Don't fool with the macro. Once I started getting down the road of basically having to design my own evaluation scheme, e.g. making the second argument (in_list) eager, I was staring at a total lose. You don't want to do that because, in general, you can't anticipate all the usages of such a thing that will break your application. Better to go with a scheme that has easy semantics that can cover all your bases. I believe that going back to your function representation will get us back into winnage.

That said, you might be surprised to see what I am suggesting now:

Code: Select all

(define (nodes beneath_name in_list)
  (or (lookup "nodes"
              (or (ref (list '* (list "name" beneath_name) '*)
                       in_list match true)
                  '()))
      '()))
So, I'm giving you something more complex, and now ref's first argument is noisy again. Oh well. On the other hand, you don't have to worry about special evaluation schemes.

So, what is the business with the added (or ... '()) wrappers? The answer is that it's a typing thing and designed to avoid runtime typing errors. lookup is expecting to see a list in its second argument; however, if ref ever fails to match, it will return nil, not a list. So, the or wrapper is in there to ensure a list comes out of that puppy.

The same typing issue is true for nodes, which also expects a list in its second argument. But also, nodes's return value should always be a list. If this typing scheme holds, this means that you can now safely do nested node calls — as in your code sample — not being concerned about tripping a runtime typing error like "ERR: list expected : nil".

I hope that helps a little, and sorry about leading you astray earlier; however, I too learned a valuable lesson. Let's see if I'll remember it though. :)
(λx. x x) (λx. x x)

bairui
Posts: 64
Joined: Sun May 06, 2012 2:04 am
Location: China
Contact:

Re: accessing deeply nested lists (XPath?)

Post by bairui »

It is the wise man who chooses another direction when he sees he's on the wrong path. Well played, rickyboy. Intellectually, I get why the macro form is not playing nice for us, but I lack the necessary coalface time at the keyboard to properly internalise that lesson. This is an area of my lisp that will take much practice. Trying to learn macros before knowing lisp in the first place is... hard (read: foolish :-/ ).

To your new solution: nice. I hadn't given any thoughts to type checking and consistency. Good to learn.

Again, thanks for your tutelage. Keep the ninja suit handy; you look good in black. :-)

Locked