*-GET for dicts -- failure continuation versus default value Taylor Campbell (21 Aug 2003 01:50 UTC)

*-GET for dicts -- failure continuation versus default value Taylor Campbell 21 Aug 2003 01:50 UTC

Scott and I seem to disagree on this.  Here's a log of our debate about
it
on IRC tonight:  (I'm Riastradh; Scott is FoxFire)

<Riastradh> Dictionaries' *-GET should take an optional failure
continuation, not default value.
<Riastradh> Default values are easy to implement in terms of FKs, but
for many purposes (like calling ERROR in the FK), default values make
code messy -- e.g.:
<Riastradh> (dict-get some-dict some-key (lambda () (error "She's not
there!"))) ;; FK version
<Riastradh> (let ((maybe-value (dict-get some-dict some-key
special-not-there-token))) ;; default value version
<Riastradh>   (if (eq? maybe-value special-not-there-token)
<Riastradh>     (error "She's not there!")
<Riastradh>     maybe-value))
<FoxFire> Yes, but capturing that closure can be expensive
<FoxFire> And in the majority of cases, it can be written as:
<Riastradh> Sure, but the latter is equally expensive, if not more so
-- a closure must be consed _and_ a comparison be made.
<FoxFire> (unless (eq? token (dict-get some-dict some-key token))
<FoxFire>   (error "She's not there!"))
<Riastradh> But then you don't get the value.
<Riastradh> You can use COND, but then you're going to cons _two_
closures -- one implicitly by COND's expansion and one explicitly by
the success continuation.
<FoxFire> cond's expansion has no closure
<Riastradh> Yes it does.
<FoxFire> You would get one closure for the success continuation
<Riastradh> (cond (foo => bar) (else baz))
<Riastradh> expands to:
<Riastradh> (let ((key foo)) (if key (bar key) baz))
<FoxFire> not true
<FoxFire> you're thinking of case
<FoxFire> sarahbot: expand (cond (foo => bar) (else baz))
<sarahbot> ((lambda (|t_if-R9-X_m|)
<sarahbot>    (if |t_if-R9-X_m|
<sarahbot>      (bar |t_if-R9-X_m|)
<sarahbot>      (begin '#t baz)))
<sarahbot>  foo)
<Riastradh> See there, a lambda!
<FoxFire> Yeah, for the success continuation
<FoxFire> still only one lambda
<Riastradh> No.  I used BAR for the SK, which in fact didn't cons a
closure.
<Riastradh> If I did:
<Riastradh> (cond ((dict-get foo bar) => (lambda (baz) ...)) (else
quux))
<Riastradh> Well:
<Riastradh> sarahbot: expand (cond ((dict-get foo bar) => (lambda (baz)
quux)) (else zot))
<sarahbot> ((lambda (|t_ifkO7rY_m|)
<sarahbot>    (if |t_ifkO7rY_m|
<sarahbot>      ((lambda (|baz_ifGK5UY_m|) quux) |t_ifkO7rY_m|)
<sarahbot>      (begin '#t zot)))
<sarahbot>  (dict-get foo bar))
<FoxFire> Yeah, thats true
<FoxFire> But solveable with a macro
<Riastradh> How so?
<FoxFire> I don't think it justifies complicating the simpler cases
<Riastradh> Give me an example of a simpler case, where the value at
the key gets used.
<Riastradh> (i.e. your original example doesn't count)
<FoxFire> (let ([result (dict-get lookup-table key 0)])
<FoxFire>   .. some computation ..)
<FoxFire> Ie all the cases where we just want a default value, not that
a missing value implies an exceptional case.
<Riastradh> That's about as common as REDUCE is.
<FoxFire> I disagree.
<FoxFire> Thats the most common case for me.
<FoxFire> (/ (dict-get lut key 0) 2) for example
<Riastradh> I can't think of any instance where I've used that sort of
case.
<FoxFire> Or more schemely:
<FoxFire> (cons 'foo (dict-get dict key '()))
<Riastradh> Anyhow, a good compiler would optimize away the closure for
the FK.
<FoxFire> It would optimize the need to create a full closure perhaps.
<FoxFire> But we should be reasonably efficient on mediocre compilers
and no-compilers
<Riastradh> I don't think a few extra closures are going to affect the
performance of many programs.
<Riastradh> Those programs that it would affect on bad/nonexistent
compilers would be run using better compilers.
<Riastradh> Unless the programmer were an idiot, in which case blame
idiocy, and don't complicate exceptional case checking.
<FoxFire> The same could be said of creating the fk version out of a
default values version
<Riastradh> Non-exceptional case checking would only involve wrapping a
lambda around a value.
<Riastradh> Default values might involve complicated computations.  You
wouldn't want those to occur in the arguments to DICT-GET, so you'd
have to use the large and ugly method for them if there were no FKs.
<FoxFire> Now that is a more decent argument, but I would come back and
say if you expect a large complicated computation in an exceptional
case, it shouldn't occur as the argument to the function producing the
exception
<FoxFire> But rather in a handler outside the function
<FoxFire> Doing it otherwise convolutes the program flow
<Riastradh> No, in my last statement I wasn't talking about exceptional
situations at all -- I was referring to default values.
<FoxFire> Right, but if the default value takes a long time to compute,
then it should appear to happen outside the call to the (simple)
dict-get
<Riastradh> I find it much cleaner to deal with that sort of thing in
the FK, not only because it involves more complicated code, but it also
requires you to create special 'not-there' tokens.
<FoxFire> Also, the dictionary lookup may occur at a very low level.
It seems onerous for its completion to require another function call to
injected code
<Riastradh> Er, let me rephrase my sentence.
<FoxFire> I agree with the not-there token ugliness, but again, that is
only a small subset of a default value's usage.
<Riastradh> I find it much cleaner not to deal with that sort of thing
with ordinary default values, not only ...
<FoxFire> And it looks uglier to me to write (dict-get dict key (lambda
() '()))
<Riastradh> To wrap it in a simple thunk for that one sort of case?
<FoxFire> Its clear though that we probably aren't going to agree on
this, so you should bring it up on the list.

What do other Schemers think about this topic?