Getting it

Posted on Mon 08 June 2009 in misc

Apparently according to href="http://c2.com/cgi/wiki?LispUsersAreArrogant">some learning
Lisp is a path to programming enlightenment. Although I'm not in that
school (elisp is about a better editor for me, not a way of life) I
did have a slight light-bulb illuminating moment today.
One of the jobs I have at the moment is maintaining some href="http://en.wikipedia.org/wiki/Simple_Network_Management_Protocol">SNMP
agent software. It's written in C with a hand hacked GTK GUI and very
sparsely documented. One of the tasks has been converting a bunch of
the code to abstract some of the SNMP tables into proper structures
which is a massive improvement on the dense mixture of sparsely commented OID
parsing/GUI update code. However I caught myself Copy & Pasting a lot of the
boiler plate code because most of it is identical apart from the
actual object parsing which looks like:
if (snmp_oid_compare(oid, oid_entry_A))
{
    obj->valueA = oid->val->integer;
}
else if (snmp_oid_compare(oid, oid_entry_B))
{
    obj->stringB = g_memdup(oid->val->string)
}
Clearly something has gone wrong when you find yourself copying and
pasting code. Really it should be possible to describe the mapping in
a data structure and have one common set of code for iterating a
table. I started wondering how such a structure would work:
myMagicMappingTable[]= {
    {oid_a, INT, offset(&magicObj->objA)},
    {oid_b, STRING, offset(&magicObj->objB)},
}
Well that would work OK, except the munging code isn't quite all that
boiler plate. The existing code isn't all straight mapping of SNMP
types to the C structure. Also using offsets into anonymous structures
is the sort of old-school C trick that one should try to avoid in
maintainable code.
As I thought on I considered a formulation involving C function
pointers in the table. This would have involved a lot of typing to
declare each function snippet before some shady casting tricks could
be pulled. "What I really need", I thought, "is some way to add
anonymous functions to my data structure". That's when the light bulb
went off and I recalled the meaning of href="http://en.wikipedia.org/wiki/First-class_function">first class
functions and the world made a little more sense.
So I can't recode this application into Lisp (or even elisp) but as an
exercise I coded up an approach to the problem in emacs to see if
things are making more sense. I ended up with:
(setq snmp-results (list
                    (list ".1.2.3.4.5" "value A")
                    (list ".1.2.3.4.6" "value B")
                    (list ".1.2.3.4.7" "value C")
                    (list ".1.2.3.4.8" "value D")))

(setq magic-object (make-hash-table :test 'equal))

(setq magic-map   '(
                    (".1.2.3.4.5" . (lambda (val obj)
                                      (puthash "thingA" val obj)))
                    (".1.2.3.4.6" . (lambda (val obj)
                                      (message "skipping thing B")))
                    (".1.2.3.4.7" . (lambda (val obj)
                                      (puthash "thingC"
                                               (format "value is %s" val)
                                               obj)))))


(let ((results snmp-results))
  (while results
    (let ((pdu (pop results))
          (magic))
      (setq magic (assoc (car pdu) magic-map))
      (if magic
          (funcall (cdr magic) (cdr pdu) magic-object)
        (message "Couldn't map:%s" pdu)))))

(maphash (lambda (k v) (message "k:%s v:%s" k v)) magic-object)
=>
 k:thingA v:(value A)
 k:thingC v:value is (value C)
 nil

And that code makes sense to me. I wonder if that means I'm starting to "get lisp"?