Diary 2019-07-25
I guess I didn't feel strongly enough about working on stuff related to Seed, Pijul, Poetry, or pip, so I... didn't.
Instead, I've now got this Lua class stuff in... a state. The current big deficiency is documentation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | --- -- A small class system. -- @module class -- Signature documentation. do -- luacheck: ignore 541 --- Signatures. -- @section signatures --- Module table. -- @field module --- Metatable. -- @table metatable -- @field __new constructor --- End Signatures -- @section end end -- Module export --- Create a class; provides sugar of the form class[M].name(meta). -- @function class -- @tparam module|nil M -- @tparam string name -- @tparam[opt] metatable meta -- @treturn table local class local function new_instance(metatable, ...) return setmetatable(metatable.__new(...), metatable) end local function empty_table() return {} end local function new_class(mod, name, metatable) metatable = metatable or {} metatable.__name = name metatable.__new = metatable.__new or empty_table if mod then mod[name] = metatable end return metatable end local function basic_metaclass() return { __call = new_instance, __new = new_class, } end local class_class = basic_metaclass() class = new_instance(class_class, nil, "class", basic_metaclass()) class(nil, "class_class", class_class) local class_in_module = class(nil, "class_in_module", { __new = function(cls, mod) return {cls, mod} end, __index = function(self, name) if type(name) ~= "string" then return end local cls, mod = table.unpack(self) return function(metatable) return cls(mod, name, metatable) end end, }) local mod_types = {["nil"] = true, table = true} function class_class.__index(self, mod) if not mod_types[type(mod)] then return end return class_in_module(self, mod) end return class |
The basic idea of this stuff is "lots of things are objects, including things that probably don't need to be". The intended usage of this stuff is that consumers local class = require("class") (or local class = require"class"; the interaction between Lua's function calls and literals is... interesting), create a module table like local M = {}, and then use the syntactic sugar class[M].MyClass{__index = {value = "whatever"}, __new = function(value) return {value = value} end}, which in this example would create a class in M called MyClass, which defines a constructor (the __new function, which I'm kind of just hoping nothing else that uses metatables collides with) that takes an optional argument and stores it in the value key; if nothing is passed, accessing value will return the string "whatever" per normal metatable mechanics. In the course of normal usage, the class constructor expects the last argument passed, if truthy, to be a table literal; the need to pass a variable in the course of defining the system is unfortunate, but unavoidable given the semantics in question.
One thing that all classes, including the "built-in" ones from this module will need to document, is whether their constructor returns a completely new object, or something passed in via its arguments. I think the current convention for class creation showcases the furthest limits of argument mutation and returning:
- The M argument is not part of the return value, but an additional target for the return value to be stored in. The purpose of this behavior is to allow storing the class's name in itself, without resorting to repetition when it comes time to put it in the module. (Regardless of whether a module is passed, the class is returned; which allows the somewhat repetitive option of storing the class in both a module and a local.)
- The metatable argument is, by virtue of the preferred call syntax, assumed to be uniquely held by the constructor at the time, and therefore acceptable to mutate or return. (Inclusive or.) The purpose of this behavior is to concisely and quickly create metatables for use as classes. Creating copies of the passed-in metatable would have various issues, so I don't bother.
This has been fun, but to make use of this, I need a project to go with it, and ideally to have Seed working. The current hurdle with Seed is that I need to write code to extract the author config; if present, make "author" flags optional, otherwise required. It's kind of fiddly, oh well. I've got time, I'll try to get that done quick. Okay, I'm generating the paths, probably. From there, it shouldn't be hard to extract the author data, if it's present, and create a placeholder if it's not.
One bit of weirdness: the first record showed two diffs, and I recorded the first; the second record also showed two diffs, the second diffs together made up the first second diff. I should probably ask about that.
Okay, definitely need to wrap up. Good night.