Skip to content

Instantly share code, notes, and snippets.

@fcard
Last active July 28, 2016 15:11
Show Gist options
  • Select an option

  • Save fcard/f356b01d5bb160dd486b9518ac292582 to your computer and use it in GitHub Desktop.

Select an option

Save fcard/f356b01d5bb160dd486b9518ac292582 to your computer and use it in GitHub Desktop.
SelfFunction: Function with an implicit argument and namespace (Example at the bottom)
## Note: This code is outdated and unmantained!
## Go here for the new version: https://github.com/fcard/SelfFunctions.jl
module SelfFunctions
export @selftype
immutable SelfFunction{F <: Function}
name::Symbol
typ::DataType
f::F
end
Base.show(io::IO, sf::SelfFunction) = print(io, "$(sf.name) (self function of type $(sf.typ))")
@inline (sf::SelfFunction)(args...) = sf.f(args...)
@inline selfcall(f::SelfFunction, t, args...) = f.f(t, args...)
@inline selfcall(f, t, args...) = f(args...)
macro selftype(maker_macro, typname::Symbol)
esc(generate_selfmacro(maker_macro, :(fieldnames($typname)), typname))
end
macro selftype(maker_macro, typedef::Expr)
const fields = map(fieldname, filter(isfield, typedef.args[3].args))
const tname = typename(typedef)
esc(quote
$typedef
$(generate_selfmacro(maker_macro, fields, tname))
end)
end
function generate_selfmacro(name, fields, tname)
@gensym self fname fimpl iname typ
quote
macro $(name)(funcdef)
const $typ = Symbol($(string(tname)))
const $self = gensym("self")
const $fname = $funcname(funcdef)
const $fimpl = $funcimpl(funcdef, $fields, $self, $typ)
const $iname = $funcname($fimpl)
$(esc)(quote
$($fimpl)
const $($fname) = $($SelfFunction)(Symbol($(string($fname))), $($tname), $($iname))
end)
end
end
end
fieldname(arg::Expr) = arg.args[1]
fieldname(arg::Symbol) = arg
isfield(arg::Expr) = arg.head == :(::)
isfield(arg::Symbol) = true
isfield(x) = false
typename(def::Expr) = def.args[2]
funcname(def::Expr) = def.head == :macrocall? funcname(def.args[2]) : def.args[1].args[1]
function funcimpl(def, fields, self, typ)
const (addmeta, fdef) = function_meta(def)
const (sig, body) = fdef.args
const (name,args) = [sig.args[1], sig.args[2:end]]
const nbody = format_calls(body, fields, self)
const nname = gensym("$(name)_selfimpl")
const nargs = [:($self::$typ);args]
const nsig = :($nname($(nargs...)))
addmeta(Expr(:function, nsig, nbody))
end
function function_meta(def::Expr)
const meta = []
while def.head == :macrocall
unshift!(meta, def.args[1])
def = def.args[2]
end
function addmeta(ndef)
for m in meta
ndef = Expr(:macrocall, m, ndef)
end
return ndef
end
return (addmeta, def)
end
function format_calls(body, fields, self)
nbody = Expr(body.head)
nbody.args = map(redef_call(self, fields), body.args)
nbody
end
function redef_call(self, fields)
function rcall(x::Expr)
if x.head == :call
:($selfcall($(rcall(x.args[1])), $self, $(map(rcall, x.args[2:end])...)))
elseif x.head == :.
x
elseif x.head == :quote
rcall_quoted(x)
elseif x.head in [:(=), :function] && length(x.args) > 1
Expr(x.head, x.args[1], rcall(x.args[2]))
else
Expr(x.head, map(rcall, x.args)...)
end
end
rcall(x::Symbol) = x in fields? :($(self).$(x)) : x
rcall(x::QuoteNode) = rcall_quoted(x)
rcall(x) = x
function rcall_quoted(x::Expr, depth)
walkin(d) = Expr(x.head, map(y->rcall_quoted(y,d), x.args)...)
if x.head == :quote
walkin(depth+1)
elseif x.head == :$
if depth == 1
Expr(:$, rcall(x.args[1]))
else
walkin(depth-1)
end
else
walkin(depth)
end
end
rcall_quoted(x::QuoteNode,depth) = QuoteNode(rcall_quoted(x.value, depth+1))
rcall_quoted(x,depth) = x
rcall_quoted(x) = rcall_quoted(x,0)
return rcall
end
end
## Examples
#= Use preexisting type
using SelfFunctions
type MyType
x::Int
end
@selftype self MyType
=##= Define and then use type
using SelfFunctions
@selftype self type MyType
x::Int
end
=##= Usage of generated macro
@self function inc()
x += 1
end
@self function inc2()
inc()
inc()
end
const mt = MyType(0)
inc(mt) # mt.x: 0 -> 1
inc2(mt) # mt.x: 1 -> 3
=###
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment