Subject: Re: Destructors
From: Erik Naggum <erik@naggum.net>
Date: Sat, 29 Jun 2002 04:05:07 GMT
Newsgroups: comp.lang.lisp
Message-ID: <3234312306448848@naggum.net>

* Joel Ray Holveck
| Every time I see a question about finalization, I hear lots of admonition
| about what it shouldn't be used for.  So, what would be an appropriate use of
| finalization?

  In my somewhat limited experience, it is not useful without "weak pointers",
  which are objects the weak pointers to which "vanish" when they are the only
  pointers that keep it alive for purposes of garbage collection and the object
  is therefore not kept alive, either.  Sometimes, you would want a system that
  uses weak hashtables for cached results of some sort, meaning that until the
  garbage collector runs, you can reference an object through a weak pointer,
  but if you have had no use for it at the time garbage collection occurs, it
  will effectively be tossed.  This is a very useful feature, but if you want
  to make the most of it, you might want some concomitant information that
  should be alive only when the object itself is alive, but which you would not
  want to tie together with a lot of pointers all over the place, because that
  would defeat the purpose of weak pointers.  Instead of letting things point
  to the weak object, you let them reference them indirectly and then let the
  weak object know.  When the weak object is tossed, it will know how to remove
  any indirect traces of itself with it, that would either not vanish on their
  own at all or which would need a test for whether their target had vanished.
  (Weak pointers magically turn to nil after garbage collection.)

  This may be very abstract and sound rather weird, but for a more intuitive
  example, think of human memory.  Forgetting is a rather important feature of
  human memory, which is extremely underrated.  Forgetting the conclusions when
  the observations are invalidated takes conscious effort in most people.  You
  find lots of people who "learn" something, then integrate that knowledge with
  something else and come up with a conclusion of some sort, which they tend to
  believe even after the first thing they learned turned out to be all wrong.
  It would be much better if the first thing you learned had a normal pointer
  to it on its own and weak pointers to the conclusions that were based upon it
  (because they should seamlessly vanish if invalidated, too), so that if you
  dropped the normal pointer, the finalization would know how to invalidate the
  conclusions based upon it as it went.  A simpler example is perhaps that you
  worked out a difficult probability problem a decade ago and then you remember
  that you solved it, but not what the solution was.  I may not be an old man,
  in fact I'm pretty sure I'm not, but working through old math textbooks can
  be a very humbling experience, to be repeated once very decade or so so you
  at least still know that your brain works, but the kind of ridiculous despair
  you may feel the first time in remembering that you solved it and trying so
  much harder to remember it than to work it out again, is probably very close
  what a Common Lisp program feels when it goes looking for the cached result
  of a three-second long computation and the weak pointer only goes "no".  On
  the flip side of Alzheimer's, there are many ways to get screwed if you
  remember something for too long, too.  Many Lisp programmers experience that
  the system behaves differently after a cold start because they forgot that
  they have done something the system had not forgotten along with their memory
  of having done it.  Emacs (Lisp) users sometimes have this problem, a useful
  keybinding that isn't, a mouse wheel that beeps or scrolls the wrong window,
  forgetting the last keyboard macro you used and thinking you invoked the
  previous one.  Such stuff.  The best expression of this problem was provided
  by an acquaintance of mine when his X server crashed and all remote sessions
  died, his multiple Emacsen croaked, his log output windows closed, and he was
  facing the standard X root window, all grey.  "My context!", he said quietly.
  Not that finalizers would have helped him.

  When I restrict the usefulness of finalization to weak pointers, it is
  because I strongly favor explicit cleanup for normal objects.  However, there
  is a whole world out there who do not.  The C++ crowd, for instance, have a
  moderately useful feature in that the destructors of stack-allocated objects
  are called when the stack is, for lack of a precise term, unwound.  (It is
  not, of course, unwound the way Common Lisp does it.)  If something like this
  is needed in Common Lisp, a competent Common Lisp programmer would simply
  design a new binding form that effectively calls the finalizer when you leave
  scope unless the object has been passed out of the scope in, say, a returned
  value.  Such a programming style could, for instance, be used with resources,
  where a "resource" is an object of a type that is picked off a pool of weak
  pointers to previousely deallocated objects when you need one (or a fresh
  object is allocated) and effectively returned to the pool when you exit
  scope.  The "finalization" would be to return it to the pool, but you would
  not do that if you were returning it.  Figuring out such lifetime thingies is
  computer work.  A similar stunt _could_ be used when dealing with streams:
  Suppose you close all streams upon scope exit that you have not returned to
  your caller as open streams.  You would want that stream to be closed when
  the caller just dropped it sometime later.  Note that the file descriptor in
  Unix is a resource of the above-mentioned kind and that the operating system
  has a finalization routine that is invoked upon exiting the program.  You
  would want similar precautions in a well-honed Common Lisp program.  Leaks
  are bad, and finalization may be used to ensure that you find them, but in
  order for leak detection to really work, you want to keep track of things
  that might leak with weak pointers.

  Finally, note that implementationally, finalizers are easily implemented with
  weak pointers -- instead of just turning the weak pointer to nil when only
  weak pointers point to the object, you would call the finalizer first.
-- 
  Guide to non-spammers: If you want to send me a business proposal, please be
  specific and do not put "business proposal" in the Subject header.  If it is
  urgent, do not use the word "urgent".  If you need an immediate answer, give
  me a reason, do not shout "for your immediate attention".  Thank you.