accessing deeply nested lists (XPath?)

Started by bairui, January 03, 2013, 11:22:38 PM

Previous topic - Next topic

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:


((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

#1
Can you use wildcards with match?


 (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

#2
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:


nil

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


(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

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

bairui

#4
...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

#5
Quote from: "bairui"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).


(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).


(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

#6
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.


(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


(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":


(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

#7
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

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



http://imgur.com/lbb9u">

bairui

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


(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

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


(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

#11
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:


(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

#12
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. :-)