xtype (halted)

Check-in [9463c12ff0]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Implement "any" keyword and `eq` default behavior.
Downloads: Tarball | ZIP archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 9463c12ff0f955c437268fdd86f19763c254e61d3fba1ab403c031ef85516fa5
User & Date: imagic 2021-11-26 18:21:01.000
Context
2021-11-26
18:24
Fix chunkname for generated code. ... (check-in: 06e6fcb534 user: imagic tags: trunk)
18:21
Implement "any" keyword and `eq` default behavior. ... (check-in: 9463c12ff0 user: imagic tags: trunk)
2021-11-25
20:41
Add warning about `eq` operator. ... (check-in: 2fe1114560 user: imagic tags: trunk)
Changes
Unified Diff Ignore Whitespace Patch
Changes to README.adoc.
100
101
102
103
104
105
106


107
108
109
110
111
112
113
114
115
116
117
118
119

120
121
122
123
124
125
126
127

128
129
130
131
132
133
134
xtype.of(t, ot)

-- Create a multifunction.
xtype.multifunction()

-- Global multifunctions namespace for binary operators.
-- For interoperability between third-party types.


-- map of Lua binary op name => multifunction
-- (add, sub, mul, div, mod, pow, concat, eq, lt, le, idiv, band, bor, bxor, shl, shr)
--
-- Warning: the `eq` metamethod has a default behavior; it should not throw an
-- error on resolution failure.
xtype.op

-- Low-level API.

-- The type's xtype_stack field is a list of types ordered from the terminal
-- type to the least specific inherited types.

-- Stack distance to another type from a terminal type.

-- return distance or nil/nothing if not of type ot
xtype.typeDist(t, ot)

-- Check and return signature (list of types).
-- ...: types
xtype.checkSign(...)

-- Distance to another signature from a call signature.

-- return distance or nothing if not generalizable to osign
xtype.signDist(sign, osign)

-- Return formatted signature string.
xtype.formatSign(sign)

-- Code generation tools.







>
>


<
<
<








>








>







100
101
102
103
104
105
106
107
108
109
110



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
xtype.of(t, ot)

-- Create a multifunction.
xtype.multifunction()

-- Global multifunctions namespace for binary operators.
-- For interoperability between third-party types.
-- Equality (eq) has a default behavior defined as: eq(any, any) -> false
--
-- map of Lua binary op name => multifunction
-- (add, sub, mul, div, mod, pow, concat, eq, lt, le, idiv, band, bor, bxor, shl, shr)



xtype.op

-- Low-level API.

-- The type's xtype_stack field is a list of types ordered from the terminal
-- type to the least specific inherited types.

-- Stack distance to another type from a terminal type.
-- ot: support "any" keyword
-- return distance or nil/nothing if not of type ot
xtype.typeDist(t, ot)

-- Check and return signature (list of types).
-- ...: types
xtype.checkSign(...)

-- Distance to another signature from a call signature.
-- osign: support "any" keyword
-- return distance or nothing if not generalizable to osign
xtype.signDist(sign, osign)

-- Return formatted signature string.
xtype.formatSign(sign)

-- Code generation tools.
148
149
150
151
152
153
154



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
----

=== Multifunction

[source, lua]
----
-- Define a multifunction signature.



-- f: definition function; nil to undefine
-- ...: signature, list of types
multifunction:define(f, ...)

-- Add a generator function.
--
-- All generators are called when no matching definition has been found to
-- eventually define new signatures.
--
-- f(multifunction, ...): called to generate new definitions
--- ...: call signature, list of types
multifunction:addGenerator(f)

-- Get the resolved function for a specific signature.
-- ...: call signature, list of types
-- return function or nil without a matching definition
multifunction:resolve(...)

-- Call the multifunction.
multifunction(...)
multifunction:call(...)








>
>
>










|



|







149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
----

=== Multifunction

[source, lua]
----
-- Define a multifunction signature.
-- The keyword "any" matches any type. It is the least specific match for a
-- given terminal type.
--
-- f: definition function; nil to undefine
-- ...: signature, list of types
multifunction:define(f, ...)

-- Add a generator function.
--
-- All generators are called when no matching definition has been found to
-- eventually define new signatures.
--
-- f(multifunction, ...): called to generate new definitions
--- ...: call signature, list of (terminal) types
multifunction:addGenerator(f)

-- Get the resolved function for a specific signature.
-- ...: call signature, list of (terminal) types
-- return function or nil without a matching definition
multifunction:resolve(...)

-- Call the multifunction.
multifunction(...)
multifunction:call(...)

Changes to examples/test_multifunction.lua.
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
-- Test multifunction.
package.path = "src/?.lua;"..package.path
local xtype = require("xtype")

do -- Test operators.
  local op_add = xtype.multifunction()
  local op_mul = xtype.multifunction()
  local op_eq = xtype.multifunction()

  local function build_type(t)
    local ins_mt = {
      xtype = t,
      __add = op_add,
      __mul = op_mul,
      __eq = op_eq
    }
    local type_mt = {
      xtype = "xtype",
      __call = function(t, v)
        return setmetatable({v = v}, ins_mt)
      end
    }
    return setmetatable(t, type_mt)
  end

  local Fruits = build_type(xtype.create("Fruits"))
  local Apples = build_type(xtype.create("Apples", Fruits))
  local Oranges = build_type(xtype.create("Oranges", Fruits))


  op_add:define(function(a, b) return Apples(a.v+b.v) end, Apples, Apples)
  op_add:define(function(a, b) return Oranges(a.v+b.v) end, Oranges, Oranges)
  op_add:define(function(a, b) return Fruits(a.v+b.v) end, Fruits, Fruits)
  op_mul:define(function(a, f) return Apples(a.v*f) end, Apples, "number")
  op_mul:define(function(a, f) return Oranges(a.v*f) end, Oranges, "number")
  op_mul:define(function(a, f) return Fruits(a.v*f) end, Fruits, "number")
  op_eq:define(function(a, b) return a.v == b.v end, Apples, Apples)
  op_eq:define(function(a, b) return a.v == b.v end, Oranges, Oranges)
  op_eq:define(function(a, b)
    return xtype.get(a) == xtype.get(b) and a.v == b.v
  end, Fruits, Fruits)

  local apples = Apples(5)
  local oranges = Oranges(5)
  -- checks
  assert(xtype.is(op_eq, "multifunction"))
  assert(xtype.get(op_eq) == "multifunction")
  assert(apples+apples == Apples(10))
  assert(op_add:call(apples, apples) == Apples(10)) -- alternative
  assert(op_add:resolve(Apples, Apples)(apples, apples) == Apples(10)) -- alternative
  assert(not (apples+oranges == Apples(10)))
  assert(apples+oranges == Fruits(10))
  assert(oranges+apples == Fruits(10))
  assert(apples*3 == Apples(15))


end
do -- Test resolution order.
  -- types
  local animal = xtype.create("animal")
  local dog = xtype.create("dog", animal)
  local cat = xtype.create("cat", animal)
  local chimera = xtype.create("chimera", cat, dog)





<
<
<
<



|
|
|













>

|
|
|
|
|
|
|
|
|






|
|

|
|




>
>







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
-- Test multifunction.
package.path = "src/?.lua;"..package.path
local xtype = require("xtype")

do -- Test operators.




  local function build_type(t)
    local ins_mt = {
      xtype = t,
      __add = xtype.op.add,
      __mul = xtype.op.mul,
      __eq = xtype.op.eq
    }
    local type_mt = {
      xtype = "xtype",
      __call = function(t, v)
        return setmetatable({v = v}, ins_mt)
      end
    }
    return setmetatable(t, type_mt)
  end

  local Fruits = build_type(xtype.create("Fruits"))
  local Apples = build_type(xtype.create("Apples", Fruits))
  local Oranges = build_type(xtype.create("Oranges", Fruits))
  local Rocks = build_type(xtype.create("Rocks"))

  xtype.op.add:define(function(a, b) return Apples(a.v+b.v) end, Apples, Apples)
  xtype.op.add:define(function(a, b) return Oranges(a.v+b.v) end, Oranges, Oranges)
  xtype.op.add:define(function(a, b) return Fruits(a.v+b.v) end, Fruits, Fruits)
  xtype.op.mul:define(function(a, f) return Apples(a.v*f) end, Apples, "number")
  xtype.op.mul:define(function(a, f) return Oranges(a.v*f) end, Oranges, "number")
  xtype.op.mul:define(function(a, f) return Fruits(a.v*f) end, Fruits, "number")
  xtype.op.eq:define(function(a, b) return a.v == b.v end, Apples, Apples)
  xtype.op.eq:define(function(a, b) return a.v == b.v end, Oranges, Oranges)
  xtype.op.eq:define(function(a, b)
    return xtype.get(a) == xtype.get(b) and a.v == b.v
  end, Fruits, Fruits)

  local apples = Apples(5)
  local oranges = Oranges(5)
  -- checks
  assert(xtype.is(xtype.op.eq, "multifunction"))
  assert(xtype.get(xtype.op.eq) == "multifunction")
  assert(apples+apples == Apples(10))
  assert(xtype.op.add:call(apples, apples) == Apples(10)) -- alternative
  assert(xtype.op.add:resolve(Apples, Apples)(apples, apples) == Apples(10)) -- alternative
  assert(not (apples+oranges == Apples(10)))
  assert(apples+oranges == Fruits(10))
  assert(oranges+apples == Fruits(10))
  assert(apples*3 == Apples(15))
  --- check equality default behavior
  assert(Rocks(5) ~= Apples(5))
end
do -- Test resolution order.
  -- types
  local animal = xtype.create("animal")
  local dog = xtype.create("dog", animal)
  local cat = xtype.create("cat", animal)
  local chimera = xtype.create("chimera", cat, dog)
Changes to src/xtype.lua.
212
213
214
215
216
217
218

219
220



221
222
223

224
225
226
227
228

229
230
231
232
233
234
235
    table.insert(names, type(t) == "string" and t or t.xtype_name)
  end
  return "("..table.concat(names, ", ")..")"
end
xtype.formatSign = format_sign

-- Stack distance to another type from a terminal type.

-- return distance or nil/nothing if not of type ot
local function type_dist(t, ot)



  if type(t) == "string" then return t == ot and 0 or nil end
  for i, st in ipairs(t.xtype_stack) do
    if st == ot then return i-1 end

  end
end
xtype.typeDist = type_dist

-- Distance to another signature from a call signature.

-- return distance or nothing if not generalizable to osign
local function sign_dist(sign, osign)
  local dist = 0
  for i=1, #sign do
    local tdist = type_dist(sign[i], osign[i])
    if not tdist then return end
    dist = dist+tdist







>


>
>
>
|
|
|
>





>







212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
    table.insert(names, type(t) == "string" and t or t.xtype_name)
  end
  return "("..table.concat(names, ", ")..")"
end
xtype.formatSign = format_sign

-- Stack distance to another type from a terminal type.
-- ot: support "any" keyword
-- return distance or nil/nothing if not of type ot
local function type_dist(t, ot)
  if ot == "any" then -- special keyword
    return type(t) == "string" and 1 or #t.xtype_stack
  else
    if type(t) == "string" then return t == ot and 0 or nil end
    for i, st in ipairs(t.xtype_stack) do
      if st == ot then return i-1 end
    end
  end
end
xtype.typeDist = type_dist

-- Distance to another signature from a call signature.
-- osign: support "any" keyword
-- return distance or nothing if not generalizable to osign
local function sign_dist(sign, osign)
  local dist = 0
  for i=1, #sign do
    local tdist = type_dist(sign[i], osign[i])
    if not tdist then return end
    dist = dist+tdist
356
357
358
359
360
361
362



363
364
365
366
367
368
369
  mfcall = loadstring(code, "xtype multifunction call("..n..")")(xtype_get, mf_call)
  -- cache
  mfcalls[n] = mfcall
  return mfcall
end

-- Define a multifunction signature.



-- f: definition function; nil to undefine
-- ...: signature, list of types
function multifunction:define(f, ...)
  local sign = check_sign(...)
  local hash = mf_hash_sign(self, sign)
  if f then -- define
    -- increase call parameters, re-compile call function







>
>
>







362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
  mfcall = loadstring(code, "xtype multifunction call("..n..")")(xtype_get, mf_call)
  -- cache
  mfcalls[n] = mfcall
  return mfcall
end

-- Define a multifunction signature.
-- The keyword "any" matches any type. It is the least specific match for a
-- given terminal type.
--
-- f: definition function; nil to undefine
-- ...: signature, list of types
function multifunction:define(f, ...)
  local sign = check_sign(...)
  local hash = mf_hash_sign(self, sign)
  if f then -- define
    -- increase call parameters, re-compile call function
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
        if candidate.def == def then self.candidates[chash] = nil end
      end
    end
  end
end

-- Get the resolved function for a specific signature.
-- ...: call signature, list of types
-- return function or nil without a matching definition
function multifunction:resolve(...)
  local sign = check_sign(...)
  return mf_resolve_sign(self, sign)
end

-- Add a generator function.
--
-- All generators are called when no matching definition has been found to
-- eventually define new signatures.
--
-- f(multifunction, ...): called to generate new definitions
--- ...: call signature, list of types
function multifunction:addGenerator(f)
  self.generators[f] = true
end

-- Create a multifunction.
function xtype.multifunction()
  -- The metatable is per multifunction to independently update the call







|












|







410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
        if candidate.def == def then self.candidates[chash] = nil end
      end
    end
  end
end

-- Get the resolved function for a specific signature.
-- ...: call signature, list of (terminal) types
-- return function or nil without a matching definition
function multifunction:resolve(...)
  local sign = check_sign(...)
  return mf_resolve_sign(self, sign)
end

-- Add a generator function.
--
-- All generators are called when no matching definition has been found to
-- eventually define new signatures.
--
-- f(multifunction, ...): called to generate new definitions
--- ...: call signature, list of (terminal) types
function multifunction:addGenerator(f)
  self.generators[f] = true
end

-- Create a multifunction.
function xtype.multifunction()
  -- The metatable is per multifunction to independently update the call
463
464
465
466
467
468
469


470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493



494
-- return processed template
function xtype.tplsub(tpl, args)
  return string.gsub(tpl, "%$([%w_]+)", args)
end

-- Global multifunctions namespace for binary operators.
-- For interoperability between third-party types.


-- map of Lua binary op name => multifunction
-- (add, sub, mul, div, mod, pow, concat, eq, lt, le, idiv, band, bor, bxor, shl, shr)
--
-- Warning: the `eq` metamethod has a default behavior; it should not throw an
-- error on resolution failure.
xtype.op = {
  add = xtype.multifunction(),
  sub = xtype.multifunction(),
  mul = xtype.multifunction(),
  div = xtype.multifunction(),
  mod = xtype.multifunction(),
  pow = xtype.multifunction(),
  concat = xtype.multifunction(),
  eq = xtype.multifunction(),
  lt = xtype.multifunction(),
  le = xtype.multifunction(),
  idiv = xtype.multifunction(),
  band = xtype.multifunction(),
  bor = xtype.multifunction(),
  bxor = xtype.multifunction(),
  shl = xtype.multifunction(),
  shr = xtype.multifunction()
}




return xtype







>
>


<
<
<



















>
>
>

472
473
474
475
476
477
478
479
480
481
482



483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
-- return processed template
function xtype.tplsub(tpl, args)
  return string.gsub(tpl, "%$([%w_]+)", args)
end

-- Global multifunctions namespace for binary operators.
-- For interoperability between third-party types.
-- Equality (eq) has a default behavior defined as: eq(any, any) -> false
--
-- map of Lua binary op name => multifunction
-- (add, sub, mul, div, mod, pow, concat, eq, lt, le, idiv, band, bor, bxor, shl, shr)



xtype.op = {
  add = xtype.multifunction(),
  sub = xtype.multifunction(),
  mul = xtype.multifunction(),
  div = xtype.multifunction(),
  mod = xtype.multifunction(),
  pow = xtype.multifunction(),
  concat = xtype.multifunction(),
  eq = xtype.multifunction(),
  lt = xtype.multifunction(),
  le = xtype.multifunction(),
  idiv = xtype.multifunction(),
  band = xtype.multifunction(),
  bor = xtype.multifunction(),
  bxor = xtype.multifunction(),
  shl = xtype.multifunction(),
  shr = xtype.multifunction()
}

-- Default eq behavior.
xtype.op.eq:define(function() return false end, "any", "any")

return xtype