From: Daniel P. Katz
Subject: Problem computing places (generalized references)?
Date: 
Message-ID: <87smwnjk57.fsf@world.std.com>
In the course of writing a simple 20-questions type of program (for
Norvig's PAIP, Exercise 3.5), I ended up with the following code
function for asking questions:

(defun query (node)
  "Ask the question about the NODE and ask again or update as appropriate."
  (case (ask-query node)
    ((y yes) (if (node-yes node)
                 (query (node-yes node))
                 (setf (node-yes node) (ask-answer))))
    ((n no) (if (node-no node)
                (query (node-no node))
                (setf (node-no node) (ask-answer))))
    (it (format t "You got it!!!"))
    (t (query node))))

This code works well enough (indeed, it's almost identical to what
Norvig proposes as an answer), but I find myself dissatisfied by the
repetition of the logic represented by

           (if (node-foo node)
               (query (node-foo node))
               (setf (node-foo node) (ask-answer)))    

in two of the cases.




I tried to come up with a function to encapsulate this logic, but had
problems since I couldn't find a way to construct the generalized
variable for the SETF given the function object and the node:

(defun query (node) 
  "Ask the query about the NODE and ask again or update as appropriate."
  (case (ask-query node)
    ((y yes) (process-node #'node-yes node)))
    ((n no)  (process-node #'node-no node)))
    (it (format t "You got it!!!"))
    (t (query node))))

(defun process-node (accessor node)
  "If the ACCESSOR element of the NODE is not NIL, then query the
sub-node, else ask for an answer to create a new sub-node."
  (if (funcall accessor node)
      (query (funcall accessor node))
      (setf (***WHAT GOES HERE?***) (ask-answer))))  ; What to do?


After some reading in the HyperSpec and CLTL2, I did try

 (setf (apply accessor (list node)) (ask-answer)) 

for the SETF form, but got complaints from CMUCL about an error in
macroexpansion since "Setf of Apply is only defined for function args
like #'symbol." which makes some sense, I suppose.





After a while I realized that I could use a macro to achieve this
effect[1] via:

(defun query (node) 
  "Ask the query about the NODE and ask again or update as appropriate."
  (case (ask-query node)
    ((y yes) (process-node (node-yes node)))
    ((n no)  (process-node (node-no node)))
    (it (format t "You got it!!!"))
    (t (query node))))

(defmacro process-node (form)
  "If the FORM is not NIL, then carry on with another question, else
ask for the answer."
  `(if ,form
       (query ,form) 
       (setf ,form (ask-answer))))

and could even clean up the namespace a bit by using macrolet (since
process-node is not a terribly general concept in this context):

(defun query (node) 
  "Ask the query about the NODE and then drill deeper or update as 
appropriate."
  (macrolet ((process-node (form)
               `(if ,form
                    (query ,form) 
                    (setf ,form (ask-answer))))) 
    (case (ask-query node)
      ((y yes) (process-node (node-yes node)))
      ((n no)  (process-node (node-no node)))
      (it (format t "You got it!!!"))
      (t (query node)))))

but I am not really happy about the macro solution.  Although it is
true that there is no longer the code duplication of the purely
functional approach, the macro code is (to my eye, at least) less
perspicuous than the initial functional code.




So does anyone have any suggestions for how I might revive the
functional approach to PROCESS-NODE?

Thanx.

Dan

From: Matthew Danish
Subject: Re: Problem computing places (generalized references)?
Date: 
Message-ID: <20021224143459.C14951@lain.cheme.cmu.edu>
Pass a writer as well as a reader function.  Though in this case I don't
see what's wrong with the macro, really =)

-- 
; Matthew Danish <·······@andrew.cmu.edu>
; OpenPGP public key: C24B6010 on keyring.debian.org
; Signed or encrypted mail welcome.
; "There is no dark side of the moon really; matter of fact, it's all dark."
From: Gabe Garza
Subject: Re: Problem computing places (generalized references)?
Date: 
Message-ID: <87d6nrtdhx.fsf@ix.netcom.com>
·····@world.std.com (Daniel P. Katz) writes:

> I find myself dissatisfied by the repetition of the logic
> represented by
> 
>            (if (node-foo node)
>                (query (node-foo node))
>                (setf (node-foo node) (ask-answer)))    
> 
> in two of the cases.
> 
> I tried to come up with a function to encapsulate this logic, but had
> problems since I couldn't find a way to construct the generalized
> variable for the SETF given the function object and the node:

If node is an instance of a class, you can use SLOT-VALUE to set and
get slot values, e.g.,:

(defun process-node (node slot-name)
  (if (slot-value node slot-name)
      (query (slot-value node slot-name))
      (setf (slot-value node slot-name) (ask-answer))))

Although the spec leaves the behaviour of SLOT-VALUE on structures
undefined, supporting SLOT-VALUE on them is fairly common so you can
also use the above for a struct on CMUCL (although it's not gauranteed
to be portable).

Gabe Garza
From: Kaz Kylheku
Subject: Re: Problem computing places (generalized references)?
Date: 
Message-ID: <cf333042.0212242026.4f13a2c8@posting.google.com>
·····@world.std.com (Daniel P. Katz) wrote in message news:<··············@world.std.com>...
> (defun process-node (accessor node)
>   "If the ACCESSOR element of the NODE is not NIL, then query the
> sub-node, else ask for an answer to create a new sub-node."
>   (if (funcall accessor node)
>       (query (funcall accessor node))
>       (setf (***WHAT GOES HERE?***) (ask-answer))))  ; What to do?

You can't pass places by reference using their accessor function. A
place is normally defined by a pair of functions: a setter and a
getter. The SETF macro acts as a compiler which recognizes the phrase
structure of the getter call, and translates it to a setter call. To
do that it has to know what setter goes with the getter. In the above
example, all you have is an anonymous function, a closure.

If you work a little bit, you can use closures to implement a place
abstraction. Define a struct type (or maybe CLOS class) called PLACE.
The type has two slots: a getter closure and a setter closure. You can
make it so that, say, (deref <place-instance>) calls the getter, and
(setf (deref <place-instance>) <value>) calls the setter. Define some
handy constructors for making places in various situations.
From: Erik Naggum
Subject: Re: Problem computing places (generalized references)?
Date: 
Message-ID: <3249842650899575@naggum.no>
* Daniel P. Katz
| This code works well enough, but I find myself dissatisfied by the
| repetition of the logic represented by
| 
|            (if (node-foo node)
|                (query (node-foo node))
|                (setf (node-foo node) (ask-answer)))    
| 
| in two of the cases.

  Could you explain why you find yourself dissatisfied by this?

  An argument against gratuitous abstraction is that apparently minor
  differences betray incidental similarity.  The branches /are/ quite
  similar, but if that is the end of the story, leave it be.  If you
  predict changes and adopt abstractions to simplify them, consider
  the consequenes of asymmetric changes.  Speaking from years of
  experience, I predict the probability of symmetric changes (in the
  macro) to be below 1 in 1000.  When you do make changes, you will
  either revert to the dissatisfying code and make straightforward
  changes or you will want to protect your abstraction investment and
  make an unnatural and more complex change that you will recognize
  as stupid and you will have another minor battle with yourself over
  which is the optimal form of expression, only wasting more time.

  The expert programmer intuitively recognizes code that needs tuning
  to improve system performance, after ample experience and mistakes.
  The novice believes his grasp of the computer and of his program
  will guide him intuitively as he decides to optimize his code, but
  the experience of his elders strongly suggests that he should not
  optimize inefficient algorithms and half-solutions, but learn more
  efficient algorithms and how to implement them correctly.

  Similarly for the writing process, but the novice believes benefits
  he can see are benefits to exploit regardless of the resources that
  will consume.  The general problem is the metric for "better code".
  The na�ve optimization may well improve something, but experienced
  programmers appreciate its costs and make a more balanced call on
  when to spend time on it.  The novice programmer views his time as
  inconsequential compared to the quality of the code, which suggests
  a lack of focus on priorities.  Habits adopted while still forming
  are hard to change when they shaped what one has become.  The time
  you spend weighs heavier as you grow wiser; older programmers value
  their time over irrelevant perfection, and thereby attain relevant
  perfection sooner.  You cannot get everything right at once, so set
  your priorities to maximize /overall/ success in finite time.

  Originally military disciplines now vital to business, logistics
  and triage embody the complex tasks of having the resources you
  need available as and when you need them and of spending them where
  they will maximize the end result.  Novice programmers are lousy at
  both disciplines and their concept of the end result is fuzzy if
  they have one at all.  Tragically, those who teach programming do
  not value their students' time, either, and encourage wastes on
  trivialities that make them feel good about local improvements at
  the cost of teaching the wrong values, lack of focus on the end
  result, and therefore no understanding of the process of achieving
  the optimal end results with minimal waste.

  I implore you to waste no more of your time on such trifle issues
  and instead appreciate that a simple cut and paste will save you
  time.  If you absolutely have to make an abstraction before you can
  move on to more worthwhile tasks, make a local one with `macrolet�.
  A global macro with a general name like "process-node" which has
  such a limited scope makes your code harder to read.  Experienced
  programmers will search for the purpose of this abstraction and as
  they find none other than fickle aesthetics, will wince at it.

-- 
Erik Naggum, Oslo, Norway

Act from reason, and failure makes you rethink and study harder.
Act from faith, and failure makes you blame someone and push harder.
From: Rahul Jain
Subject: Re: Problem computing places (generalized references)?
Date: 
Message-ID: <87vg1h9r89.fsf@localhost.localdomain>
·····@world.std.com (Daniel P. Katz) writes:

> This code works well enough (indeed, it's almost identical to what
> Norvig proposes as an answer), but I find myself dissatisfied by the
> repetition of the logic represented by
> 
>            (if (node-foo node)
>                (query (node-foo node))
>                (setf (node-foo node) (ask-answer)))    

If you are using CLOS classes, you can do the following:

(with-accessors (foo node-foo)
  (if foo
      (query foo)
      (setf foo (ask-answer))))

--
Rahul Jain