Skip to content

Instantly share code, notes, and snippets.

@andrewstarks
Last active December 20, 2015 21:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewstarks/6199229 to your computer and use it in GitHub Desktop.
Save andrewstarks/6199229 to your computer and use it in GitHub Desktop.
weak_type.lua: a first whack at a small type library
--[[
weak_type.lua
A type library for the weak.
Andrew Starks <andrew.starks@trms.com>
Version .01, August, 10, 2013
This code is hereby placed in the public domain.
--]]
local _type, setmetatable, getmetatable, select, ipairs, print, tostring, error, format =
type, setmetatable, getmetatable, select, ipairs, print, tostring, error, string.format
_ENV = {}
local type = setmetatable({}, {
__call = function(t, ...)
local arg_count = select("#", ...)
if arg_count == 0 then
return "nil", "invalid key"
elseif arg_count == 1 then
return _type(...)
else
return t.check((select(1,...)),select(2,...))
end
end
})
function type.tostring(o)
return tostring(type.get(o))
end
type.types = setmetatable({}, {
__mode = "v",
__type={types = 1, "types", table = true}}
)
local type_mt = {
__type = {type = 1, "type", table = true},
__tostring = function(self)
return self[1]
end,
__eq = function(a, b)
type.is_equal(a, b)
end,
__le = function(a, b)
return type.is_contained(a,b)
end
}
local types = type.types
function type.new(t)
--print(_type(t))
if not t or _type(t) ~= "string" and _type(t) ~= "table" then
error("Expected string or table of type names.")
end
local new_type = {}
for i,v in ipairs( _type(t) == "string" and {t} or t) do
new_type[i] = v
new_type[v] = i
-- print("making",new_type[v],new_type[i])
end
--make a table entry, but don't index it.
--that way #length points to how many types
--are assigned to it, but not the obvious ones.
--also, tables that are passed in can be done this way too.
new_type.table = true
local t_name = new_type[1]
local i = types[t_name] or #types + 1
types[t_name] = i
types[i] = new_type
return setmetatable(new_type, type_mt)
end
function type.assign (o, new_type)
if _type(o) ~= "table" then
error("Expected a table as first argument to type.assign. Received %s", _type(o) )
end
local o_mt = getmetatable(o) or {}
o_mt.__type = new_type ~= nil and
(type.check(new_type, "type") and new_type or type.new(new_type)) or
nil
return setmetatable(o, o_mt)
end
function type.get(o)
if _type(o) ~= "table" then
return _type(o)
else
local o_mt = getmetatable(o)
return o_mt and o_mt.__type or "table"
end
end
function type.is_equal(a, b)
return type.tostring(a) == type.tostring(b)
end
function type.is_contained(a, b)
local a_t, b_t = type(a, "type") and a or type.get(a), type(b, "type") and b or type.get(b)
if _type(a_t) == "string" then
--warning, if somehow a_t is a string and b was passed in by __le, then this would not work.
--because __le works on the <= operator and needs types.
--type does not work with types, but with object that have them.
return type(b, a_t)
elseif _type(b_t) == "string" then
if #a_t > 1 then
return false
else
return a_t[1] == b_t
end
else
local match = true
for i, v in ipairs(a_t) do
if not b_t[v] then
return false
end
end
return true
end
end
function type.contains(a, b)
return is_contained(b, a)
end
function type.check(o, ...)
local search_count = select("#", ...)
local arg_len = select("#", ...)
if arg_len == 0 then
return type.tostring(o)
end
local o_type = type.get(o)
for i = 1, arg_len do
local cur_search = (select(i, ...)) -- t:match( )
--we'll return the primary type and the matched type.
if _type(o_type) == "table" and o_type[cur_search] or o_type == cur_search then
return _type(o_type) == "table" and o_type[1] or o_type, cur_search
end
end
--didn't find it.
return false
end
--testing
--[[
local pf = function(...) print(format(...)) end
print(type((function() return end)()))
pf([=[
Consider our friendly type function:
type("Hello, World?"): %s
]=],
type("Hello, World?"))
--> string
pf([=[
We normally check for type equality with this:
type('hello') == 'string' (%s)
]=],
type('hello') == 'string')
--> string
pf([=[
What if we could also do this:
type('hello', 'string') (%s)
]=],
type('hello', 'string') )
--> string
local my_object = type.assign({}, {"My_Object_Type", "string"})
pf([=[
Not a big deal, but this could lead to some nice tricks:
type(my_object, 'string') (%s, %s)
]=],
type(my_object, 'string'))
-->My_Object_Type, string
print([=[
In the last example, My_Object_Type is the primary type of the object,
so it is returned first. 'string' was the match, so it's second.
So we see that objects can have multiple types. Let's look at that a bit further...
]=])
local obj = type.assign({},{"test", "foo", "bar"})
local obj1 = type.assign({}, {"test", "foo"})
local obj2 = type.assign({}, {"test", "foo", "baz"})
pf([=[
Containment:
type.is_contained(obj1, obj2): %s
type.is_contained(obj2, obj1): %s
]=], type.is_contained(obj1, obj2), type.is_contained(obj2, obj1))
--> true, false
pf([=[
Containment with __le operator:
type.get(obj2) <= type.get(obj1): %s
type.get(obj1) <= type.get(obj2): %s
]=],
type.get(obj2) <= type.get(obj1), type.get(obj1) <= type.get(obj2))
--> false, true
local obj4 = type.assign({}, {"int64", "number"})
pf([=[
Use is_contained to see if an object can 'behave' like something, even with native types.
type.is_contained(3, obj4): %s
]=],
type.is_contained(3, obj4))
-->
pf([=[
Note that <= doesn't work directly on the object.
It works on the type (which I guess is an object, but you get the idea.)
Oune could make these operators work, although I don't it's a good idea.
Also, there is no '>=', but it still works:
type.get(obj2) >= type.get(obj1): %s
type.get(obj1) >= type.get(obj2)%s
]=],
type.get(obj2) >= type.get(obj1), type.get(obj1) >= type.get(obj2) )
--> true, false
local obj3 = type.assign({}, {"dizzle"})
pf([=[
Is equal is there. It doesn't check to see if all sub-types are there.
Only if the tostring method for the type is equal.
This matches the use cases that I can think of:
type.is_equal(obj1, obj2): %s
type.is_equal(obj1, obj3): %s
]=],
type.is_equal(obj1, obj2), type.is_equal(obj1, obj3))
--> true false
pf([=[
Works like normal, too:
type("Hello, word!"): %s
type(2): %s
type(nil): %s
]=], type("Hello, word!"), type(2), type(nil))
print([=[
As demonstrated, assign puts a new type into a table. type.new returns a new type.
Types are saved in a type.types table. Not sure why right now.
Maybe an evil plan will emerge.
type.get returns an object's type object. type. 'tostring' returns the string
representation of the type, whereas type(o) return's o's native type (table).
]=])
pf([=[
Note that type(obj, 'table') returns a truthy value ('%s' in this case).
This is because all objects are tables, so you get that type match for free.
You're welcome.
]=],
type(obj, 'table') )
print([=[
If it isn't abundantly clear, this is a first whack at something useful.
Hopefully it's not too un-Lua-like. I tried to stick to real-world problems
that I've actually run into.
]=])
--]]
return type
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment