Subject: bug#7086: `booleanp' return value is multi-valued list

From: MON KEY <[email-masked]>
Date: Fri, 24 Sep 2010 18:00:26 -0400

Lawrence Mitchell
>> This one matters. It is the penultimate Emacs lisp predicate.
>> It tests for a boolean value yet it returns a list.

> Which you can happily dispatch on:
>
> (if (booleanp foo)
>     (if (null foo)
>         (do-something-if-foo-is-nil)
>       (do-something-if-foo-is-t))
>   (do-something-if-foo-is-neither-nil-nor-t))

Not happliy if happiness means employing nested ifs conditioned on the variadic
return value of a boolean.

> It's hard to see what else is required.

Not much, besides not having to reinvestigate the boolean status of foo
repeatedly...

The version of `booleanp' supplied at the bottom of this message does what I
think is expected.

My point is, your example illustrates a single branch where the return value of
`booleanp' isn't cached away in some local var. There are certainly situations
where holding onto that value for subsequent interrogation later is desired.
With your example one is required to repeat the idiom:

  (if (booleanp foo)
      (if (null foo)
          {...}
        ))

What I am suggesting is that in many situations where one wants to know if the
value of foo is boolean it is often desired to reexamine that value more than
once either in the same form or enclosing environment. So, it makes sense (and
is cleaner than the repeated branching) to store `booleanp's return value a
local var for later (re)examintation, also by storing foo in a local variable we
can pass its _state_ around as data However with regards the latter, doing it
cleanly requires that `booleanp' return a list with a consistent form for all
possible states of foo; `t', `nil', and not-a-boolean. Which FWIW is the
"reflection" i was referred to earlier though apparently Eli mistook this to be
of the navel gazing type.

As it is now with `booleanp's current multivariant return values:
 { (t) | (nil t) | nil }

even when we cache the value it is still required to deconstruct the list
elements imediately because it is unreasonable to delay taking the car of (nil
t) in lieu of some later check for (null foo) because foo might've changed since
then. Which BTW is why your solution imediately (re)investigates foo just after
interrogating it.  IOW, as currently configured no matter what the return value
of `booleanp' subsequent "dispatching" still requires the data that `booleanp'
returned your (null foo) being an illustrative case.

Changing booleanp to consistently return a two elt list for each of its three
possible results would address this and is a much better solution than requiring
all callers to repeatedly (re)xamine foo just to parse/verify the junk booleanp
currently throws out.

> Calling a function expecting an argument without one is an error,

Your missing my point.

W/re `booleanp' how the function call occurs is irrelevant. The error isn't
calling the function without an arg, the locus of the error was that the
interpreter evaluated the form _without_ signalling.

(defun tt--bool-nil (())
  (booleanp ()))

(disassemble 'tt--bool-nil)
;=> Debugger entered--Lisp error: (error "Compilation failed")

(defun tt--not-gonna #(x)
  x)
;=> Debugger entered--Lisp error: (invalid-read-syntax "#")

> irrespective of the function involved.

Why do you assume the call will happen as a function and not instead inside of
or as a macro, or in a closed over lexical environ on the heap, is
delayed, etc.?

Which is what I am suggesting with:

 "These are just the types of situations where interrogating a boolean
is what is
 wanted."

> Do you in fact want:
> (require 'cl)

Well, even when in fact i do, the byte-compiler would protest that in fact what
I wanted was:

(eval-when-compile (require 'cl))

Besides, the cl.el package is certainly not a panacea here given the lack of
multiple value return. This said, following is what is wanted:

(defun new-booleanp (putative-boolean)
  (or (and (eq putative-boolean t) '(t t))
      (and (eq putative-boolean nil) '(nil t))
      '(nil nil)))

(defun multiple-booleanp (obj)
  (values-list (new-booleanp obj)))

(multiple-booleanp t)
;=> t, t

(multiple-booleanp nil)
;=> t, nil

(multiple-booleanp "bubba")
;=> nil, nil

And barring the cold chance in hell that Emacs will ever return multiple values
I'd settle for:

(multiple-booleanp t)
;=> (t  t)

(multiple-booleanp nil)
;=> (t nil)

(multiple-booleanp "bubba")
;=> (nil nil)

> Lawrence Mitchell

--
/s_P\