lua-users home
lua-l archive

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


In the discussion of the utility of macros, I got thinking about Ruby which
seems to get by without them. My Ruby is weak, so I'm going to write
examples in pseudo-Lua.

A class definition in Ruby essentially sets up an execution context in which
other code then can add methods or make other alterations. For example:

    class "Foo" from Baz
        
        function display( self ) print( self:name() ) end

        function set_name( self, newName )
            local oldName = self._name
            if oldName ~= newName then
                self._name = newName
                self:send_change_notification( "name" )
            end
        end

        function name( self )
            return self._name
        end

    end

What's more, if accessors with that pattern were common, we could somewhere
define:

    function readWriteField( publicName, privateName )
        
        local env = get_calling_environment()

        env[ publicName ] = function( self )
            return self[ privateName ]
        end

        env[ "set_" .. publicName ] = function( self, newValue )
            local oldValue = self[ privateName ]
            if oldValue ~= newValue then
                self[ privateName ] = newValue
                self:send_change_notification( publicName )
            end
        end

    end

Then our class definition becomes:

    class "Foo" from Baz
        
        function display( self ) print( self:name() ) end

        readWriteField( "name", "_name" )

    end

Another example, this time sparked by reading Rob Pike's structural regular
expressions paper, what if one could write something like:

    local filter = TextFilters.Filter

        match "(.+/n)+"             -- split into multi-line chunks
        if_find "%%A.*Bimmler"      -- check for author
        match ".+/n"                -- split into lines
        if_find "%%T"               -- find title lines
        print_text()                -- print the current line
 
    end

Each of the operations would work by adding an element to the filter chain.
Running the filter would then process the chain.

I'm sure that the various macro systems floating around could support this,
but they aren't going to be easy to construct. Ruby manages to make these
sort of constructs easy without needing to resort to macros. Let's explore
why...

The first obvious thing is that it needs to be lightweight to support
creating function closures and passing them to other functions. While it
would be good for this to be lightweight from a runtime perspective, for now
the key point is that it be lightweight from a syntactic perspective.

If we were prepared to change the meaning of "do" (and if not consider doing
this with "begin") in the cases where it wasn't at the beginning of a line
and wasn't already part of some other syntactic construct, we could say that
"do" by itself was short for "function()" and that "do" followed on the same
line by a left parenthesis was short for specifying a function with
arguments.

Secondarily, we introduce the policy that a function specified after an
existing function reference or function call -- particularly if specified
using the new syntax -- gets passed as an extra parameter. Then we could
have something like:

    class "Foo" do

        inherit_from( Baz )
        
        function display( self ) print( self:name() ) end

        function set_name( self, newName )
            local oldName = self._name
            if oldName ~= newName then
                self._name = newName
                self:send_change_notification( "name" )
            end
        end

        function name( self )
            return self._name
        end

    end

The other piece that is needed is a way to get some form of dynamic scoping.

Assume, for example, that as part of Lua's core, we track a stack of dynamic
scopes. In fact, we don't really need a stack so much as a way to get and
set the current dynamic scope table and an easy way to refer to it. For
example, we might use the @ sign and the above examples would become:

    function readWriteField( publicName, privateName )
        
        @[ publicName ] = function( self )
            return self[ privateName ]
        end

        @[ "set_" .. publicName ] = function( self, newValue )
            local oldValue = self[ privateName ]
            if oldValue ~= newValue then
                self[ privateName ] = newValue
                self:send_change_notification( publicName )
            end
        end

    end

    class "Foo" do

        inherit_from( Baz )

        function @display( self ) print( self:name() ) end

        readWriteField( "name", "_name" )

    end

The point of this message is that while all of this could be done with macro
packages, it is probably worthwhile looking to see whether Lua could get 80%
of the expressive benefit for 20% of the effort and avoid a proliferation of
competing macro-based language changes.

Mark