Coding 2022-09-15

By Max Woerner Chase

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.