Coding 2022-09-15
All right, good work, me.
I've got a basic single-dispatch multi-function-y thing with no inheritance. Nice and lean.
...
Like, I know it's not fair to compare my Lua version to the Java version, since the Java code is constrained for pedagogical reasons, but it takes less Lua code to put together the functionality I need from scratch than it does to force Java to sort of have metaprogramming. Just look:
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 | local ephemeron = require "ephemeron" local class_from_instance = ephemeron() local m = {} local function make_destructor(slots) return function(instance) local instance_args = {} for i, v in ipairs(slots) do instance_args[i] = instance[v] end return table.unpack(instance_args) end end local destructors = ephemeron() local protocol = function(implementations) local impls = ephemeron() for k, v in pairs(implementations) do impls[k] = v end return function(instance, ...) return impls[class_from_instance[instance]](instance, ...) end end local class = function(body) local slots = table.move(body, 1, #body, 1, {}) local instance_meta = {} for k, v in pairs(body) do if type(k) == "string" then instance_meta[k] = v end end local function build(...) local instance = {} local args = {...} for i, v in ipairs(slots) do instance[v] = args[i] end class_from_instance[instance] = build return setmetatable(instance, instance_meta) end destructors[build] = make_destructor(slots) return build end m.class = class m.protocol = protocol function m.destructure(instance) return destructors[class_from_instance[instance]](instance) end return m |
I'm not even using all of that code yet, and it's still shorter than the initial version of GenerateAst.java. And, of course, it's regular Lua. That said, there are a few things I want to add. I've got the weird local X = function construct because I'm planning to add contract enforcement to the public API, which will add a few lines.
This is all used like
1 2 3 4 5 6 7 8 | local disinherit = require "disinherit" return { binary = disinherit.class {"left", "operator", "right"}, grouping = disinherit.class {"expression"}, literal = disinherit.class {"value"}, unary = disinherit.class {"operator", "right"}, } |
and
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 | local expr = require "lox/expr" local printast local function parenthesize(name, ...) local builder = {"(", name} for _, v in ipairs {...} do table.insert(builder, " ") table.insert(builder, printast(v)) end table.insert(builder, ")") return table.concat(builder) end printast = require"disinherit".protocol { [expr.binary] = function(binary) return parenthesize(binary.operator.lexeme, binary.left, binary.right) end, [expr.grouping] = function(grouping) return parenthesize("group", grouping.expression) end, [expr.literal] = function(literal) return tostring(literal.value) end, [expr.unary] = function(unary) return parenthesize(unary.operator.lexeme, unary.right) end, } return printast |
I'm going to have to think about this some, because setting up contracts as-is will do Bad Things to the protocol function. Like, if you put contracts on a constructor, as-is, that'll break protocols because now you don't have access to the base constructor. Either the protocol method needs to have some way of getting the "underlying function", or the class function needs to have some way of taking an optional callback to transform the constructor before it ever gets called. I'm inclined to do things the latter way, although, to get that ergonomic, it'd need some tweaks to the contracts interface, which is no big deal.
Anyway, it's getting late, although not as late as it's gotten before, and I want to wrap up, so I'm going to post this, consider this other stuff later, and try to knock out the Reverse Polish Notation challenge tomorrow.
Good night.