Skip to content
This repository has been archived by the owner on Feb 7, 2019. It is now read-only.

[WIP] another parametric trait attempt #18

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/params.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Traits

@traitdef Functor{X{Y}} begin
fmap(Function,X) -> X
end

@traitimpl Functor{ Array{T,N} -> T } begin
fmap{T}( f::Function, x::Array{T,1} ) = map( f, x )
end
4 changes: 3 additions & 1 deletion src/Traits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ include("base_fixes.jl")
## common helper functions
include("helpers.jl")

const VERBOSE=true

#######
# Flags
#######
Expand Down Expand Up @@ -123,7 +125,7 @@ end

`istrait( (Tr1{Int, Float64}, Tr2{Int}) )`
""" ->
function istrait{T<:Trait}(Tr::Type{T}; verbose=false)
function istrait{T<:Trait}(Tr::Type{T}; verbose=VERBOSE)
if verbose
println_verb(x) = println("**** Checking $(deparameterize_type(Tr)): " * x)
else
Expand Down
173 changes: 153 additions & 20 deletions src/traitdef.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,33 +13,99 @@
# end
# end

traitparamscount_registry = Dict{Symbol, Array{Int,1}}()

# this function is augmented on-the-fly by additional methods declared using
# traitimpl macro
traitparams{S,T,N}( ::Type{Val{S}}, ::T, ::Type{Val{N}} ) = (T.parameters[end],)

function traitparams_delayed{T}( s::Symbol, dt::Type{T}, n::Int )
ex = :( Traits.traitparams( Val{$s}, $dt, Val{$n} ) )
ex.args[2].args[2] = Expr( :quote, s )
if VERBOSE
println( ex )
end
eval( ex )
end

function traitparamscount( tr::Symbol )
global traitparamscount_registry
if haskey( traitparamscount_registry, tr )
return traitparamscount_registry[ tr ]
else
return Int[0]
end
end

# 1) parse the header
###
function parsecurly(def::Expr)
# parses :(Cmp{x,y})
# into: :Cmp, [:x,:y], :(Cmp{x,y}), ()

# parses :(Monad{X{Y}})
# into: :Monad, [:(X{Y})], :(Monad{X}), ()

# multiple parametric trait:
# parses :(ComboTr{X{Y}, Z{T}})
# into: :ComboTr, [:(X{Y}), :(Z{T}) ]], :(ComboTr{X,Z}), ()

# Note that if we have
# :(ComboTr{X{Y}, Z{Y}}) # note the same Y
# The trait constructor will have an assertion that the parameter in X and Z must match
name = def.args[1]
paras = Symbol[]
paras = Union{Symbol,Expr}[]
append!(paras,def.args[2:end])
trait = def
trait = Expr( :curly, name,
map( x->typeof(x)==Symbol ? x : Base.Meta.isexpr( x, :curly )? x.args[1] : error( "Traits: unknown " * string(x) ),
def.args[2:end])... )
return name, paras, trait, :(Tuple{})
end

function parsecomp(def::Expr)
# parses :(Cmp{x,y} <: Eq{x,y})
# into: :Cmp, [:x,:y], :(Cmp{x,y}), :(Tuple{Eq{x,y}})

# parses :(Tr2{X{Z1},Y{Z2}} <: Tr2base{X,Y})
# into: :Tr2, [:(X{Z1}),:(Y{Z2})], :(Tr2{X,Y}), :((Tr2base{X,Y},))
# the supertraits' parameters are redundant and, if given, are stripped out. So the following
# would produce the same output
# :(Tr2{X{Z1},Y{Z2}} <: Tr2base{X{Z1},Y{Z2}})
if def.args[2]!=:<:
error("not a <:")
end
name, paras, trait = parsecurly(def.args[1])
supertraits = :(Tuple{$(def.args[3])})

if !Base.Meta.isexpr( def.args[3], :curly )
error( "Traits: RHS of " * string( def ) * " must be a curly (Trait) expression" )
end
c = def.args[3]
supertraittype = Expr( :curly, c.args[1],
map( x->typeof(x)==Symbol ? x : Base.Meta.isexpr( x, :curly )? x.args[1] : error( "Traits: unknown " * string(x) ),
c.args[2:end] )... )
supertraits = :(Tuple{$(supertraittype)})
return name, paras, trait, supertraits
end

function parsetuple(def::Expr)
# parses :(Cmp{x,y} <: Eq{x,y}, Sz{x}, Uz{y})
# into :Cmp, [:x,:y], :(Cmp{x,y}), :(Tuple{Eq{x,y},Sz{x},Uz{y}})

# parses :(Tr2{X{Z1},Y{Z2}} <: Tr2base{X,Y}), Tr1base{X}
# into: :Tr2, [:(X{Z1}),:(Y{Z2})], :(Tr2{X,Y}), :((Tr2base{X,Y},Tr1base{X}))
# the supertraits' parameters are redundant and, if given, are stripped out. So the following
# would produce the same output
# :(Tr2{X{Z1},Y{Z2}} <: Tr2base{X{Z1},Y{Z2}}, Tr1base{X{Z1}})
name, paras, trait, supertraits = parsecomp(def.args[1])
append!(supertraits.args, def.args[2:end])
for i in 2:length(def.args)
c = def.args[i]
if !Base.Meta.isexpr( c, :curly )
error( "Traits: supertrait #" * string(i) * " is not a curly (Trait) expression" )
end
push!( supertraits.args, Expr( :curly, c.args[1],
map( x->typeof(x)==Symbol ? x : Base.Meta.isexpr( x, :curly )? x.args[1] : error( "Traits: unknown " * string(x) ),
c.args[2:end] )... ) )
end
return name, paras, trait, supertraits
end

Expand Down Expand Up @@ -70,13 +136,32 @@ function parsetraithead(def::Expr)
end
# make :(immutable Cmp{X,Y} <: Trait{(Eq{X,Y}, Tr1{X})} end)
out = :(immutable $trait <: Traits.Trait{$supertraits} end)
return out, name

# capture type parameters e.g. the Y in Monad{X{Y}}
paramscount = Int[]
for p in paras
if Base.Meta.isexpr( p, :curly )
for i=2:length(p.args)
@assert( typeof( p.args[i] ) == Symbol )
end
push!( paramscount, length(p.args)-1 )
else
push!( paramscount, 0 )
end
end
global traitparamscount_registry

if haskey( traitparamscount_registry, name ) && traitparamscount_registry[ name ] != paramscount
println( "Trait " * string( name ) * " is redefined with a different params signature." )
end
traitparamscount_registry[ name ] = paramscount
return out, name, paras
end

# 2) parse the function definitions
###

function parsebody(body::Expr)
function parsebody(name::Symbol, body::Expr, paras::Array{Union{Symbol,Expr},1} )
# Transforms:
# body = quote
# R = g(X)
Expand All @@ -88,32 +173,74 @@ function parsebody(body::Expr)
# end
#
# into
#



# :(Bool[X==Y]),
# :(...associated types...)
isassoc(ex::Expr) = ex.head==:(=) # associated types
isconstraints(ex::Expr) = ex.head==:macrocall # constraints

outfns = :(Traits.FDict())
constr = :(Bool[])
assoc = quote end
local_typesyms = Set{Symbol}()
push!( local_typesyms, name )
# but first, add the assoc types from the head
paramscount = traitparamscount( name )

for (i,p) in enumerate( paras )
if Base.Meta.isexpr( p, :curly )
hosttype = p.args[1]
names = p.args[2:end]

lhsnames = map( x->in(x,local_typesyms ) ? symbol("__",x,i,"__") : x , names )

# the reason we do eval_curmod is because we don't want
# to precompile this part: the actual trait parameters
# are defined later
assocexpr = :( () = placeholder) # note placeholder
append!( assocexpr.args[1].args, lhsnames )
# to replace the placeholder above
subexpr = :( Traits.traitparams_delayed(:$name, $hosttype, $i ) )
subexpr.args[2] = Expr( :quote, name )
assocexpr.args[2] = subexpr
push!( assoc.args, assocexpr )

for j in 1:length(names)
push!( local_typesyms, names[j] )
if lhsnames[j] != names[j]
lhs = lhsnames[j]
rhs = names[j]
teststmt = :(
if( $lhs != $rhs )
Base.throw( string( "Trait: ", " pos=", $(i), " giventype=",
String($hosttype) ," curlypos=",$(j), " expects ",String($rhs), ". got ", string($lhs) ) )
end )
teststmt.args[2].args[2].args[2].args[2] = "Trait: " * string(name)
push!( assoc.args, teststmt )
end
end
push!( local_typesyms, hosttype )
elseif typeof( p ) == Symbol
push!( local_typesyms, p )
end
end
for ln in Lines(body)
if isconstraints(ln)
parseconstraints!(constr, ln)
elseif isassoc(ln)
push!( local_typesyms, ln.args[1] )
push!(assoc.args, ln)
else # the rest of the body are function signatures
parsefnstypes!(outfns, ln)
parsefnstypes!(outfns, ln, local_typesyms )
end
end
# store associated types (no need for TypeVar here):
# store associated types
tmp = :(Any[])
for ln in Lines(assoc)
tvar = ln.args[1]
push!(tmp.args, tvar)
for tvar in local_typesyms
if tvar == name
continue
end
stvar = string(tvar)
push!(tmp.args, :(TypeVar(symbol($stvar) ,$tvar)))
end
push!(assoc.args, :(assoctyps = $tmp))
return outfns, constr, assoc
Expand All @@ -133,7 +260,9 @@ function parseconstraints!(constr, block)
end
end

function parsefnstypes!(outfns, ln)
function parsefnstypes!(outfns::Expr, lnargs::Expr, local_typesyms::Set{Symbol} )
ln = deepcopy( lnargs )

# parse one line containing a function definition
function parsefn(def)
# Parse to get function signature.
Expand Down Expand Up @@ -268,21 +397,25 @@ end
""" ->
macro traitdef(head, body)
## make Trait type
traithead, name = parsetraithead(head)
traithead, name, paras = parsetraithead(head)
# make the body
meths, constr, assoc = parsebody(body)
meths, constr, assoc = parsebody(name, body, paras )
# make sure a generic function of all associated types exisits

traitbody = quote
methods::Traits.FDict
constraints::Vector{Bool}
assoctyps::Vector{Any}
assoctyps::Vector{TypeVar}
function $((name))()
$assoc
new( $meths, $constr, assoctyps)
end
end
# add body to the type definition
traithead.args[3] = traitbody

if VERBOSE
println( traithead )
end
return esc(traithead)
end
12 changes: 11 additions & 1 deletion src/traitfns.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# @traitfn f1{S,T<:Integer; D1{S}, D1{T} }(s::S,t::T) = sin(s) - sin(t)
# @traitfn f1{X,Y<:FloatingPoint; D1{X}, D1{Y} }(x::X,y::Y) = cos(x) - cos(y)

typealias FName Union(Symbol,Expr)
typealias FName Union{Symbol,Expr}

# generates: X1, X2,... or x1, x2.... (just symbols not actual TypeVar)
type GenerateTypeVars{CASE}
Expand All @@ -27,6 +27,16 @@ type ParsedFn # (probably should adapt MetaTools.jl...)
traits::Vector{Any} # [D1{X}, D2{X,Y}]
body::Expr # quote ... end
end
function ==(p::ParsedFn, q::ParsedFn)
out = true
for n in fieldnames(p)
out = out && getfield(p,n)==getfield(q,n)
if !out
@show n, getfield(p,n), getfield(q,n)
end
end
out
end

# Parsing:
function parsetraitfn_head(head::Expr)
Expand Down
Loading