Skip to content

okabsd/base

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

base

This is Yet Another Small Library for Lua, providing a foundation for derivative Object-oriented Programming.

Here’s a non-trivial example.

local Map = require 'base' : derive(function (fn)
    function fn:has (key)
        return rawget(self.data, key) ~= nil
    end

    function fn:get (key)
        return rawget(self.data, key)
    end

    function fn:set (key, value)
        rawset(self.data, key, value)
    end

    function fn:size ()
        local sz = 0

        for _, _ in pairs(self.data) do sz = sz + 1 end

        return sz
    end

    return function (self)
      self.data = {}
    end
end)

local WeakMap = Map:derive(function ()
    return function (source, self, mode)
        source(self)
        setmetatable(self.data, { __mode = mode or 'k' })
    end
end

local map = Map()
map:set('a', 1)
map:set('b', 2)
print(map:has('a'), map:get('b'), map:has('c')) -- true, 2, false

local wmap = WeakMap()
wmap:set(map, {})
print(wmap:has(map), wmap:size()) -- true, 1
map = nil
collectgarbage() -- Force GC
print(wmap:size()) -- 0

Install

The easy way is to use luarocks.

$ luarocks install base

Alternatively, grab the source, and manually add it to your project.

Goals & Reasoning

There are a number of OOP libraries for Lua. Why another?

With base, I wanted two main things:

  • Tiny, uncomplicated library

  • Clear and concise implementations

I didn’t want auxiliary functions, extra padding tables, or an (overly) confusing lexicon. There are no fluffy, duck-typing methods. No add-ons. Nothing special.

There are two distinct notions with base: sources, and derivatives. They are relative terms.

In the example above, WeakMap is a derivative of Map, which makes Map a source for WeakMap. You can think of these as parent/children 'classes', but it is important to make the distinction that everything is hot - functions are shared, not copied.

Base is the root source for all derivatives.

map and wmap are instances of Map and WeakMap, respectively.

base is bilateral in the way that both shared function tables (Foo.fn) and the derivatives themselves (Foo) can look to their sources for methods.

If Bar is a derivative of Foo, and Foo has a method Foo:abc, then Bar will also have access to that method.

If b is an instance of Baz, and Baz is a derivative of Qux, which has the shared method Qux.fn:xyz, then b will have access to that method.

This is why :derive is available to all derivatives.


Why 2.0.0?

Version 2.0.0 is a reimplementation of the same ideas. After using base for some time, I noticed certain patterns. Mainly, the use of single files for derivatives was common, but lead to a lot of repeated statements. 2.0.0 aims to cut down on those statements, and provide a more concise implementation pattern.

A typical single derivative file, shown in both styles.

1.0.0:

local Foobar = require 'base' : derive(function (_, self, ...)
    self.qux = { ... }
end)

function Foobar:satic_method () [[ ... ]] end

function Foobar.fn:foo () [[ ... ]] end
function Foobar.fn:bar () [[ ... ]] end

return Foobar

2.0.0:

return require 'base' : derive(function (fn, D)
    function D:static_method () [[ ... ]] end

    function fn:foo () [[ ... ]] end
    function fn:bar () [[ ... ]] end

    return function (self, ...)
        self.qux = { ... }
    end
end)

Documentation

There’s not a whole lot to base.

local Base = require 'base'

In this case, the return value from require is a singleton table, which we call Base.

Base has a single method, used to create derivatives, :derive, which in turn takes a single argument, context.

context is a function with the signature (fn, Derivative) → function. This function is called when the new derivative is formed, and is passed the following:

  • fn is the shared function table, a shortcut for Derivative.fn.

  • Derivative is the new derivative.

local List = Base:derive(function (fn, Derivative)
    [[ ... ]]
end)

Each derivative has a shared function table, .fn, which can be used to create methods that any instances of the derivative, or instances of any derivatives of the derivative have access to.

  • Note: Base also has a shared function table, in the event you want to add some kind of universally shared method. However, generally speaking, this is not a great idea.

local List = Base:derive(function (fn, Derivative)
    function fn:each (action)
        for i, v in ipairs(self.data) do
            action(v, i)
        end
    end

    [[ ... ]]
end)

The return value of context must be a function, which acts as an initializer for new instances of the derivative. We’ll simply call it initializer.

The function signature of initializer depends on whether you are deriving a new derivative directly from Base or not:

When deriving a new derivative directly from Base, the source argument is absent, and the argument list begins from self.

  • source is a function which provides access to the initializer of the derivative's closest source. It has the signature (instance, …​) → nil.

  • self is the newly formed instance.

  • …​ are any arguments passed to the constructor.

local List = Base:derive(function (fn, Derivative)
    return function (self, ...)
        self.data = { ... }
    end
end)

local List2 = List:derive(function (fn, Derivative)
    function fn:print ()
        self:each(print)
    end

    return function (source, self, ...)
        source(self, ...)
    end
end)

All derivatives act as constructors when directly invoked, returning the newly formed instance.

  • Note: Base is unique in that it is not a derivative, has no initializer, and does not act as a constructor.

local ls = List('a', 'b', 'c')

ls:each(print)

local ls2 = List2('d', 'e', 'f')
ls2:print()

It should be noted that, for simplicity’s sake, derivatives, their shared function tables, and their instances all act as their own metatables. You might notice an index metaproperty on each object created with this library, as well as some extras on derivatives. It’s best to not mess with these members.

License

MIT, just like Lua.

About

Derivative Object-oriented Programming

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages