[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

*-GET for dicts -- failure continuation versus default value

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?