With the reassurance of Roberto that loadin() would do what I previously needed
setfenv() for, I set about looking at Lua 5.2 today with a view to learning a
little more about these handy looking lexically scoped environments.
Unfortunately I ran immediately foul of the problem that perfectly natural (to
me and thus I am only able to assume to others) constructions failed and left
me cold. The semantics of error handling in one case left me slightly
confused.
Starting with the error case since that's the most easily fixed I expect:
in not_present_here do
a = 1
end
will report an error on the assignment line that it attempted to index
"(environment)" which was nil. Now, while I imagine that someone at some point
thought "oooh, nil environments would be useful", surely more useful would be
that the 'in ... do' line cause an error along the lines of "Unable to set
environment: expected table, got nil" ?
Moving on from that, I immediately saw the benefit of making some of my
initialisers neater. In particular I liked that I could do things like:
local actor = NewActor()
in actor do
givenname = "Hector"
familyname = "Yargh"
age = 27
fullname = givenname .. " " .. familyname
end
But then I thought to myself: "Wouldn't it be nicer if I could use a localised
function for generating the full name, after all, some cultures prefer
"Givenname FAMILYNAME" and some use "Familyname Givenname" etc. But I couldn't
do:
fullname = formatFullName(givenname, familyname)
without putting 'formatFullName' into the actor metatable, an undesirable
requirement (namespace pollution), or making it a local for the duration of its
use, also undesirable (repetitious since it would be needed in any block/file
which used it; and needing additional indirection since I wouldn't be able to
localise the behaviour of the function by doing formatFullName =
formatFullNameLikeEuropeans etc.).
So I thought to myself "What if I stuffed a surrounding scoping of _G ?", and
thus I tried:
in _G do
in {is_ok = "Hurrah"} do
print(is_ok)
end
end
But that failed, because it didn't look upwards through the supposedly
lexically scoped environments. Unlike local; this lexical scoping seems more
magical.
Next I pondered... would I be able to use this scoping to make object
operations neater? But of course the answer is "no" because methods expect to
be called by use of object:method() and thus:
in io.open("/tmp/debuglog", "w") do
write(mydebuginformation)
for foo in mydebugiterator() do
write(foo)
end
close()
end
But this "obvious" use of the construct also falls down unless write
etc are rewritten to actually be a closure with its file as an upvalue.
I'm sure I could come up with more "obvious on the face of it" uses of this
which fall down in non-obvious ways; but I think this set of examples
illustrate my concern sufficiently. Thus the "in env do code() end" construct,
while looking very useful at face-value, turns out to fail on most of the
straightforward uses I thought up to try and find a way in which it would make
my life easier. That this construct (and loadin() etc) is being promoted as a
full replacement for all the blessed uses of setfenv worries me. Much of the
clumsy constructs which used setfenv will be no less clumsy in this new world;
and many of the previously elegant uses of it will be clumsy and/or impossible
without recourse to the debug library; the use of which is, quite rightly,
deprecated in normal code.
Regards,
Daniel.