lua-users home
lua-l archive

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


On 2016-07-22 00:33, Jonathan Goble wrote:
> Perhaps what Lua *really* needs to solve this problem, then, is a
> __bool metamethod that, if present, is called when the object is used
> in a boolean context (such as `if`, `and`, and `or`) and returns
> either true or false, replacing the default true. Then one can take
> these `null` singletons and slap a `{__bool = function(t) return false
> end}` metatable on them, allowing them to gain the benefits of `nil`
> without the drawback of `nil`.
> 
> What does everyone else think about a __bool metamethod?

Yes!!1!  I have built & used that before (as __false).

Experience report: (I loved it!)



How can it be done?

What I did was (IIRC) turn luaO_isfalse(x) from a macro in lobject.h(?)
to a function luaV_isfalse(L,x) in lvm.c, adjust what needs adjusting
(ORDER TM, add decl. to lvm.h, add the L arg in all (2 or 3) use sites,
remove no-longer-valid short circuitry in lcode.c ("" or x, 0 and x,
...)) and then defined __false/luaV_isfalse to:

 - handle nil, false, true as currently (false,false,true)
otherwise, check metavalue:
 - a boolean or nil: __false is true -> value is false
                     __false is false (or nil) -> value is true
(The true/false order was chosen such that you're not jumping around in
the truth table: (__false = nil) ~ (__false = false) whereas it would
probably be (__bool = nil) !~ (__bool = false).)
 - a function: call it, check result for truth (recursively if needed)
 - a value: is it rawequal()? then it's false, else true.

This was surprisingly easy (but I seem to have botched some strange
case, because rarely I got a segfault... but it worked well enough for
experiments, the standard Lua tests & my extension checking __false also
ran cleanly.)



Why did I use this definition?

 -  function, because meta_method_
      -  recursion: so it's possible to experiment & see if it's needed
 -  boolean: _all_ values of this "metatype" are true/false
             (e.g. Null, False, ...)
 -  rawequal: there's one canonical falsish value, e.g. (if you like C)

        getmetatable"".__false = ""
        debug.setmetatable(0,{__false = 0})

        (or similar stuff for custom types)

I found that the boolean/rawequal cases got rid of >=95% of all uses,
and I personally found '__false = true' instead of '__false = function()
return true end' and '__false = Null' instead of '__false = function(x)
return rawequal(Null,x) end' to be more readable.

The function version was only necessary for lazy evaluation (trigger
evaluation (which changes the metatable), then check the result with the
new value('s contents) (& new metatable/-method) – this needs(?)
recursion as well), or shoving some complex predicate (that I can't
remember anymore) into the metamethod & out of view, or for really silly
stuff like

 coin=setmetatable({},{__false=function() return math.random(2)==1 end})

(where 'coin(0.5)' or 'coin()' would arguably be clearer than 'coin' at
use site... it's only nice to confuse people.)



What's possible?

Boxed values are a lot nicer to handle.  If you're gluing (or building)
some other system with its own notion of truth, you're probably boxing
all values in some form(*).  The cleanest way so far was to either not
use Lua's control structures or to have some predicate p(x) --> bool and
wrap that around all expressions in control structures (and never forget
it or else  while False do end  runs forever etc.).

By extension of that, language-wide lazy evaluation becomes a lot more
usable/invisible.

Custom truth values (True/False/FileNotFound) or other markers can be
made.  An error value can be false, yet still contain extensive error
information (Think Haskell's Either with Left/Right or Coq's sum/inl/inr
etc.).

(And probably much more that I didn't try.)



How does other (non __false-aware) code cope?

It depends.

If you change primitive type's __false, all bets are off.  (Doing that
is awesome when gluing stuff - suddenly both languages behave the same
in that aspect, you no longer have to mentally switch around as much.
But you'll be rewriting/adjusting any libraries you're using - a
trade-off that's worth it IMHO.)

If you leave primitive types alone, then it mostly depends on what your
falsish values are used for.  Stuff that never leaves your library /
never gets passed to another library is safe.  Alternate error/"absence"
values (like null) get replaced by 'x or "foo"' and miss 'if x then ...'
branches, which _tends_ to be the "right thing".  For other things,
that's probably wrong.

Storage (tables & other data structures) works just fine if the check in
that library is 'x ~= nil' but breaks (and is already broken) if the
check is just 'x' (which already fails to handle plain 'false').

In general, library code probably needs some small adjustments to
clarify what's checked for (which tends to make the code state more
precisely what's the relevant property).  Some things might be
completely broken, but I didn't run into such a case.



Any other disadvantages?

The three one-line code snippets above.  Eeek!  I don't want something
like that done to me from a library.  (But if I'm changing Lua to behave
more like some other system I'm gluing/building, that's totally cool!)

Every truth check has to check for metamethod presence & gets a tiny bit
slower.  (I did not notice any slowdown, even for large programs not
using this feature at all, but didn't benchmark.)

(And probably more that I didn't run into.)

"Complexity"? -- debatable.  The patch is simple, and it gets rid of
_a lot_ of translation logic in the code (if you need it).  It makes
control structures configurable, which increases their utility, but also
increases (if only perceived) "uncertainty".



Any notable non-solution alternatives?

An operator '?x': Seems like it might get the job done but actually
doesn't -- only code that knows of the presence of non-standard values
and checks '?x' instead of 'x' will be compatible, and it's not much
shorter than 'p(x)'.  Tried it, doesn't improve on what we have.



-----

[For my long-term goal (i.e. years, if I ever get there) of building
some dependently-typed system in Lua (to finally get rid of the
hard-to-port several-hundred-MB monsters that are Haskell and Coq), I
will almost certainly use this whether it's part of the language or not.
 Not using __false and having p(x)-es all over the place would be
madness...]


(*) - Using the same function for several type's __eq is communicating
clearly that this comparison function can handle both types.  Yet, __eq
refuses checking equality across "primitive types" even if that was
useful within a "logical type" built across several "primitive types".
So if you have e.g. mixed table/userdata (or table/boolean etc.)
"logical types", you must convert or box values to get equality.
Compare Eq/JMeq if you've heard of that before.  (I'll write some more
on that once I find the time... soon, hopefully.)