lua-users home
lua-l archive

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


> Probably the most 'natural' identity/no-operation operator is '+', noting
> that classically "+5" is the same as "5" (which appears in Lua as 1e+5 ==
> 1e5). I suppose that using the identity operator might be clearer in
cases
> needing leading parentheses? Eg:
>       +(fred or joe)(123)

That doesn't work because + is not unambiguously unary. So:

    a = f
    +(b or c)(g)

is still ambiguous. In any event, I find the use of + as an identity
operator a bit irritating, for some reason. Maybe it's my anti-Perl bias. I
prefer + to be unambiguously binary.

I actually use Do in my own code; it's a shame that "do" is not available.
"call" would be another possibility, but the word is not very attractive.
There is also "perform", "execute", etc.

Another possibility is:
    do (b or c)(g) end

The only change necessary to implement that one is that a function call
starting with ( is not valid *except* as the first statement in a do-block.
But the trailing "end" bothers me a bit. On the whole, ! is not so awful
either.

The syntactic restriction only needs to apply to
function-calls-as-statements; within an expression, it is not ambiguous as
long as statements cannot start with (.

It is useful to have an identity function when you're passing functions
around. An identity operator is less interesting because in Lua you cannot
syntactically abstract operators into functions, like you can for example
in Dylan. It's sometimes nice to be able to do so: \+ is arguably easier to
read than function(a, b) a + b end, although excessive use does lead to the
line-noise syndrome (so it is another non-suggestion for Lua). This is
particularly useful if you like using APL-like primitives like reduce: in
APL, it's +/vector; in some other functional language it could be reduce
(\+, vector).

The nice thing about operators-as-functions is that it could lead to a
considerable efficiency if they were used often; rather than doing a
full-fledged subroutine call, the VM can just execute an opcode. (I won't
make any promises about sending a patch; I had one for Lua 4 but I don't
know where I put it, and I really don't have time right now.)

By the way, on the subject of operators producing functionals, the key
insight is that in Lua there is very little difference between a function
and a table with a __call metamethod. I refer to the latter as functables,
and they are incredibly useful. For example, you can write a simple
memoising functable which stashes the function to be memoised in a hidden
key (using a freshly-minted table as the key value; the key is "closed"
into the metamethods) and then defines the __index metamethod to call the
function and stash its value in the table; the __call metamethod defers to
a table-get, and is mostly there as syntactic sugar but has the additional
value that the table can be used *almost* as though it were a function --
sadly, there are a couple of places in Lua where the __call metamethod is
not checked. However, it is available for functional composition, currying,
and so on.

Another very useful example of a functable is a function which has
non-algorithmic exceptions. For example, plural(x); the exceptions are
stashed in the table, and the function is used to add "s" or "es" as
appropriate. This is just like the memoising functable, except that it
doesn't remember the result. Again, it is handy to have this available as a
function and not just as a table. I've written about this before on this
list.

Arguably, all this is just monkeying around with syntax, but it is an
interesting way to look at program structure and I thank Lua for giving me
this insight. In the case of plural(), for example, the plural function
could be written with an exception list, but that distracts from the
pluralisation algorithm and requires some sort of API to add to the
exception list, whereas with the functable, adding to the exception list is
seamless:

    plural = Functable(function(word)
                       -- some sample rules:
                           -- if word ends in consonant, "y", change "y" to
"ies"
                       -- if word ends in "s", append "es"
                       -- otherwise append "s"
                        end)
    plural.mouse = "mice"
    plural.cow = "kine"

This made for a nifty bottles of beer implementation (although not nearly
compact as Philippe's):

Here is To_Functable, a variant which functablises its first argument and
returns it.

 do
  local marker = {}
  function marker.__index(t, k) return t[marker](k) end
  function marker.__call(t, v) return t[v] end
  function To_Functable(t, fn)
    t[marker] = fn
    setmetatable(t, marker)
    return t
  end
end

-- Functable bottles of beer implementation

    spell_out = {
                 "One", "Two", "Three", "Four", "Five",
                 "Six", "Seven", "Eight", "Nine", "Ten",
                 [0] = "No more",
                 [-1] = "Lots more"
                }
    spell_out = To_Functable(spell_out, function(i) return i end)
    -- here it would have been nice to have had an identity function handy,
no?

    bottles = To_Functable({"Just one bottle of beer"},
                           function(i)
                             return spell_out(i) .. " bottles of beer"
                           end)

    function line1(i)
      return bottles(i) .. " on the wall, " .. bottles(i) .. "\n"
    end

    line2 = To_Functable({},
              function(i) return "Take one down and pass it around,\n" end)

    line2[0] = "Go to the store, Buy some more,\n"

    function line3(i)
      return bottles(i) .. " on the wall.\n"
    end

    function song(n)
      for i = n, 0, -1 do
        io.write(line1(i), line2(i), line3(i - 1), "\n")
      end
    end

After coming up with the functable insight, I started looking for two
language features:

1) gensym. There is no problem with using freshly-minted tables as marker
keys, but a gensym would use up less space. This is now available with
light-userobjects so I'm happy.

2) hidden keys. A hidden key is one which does not show up in an iteration
sequence, and would be useful to hide keys like the memoised function in a
memoising functable, etc. This can be implemented with the generalised for
statement, but I really wish that I could define the default iterator for a
table through a __next metamethod.

3) perhaps even a builtin to implement To_Functable, or some way to make
the syntax nicer.

I freely accept that these are features which might only be of interest to
me.

I hope someone out there found this interesting. :)

R.