Skip to content

Instantly share code, notes, and snippets.

@tiffany352
Last active December 14, 2015 09: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 tiffany352/5066570 to your computer and use it in GitHub Desktop.
Save tiffany352/5066570 to your computer and use it in GitHub Desktop.
Typeclasses in lua
local fun = {}
--[[
fun.lua, tiffany's typeclass implementation in lua
Copyright (c) 2013 tiffany <tiffany@stormbit.net>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Usage:
--To create a trait
fun.trait "SomeTypeclass" {
doSomething = function() doSomethingNoIMeanReallyDoIt() end,
overLoadMe = fun.stub,
someConst = 4
}
-- an implementation of that trait for numbers
fun.impl("number", "SomeTypeclass") {
overLoadMe = function(self, x) print("hi, "..x) end,
}
-- casting a value into a typeclass, if someval is a number it'll use the previously defined impl
local val = cast("SomeTypeclass", someval)
-- calls overLoadMe with a properly set "self"
val:overLoadMe("you")
]]
fun.traits = {}
function fun.stub()
error "Stub function!"
end
function fun.trait(name)
return function(tab)
fun.traits[name] = tab
end
end
function fun.typename(v)
if type(v) == "userdata" then
if type(getmetatable(v).__tname) == "string" then
return getmetatable(v).__tname
end
for k, v in pairs(debug.getregistry()) do
if v == getmetatable(v) then
getmetatable(v).__tname = k
return k
end
end
return "userdata"
else
return type(v)
end
end
function fun.impl(T, trait)
if not fun.traits[trait] then
error("No such trait "..tostring(trait))
end
if not fun.traits[trait].impls then
fun.traits[trait].impls = {}
end
local impls = fun.traits[trait].impls
return function(tab)
local function ignored(k)
return k == "impls"
end
impls[T] = {}
for k, v in pairs(fun.traits[trait]) do
if not ignored(k) then
if not tab[k] then
error("Incomplete typeclass: No such value "..tostring(k))
end
if type(v) ~= type(tab[k]) then
error("Type mismatch for member "..tostring(k)..": Expected "..fun.typename(v)..", got "..fun.typename(tab[k]))
end
if type(v) == "function" then
local oldfun = tab[k] or v
impls[T][k] = function(self, ...)
return oldfun(self.value, ...)
end
else
impls[T][k] = tab[k]
end
end
end
setmetatable(impls[T], {__newindex=function() error "Attempt to modify impl" end})
end
end
function fun.cast(trait, val)
if not fun.traits[trait] then
error("No such trait "..tostring(trait))
end
if not fun.traits[trait].impls then
error("No impls of trait "..tostring(trait))
end
if not fun.traits[trait].impls[fun.typename(val)] then
error("No impl of trait "..tostring(trait).." for type "..fun.typename(val))
end
local instance = {}
for k, v in pairs(fun.traits[trait].impls[fun.typename(val)]) do
instance[k] = v
end
instance.value = val
return instance
end
function fun.partialapp(fn, ...)
if type(fn) ~= "function" then
error "Expected function"
end
local t = {...}
return function(...)
return fn(unpack(t), ...)
end
end
-- (.) :: (b -> c) -> (a -> b) -> a -> c
-- f . g = \x -> f (g x)
function fun.compose(...)
local t = {...}
return function (...)
local last = {...}
for i = #t, 1, -1 do
last = {t[i](unpack(last))}
end
return unpack(last)
end
end
-- map :: (a -> b) -> [a] -> [b]
function fun.map(fn, t)
if type(fn) ~= "function" then
error "Expected function"
end
if type(t) ~= "table" then
error "Expected table"
end
local r = {}
for _, v in pairs(t) do
r[#r + 1] = fn(v)
end
return r
end
function fun.fold(fn, acc, t)
if type(fn) ~= "function" then
error "Expected function"
end
for _, v in pairs(t) do
acc = fn(acc, v)
end
return acc;
end
function fun.filter(fn, t)
if type(fn) ~= "function" then
error "Expected function"
end
if type(t) ~= "table" then
error "Expected table"
end
local r = {}
for _, v in pairs(t) do
if fn(v) then
r[#r+1] = v
end
end
return r
end
fun.trait "Functor" {
fmap = function(self, fn) end
}
fun.impl("table", "Functor") {
fmap = function(self, fn) fun.map(fn, self) end
}
return fun
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment