From f69db638a2fd8dabc14c35caaab0394b498fc58f Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 25 Sep 2015 09:47:51 +0200 Subject: [PATCH 1/6] Branch to work on trait functions depending on multiple traits --- src/SimpleTraits.jl | 97 +++++++++++++++++++++++++++++++++++++++------ src/base-traits.jl | 11 +---- test/runtests.jl | 52 ++++++++++++++++++++++++ 3 files changed, 138 insertions(+), 22 deletions(-) diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index 0e04fe2..539abdc 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -4,36 +4,60 @@ const curmod = module_name(current_module()) # This is basically just adding a few convenience functions & macros # around Holy Traits. -export Trait, istrait, @traitdef, @traitimpl, @traitfn, Not +export Trait, istrait, @traitdef, @traitimpl, @traitfn, Not, + IsAnything, IsNothing -# All traits are concrete subtypes of this trait. SUPER is not used +# General trait exception +type TraitException <: Exception + msg::AbstractString +end + + +# All traits are subtypes of this trait. SUPER is not used # but present to be compatible with Traits.jl. ## @doc """ ## `abstract Trait{SUPER}` - """ -All Traits are subtypes of abstract type Trait. (SUPER is not used -here but in Traits.jl) +All Traits are subtypes of abstract type Trait or a Tuple of Traits. +(SUPER is not used here but in Traits.jl) """ -abstract Trait{SUPER} +abstract Trait{SUPER<:Tuple} # a concrete Trait will look like ## immutable Tr1{X,Y} <: Trait end # where X and Y are the types involved in the trait. +# function Base.show{T<:Trait}(io::IO, Tr::Type{T}) +# invoke(show, Tuple{IO, DataType}, io, Tr) +# print(" (a Trait)\n") +# end + +# Needs a better name: +immutable TraitIntersection{S<:Tuple} <: Trait{S} end # https://github.com/JuliaLang/julia/issues/13297 +# function Base.show{T<:Tuple}(io::IO, ti::Type{TraitIntersection{T}}) +# println(io, "TraitIntersection of:") +# for Tr in T.parameters +# print(io, " ") +# if Tr<:TraitIntersection +# show(io, Tr) +# else +# invoke(show, Tuple{IO, DataType}, io, Tr) +# end +# print(io, "\n") +# end +# end """ The set of all types not belonging to a trait is encoded by wrapping it with Not{}, e.g. Not{Tr1{X,Y}} """ -abstract Not{T<:Trait} <: Trait - +immutable Not{T<:Trait} <: Trait end # Helper to strip an even number of Not{}s off: Not{Not{T}}->T stripNot{T<:Trait}(::Type{T}) = T stripNot{T<:Trait}(::Type{Not{T}}) = Not{T} stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T) """ -A trait is defined as full filled if this function is the identity function for that trait. -Otherwise it returns the trait wrapped in `Not`. +A trait is defined as full filled if this function is the identity +function for that trait. Otherwise it returns the trait wrapped in `Not`. Example: ``` @@ -45,9 +69,22 @@ Instead of using `@traitimpl` one can define a method for `trait` to implement a trait. If this uses `@generated` functions it will be in-lined away. For example the `IsBits` trait is defined by: ``` +@traitdef IsBits{X} +@generated trait{X}(::Type{IsBits{X}}) = isbits(X) ? :(IsBits{X}) : :(Not{IsBits{X}}) +``` """ trait{T<:Trait}(::Type{T}) = Not{T} trait{T<:Trait}(::Type{Not{T}}) = trait(T) +@generated function trait{TU<:Tuple}(::Type{TraitIntersection{TU}}) + out = Any[] + for T in TU.parameters + if !(T<:Trait) + error("Need a tuple of traits") + end + push!(out, trait(T)) + end + return :(TraitIntersection{Tuple{$(out...)}}) +end ## Under the hood, a trait is then implemented for specific types by ## defining: @@ -67,7 +104,8 @@ istrait(Tr1{Int,Float64}) => return true or false """ istrait(::Any) = error("Argument is not a Trait.") istrait{T<:Trait}(tr::Type{T}) = trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265 - # thus is redefine when traits are defined + # thus it is redefine when traits are defined + """ Used to define a trait. Traits, like types, are camel cased. Often they start with `Is` or `Has`. @@ -79,7 +117,17 @@ Examples: ``` """ macro traitdef(tr) - :(immutable $(esc(tr)) <: Trait end) + if tr.head==:curly + :(immutable $(esc(tr)) <: Trait end) + elseif tr.head==:tuple + supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] + tr = tr.args[1].args[1] + :(immutable $(esc(tr)) <: Trait{Tuple{$(supert...)}} end) +# :(typealias $(esc(tr)) TraitIntersection{Tuple{$(supert...)}}) + else + throw(TraitException( + "Either define trait as `@traitdef Tr{...}` or as subtrait of at least two supertraits `@traitdef Tr{...} <: Tr1, Tr2`")) + end end """ @@ -107,6 +155,7 @@ macro traitimpl(tr) fnhead = :($curmod.trait{$(curly...)}($arg)) isfnhead = :($curmod.istrait{$(curly...)}($arg)) quote + $trname <: TraitIntersection && error("Cannot use @traitimpl with TraitIntersection: implement each super-trait by hand.") $fnhead = $trname{$(paras...)} $isfnhead = true # Add the istrait definition as otherwise # method-caching can be an issue. @@ -157,6 +206,23 @@ Defines a function dispatching on a trait: @traitfn f{X,Y; Tr1{X,Y}}(x::X,y::Y) = ... @traitfn f{X,Y; !Tr1{X,Y}}(x::X,y::Y) = ... # which is just sugar for: @traitfn f{X,Y; Not{Tr1{X,Y}}}(x::X,y::Y) = ... +``` + +CAUTION: trying to dispatch on several traits for just one method does +not work but silently fails! Example: +``` +@traitfn f{X; Tr1{X}}(x::X) = ... +@traitfn f{X; Tr2{X}}(x::X) = ... # this completely shadows the first method + # (even though it still exists in the method table!) +f{X}(x::X) = ... # this would also shadow either of above defs +``` +However, this is all fine: +``` +@traitfn f{X; Tr1{X}}(x::X) = ... +@traitfn f{X; !Tr1{X}}(x::X) = ... # ok, to do both the trait and its negation +@traitfn f{X; Tr2{X}}(x::X, y) = ... # ok, as method signature is different to above +@traitfn f{X; !Tr2{X}}(x::X, y) = ... + ``` """ macro traitfn(tfn) @@ -192,6 +258,13 @@ Base.done(::GenerateTypeVars, state) = false # Extras #### +"Trait which contains all types" +@traitdef IsAnything{X} +@traitimpl IsAnything{Any} +"Trait which contains no types" +typealias IsNothing{X} Not{IsAnything{X}} +# TODO what about IsAnything{X,Y} ? + include("base-traits.jl") end # module diff --git a/src/base-traits.jl b/src/base-traits.jl index c3f2f29..f861061 100644 --- a/src/base-traits.jl +++ b/src/base-traits.jl @@ -1,16 +1,7 @@ module BaseTraits using SimpleTraits -export IsLeafType, IsBits, IsImmutable, IsContiguous, IsFastLinearIndex, - IsAnything, IsNothing, IsCallable - -"Trait which contains all types" -@traitdef IsAnything{X} -SimpleTraits.trait{X}(::Type{IsAnything{X}}) = IsAnything{X} - -"Trait which contains no types" -typealias IsNothing{X} Not{IsAnything{X}} - +export IsLeafType, IsBits, IsImmutable, IsContiguous, IsFastLinearIndex, IsCallable "Trait of all isbits-types" @traitdef IsBits{X} diff --git a/test/runtests.jl b/test/runtests.jl index edb8eb3..4a37708 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,10 @@ using SimpleTraits using Base.Test trait = SimpleTraits.trait +Inter = SimpleTraits.TraitIntersection + +immutable A end +immutable B end # @test_throws MethodError trait(4) @test_throws ErrorException istrait(4) @@ -89,6 +93,54 @@ trait = SimpleTraits.trait @test gg27([1])==Array{Int,1} +# Tuple traits +# typealias STr3{X,Y} Inter{Tuple{Tr1{X}, Tr2{X,Y}}} +# @test istrait(STr3{Int,Float64}) +# @traitfn f54{X, Y; STr3{X,Y}}(x::X, y::Y) = 1 +# @traitfn f54{X, Y; !STr3{X,Y}}(x::X, y::Y) = 2 +# @test f54(4,5.0)==1 +# @test f54("asdf","asdf")==2 + +# @test_throws ErrorException @traitimpl STr3{A, Float64} +# @traitimpl Tr1{A} +# @traitimpl Tr2{A, Float64} +# @test f54(A(),5.0)==1 + +# # nested subtraits +# @traitdef Tr3{X} +# @traitimpl Tr3{A} +# typealias STr4{X,Y} Inter{Tuple{STr3{X,Y}, Tr3{X}}} +# @test istrait(STr4{A, Float64}) +# @test !istrait(STr4{Float64, A}) +# # use traitdef syntax: +# @traitdef STr44{X,Y} <: STr3{X,Y}, Tr3{X} +# @test STr44===STr4 +# @test STr44{A,Int}===STr4{A,Int} + +## several traits +@traitdef TT1{X} +@traitimpl TT1{A} +@traitdef TT2{Y} +@traitimpl TT2{B} +# this gives combinations: +@test trait(Inter{Tuple{TT1{A},TT2{B}}})==Inter{Tuple{ TT1{A} , TT2{B} }} +@test trait(Inter{Tuple{TT1{A},TT2{A}}})==Inter{Tuple{ TT1{A} , Not{TT2{A} }}} +@test trait(Inter{Tuple{TT1{B},TT2{B}}})==Inter{Tuple{Not{TT1{B}}, TT2{B} }} +@test trait(Inter{Tuple{TT1{B},TT2{A}}})==Inter{Tuple{Not{TT1{B}}, Not{TT2{A} }}} + + + + +# @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +# @traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +# @traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 +# @traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 + +# @test f55(A(),B())==1 +# @test f55(B(),B())==2 +# @test f55(B(),A())==3 +# @test f55(A(),A())==4 + ###### # Other tests ##### From 3961af996ddc4eb391fd0983c868eafe9ddfaaec Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 25 Sep 2015 12:02:18 +0200 Subject: [PATCH 2/6] Works so far --- README.md | 44 ++++++++++-- src/SimpleTraits.jl | 163 ++++++++++++++++++++++++++++++++------------ test/runtests.jl | 95 +++++++++++++++++--------- 3 files changed, 219 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 1234f62..1cecc89 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Note that Julia 0.3 is only supported up to tag This attempts to reduce the complexity of [Traits.jl](https://github.com/mauro3/Traits.jl), but at the same time -staying compatible. On the upside, it also works for Julia-0.3. It -drops support for: +staying compatible (but I think that is not possible currently). On +the upside, it also works for Julia-0.3 (up to tag v0.0.1). It drops +support for: - Trait definition in terms of methods and constraints. Instead the user needs to assign types to traits manually. This removes the @@ -42,7 +43,8 @@ then add types to the traits with Functions which dispatch on traits are constructed like: ```julia -@traitfn f{X; Tr1{X}}(x::X) = 1 +@traitfn f{X; Tr1{X}}(x::X) # initialize +@traitfn f{X; Tr1{X}}(x::X) = 1 @traitfn f{X; !Tr1{X}}(x::X) = 2 ``` This means that a type `X` which is part of the trait `Tr1` will @@ -54,6 +56,7 @@ dispatch to the method returning `1`, otherwise 2. Similarly for `Tr2`: ```julia +@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) @traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) = 1 @test f(5, "b", "a")==1 @test_throws MethodError f(5,5, "a")==2 @@ -63,13 +66,27 @@ Similarly for `Tr2`: Note that for one generic function, dispatch on traits can only work on one trait for a given signature. Continuing above example, this -does not work as one may expect: +does not work: ```julia @traitfn f{X; !Tr2{X,X}}(x::X) = 10 ``` -as this definition will just overwrite the definition `@traitfn f{X; -Tr1{X}}(x::X) = 1` from above. If you need to dispatch on several -traits, then you need Traits.jl. +and will result in a method which never gets called (without an error +being thrown). + +*Dispatch on several traits* + +It is also possible to dispatch on several traits: + +```julia +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 +@traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 +``` +Note that all methods need to feature the same traits (possibly +negated) in the same order. Any method violating that will never be +called (without an error being thrown). ## Advanced features @@ -136,6 +153,14 @@ b2 = f(b) @assert b2==B(2) ``` +# TODO + +- [ ] catch more syntax errors +- [ ] make a debug mode which catches when wrong trait-methods are + defined +- [ ] implement trait-inheritance +- [ ] implement default argument functions +- [ ] implement keyword functions # References @@ -151,3 +176,8 @@ b2 = f(b) ([Jutho's idea](https://github.com/JuliaLang/julia/issues/10889#issuecomment-94317470))? In particular could it be used in such a way that it is compatible with the multiple inheritance used in Traits.jl? + *Now with multi-traits functions this may not be necessary anymore: + the intersection of two traits can divide the types into four groups.* +- could there be Vararg traits? Generally yes as there are vararg + methods. However, in the current system it is not that easy as + there are no Vararg types. Maybe `Tr{Tuple{X,Y,...}}` could work. diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index 539abdc..c15ffaa 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -30,7 +30,16 @@ abstract Trait{SUPER<:Tuple} # print(" (a Trait)\n") # end -# Needs a better name: +""" +TraitIntersection is used when defining a trait-method like +``` +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 +``` +to encode the total trait `{TT1{X}, TT2{Y}}`. +""" immutable TraitIntersection{S<:Tuple} <: Trait{S} end # https://github.com/JuliaLang/julia/issues/13297 # function Base.show{T<:Tuple}(io::IO, ti::Type{TraitIntersection{T}}) # println(io, "TraitIntersection of:") @@ -75,16 +84,30 @@ in-lined away. For example the `IsBits` trait is defined by: """ trait{T<:Trait}(::Type{T}) = Not{T} trait{T<:Trait}(::Type{Not{T}}) = trait(T) -@generated function trait{TU<:Tuple}(::Type{TraitIntersection{TU}}) - out = Any[] - for T in TU.parameters - if !(T<:Trait) - error("Need a tuple of traits") + +""" +TraitIntersections use a generated method of the `trait` function to +evaluate whether a trait is fulfilled or not. If a new type is added +to a trait it can mean that this generated function is out of sync. +Use this macro to re-initialize it. (Triggers a warning) +""" +macro reset_trait_intersections() + out = esc(:out46785) # poor man's gensym + TU = esc(gensym()) + quote + @generated function SimpleTraits.trait{$TU<:Tuple}(::Type{TraitIntersection{$TU}}) + $out = Any[] + for T in $TU.parameters + if !(T<:Trait) + error("Need a tuple of traits") + end + push!($out, trait(T)) end - push!(out, trait(T)) + return :(SimpleTraits.TraitIntersection{Tuple{$(out46785...)}}) + end end - return :(TraitIntersection{Tuple{$(out...)}}) end +@reset_trait_intersections # initialize it ## Under the hood, a trait is then implemented for specific types by ## defining: @@ -120,10 +143,14 @@ macro traitdef(tr) if tr.head==:curly :(immutable $(esc(tr)) <: Trait end) elseif tr.head==:tuple - supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] - tr = tr.args[1].args[1] - :(immutable $(esc(tr)) <: Trait{Tuple{$(supert...)}} end) -# :(typealias $(esc(tr)) TraitIntersection{Tuple{$(supert...)}}) + ## Error: (I) + return :(throw(TraitException("Sub-traiting is not supported"))) + # supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] + # tr = tr.args[1].args[1] + ## Or proper supertrait (II) + # :(immutable $(esc(tr)) <: Trait{Tuple{$(supert...)}} end) + ## Or alias (III) + :(typealias $(esc(tr)) TraitIntersection{Tuple{$(supert...)}}) else throw(TraitException( "Either define trait as `@traitdef Tr{...}` or as subtrait of at least two supertraits `@traitdef Tr{...} <: Tr1, Tr2`")) @@ -177,53 +204,99 @@ function traitfn(tfn) else hasmac = false end + # see whether we're initializing the function or not: + if tfn.head==:call + init = true + # add a dummy body + tfn = :($tfn=begin 1 end) + elseif tfn.head==:function || tfn.head==:(=) + init = false + else + error("Not recognized: $tfn") + end + fhead = tfn.args[1] - fbody = tfn.args[2] fname = fhead.args[1].args[1] args = insertdummy(fhead.args[2:end]) - typs = fhead.args[1].args[3:end] - trait = fhead.args[1].args[2].args[1] - if isnegated(trait) - trait = trait.args[2] - val = :(::Type{$curmod.Not{$trait}}) - else - val = :(::Type{$trait}) - end - if hasmac - fn = :(@dummy $fname{$(typs...)}($val, $(args...)) = $fbody) - fn.args[1] = mac # replace @dummy - else - fn = :($fname{$(typs...)}($val, $(args...)) = $fbody) + tpara = fhead.args[1].args[3:end] + # the trait dispatch wrapper + if isa(fhead.args[1].args[2], Symbol) + error("There are no trait-constraints, e.g. f{X; Tr{X}}(...)") end - quote - $fname{$(typs...)}($(args...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait), $(striparg(args)...))) - $fn + traits = fhead.args[1].args[2].args[1:end] + trait_args = [isnegated(t) ? :($curmod.Not{$(t.args[2])}) : t for t in traits] + trait_args = length(trait_args)==1 ? trait_args[1] : :($curmod.TraitIntersection{Tuple{$(trait_args...)}}) + # TODO: add test to throw on @traitfn f56{X,Y; !(T4{X,Y}, T{X})}(x::X, y::Y) + + trait_args_noNot = [isnegated(t) ? :($(t.args[2])) : t for t in traits] + trait_args_noNot = length(trait_args_noNot)==1 ? trait_args_noNot[1] : :($curmod.TraitIntersection{Tuple{$(trait_args_noNot...)}}) + if init # the wrapper/trait-dispatch function: + return :($fname{$(tpara...)}($(args...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait_args_noNot), $(strip_tpara(args)...)))) + # TODO: + # - return also logic functions returning a nice error + + else # the logic: + fbody = tfn.args[2] + if hasmac + logic = :(@dummy $fname{$(tpara...)}(::Type{$trait_args}, $(args...)) = $fbody) + logic.args[1] = mac # replace @dummy + else + logic = :($fname{$(tpara...)}(::Type{$trait_args}, $(args...)) = $fbody) + end + # TODO: + # - could a error be thrown if a logic is added for a trait which is not wrapped? + return logic end end """ -Defines a function dispatching on a trait: +Defines a function dispatching on a trait. + +First initialize it to let it know on which trait it dispatches +``` +@traitfn f{X,Y; Tr1{X,Y}}(x::X,y::Y) +``` +then add trait methods: ``` @traitfn f{X,Y; Tr1{X,Y}}(x::X,y::Y) = ... @traitfn f{X,Y; !Tr1{X,Y}}(x::X,y::Y) = ... # which is just sugar for: @traitfn f{X,Y; Not{Tr1{X,Y}}}(x::X,y::Y) = ... ``` -CAUTION: trying to dispatch on several traits for just one method does -not work but silently fails! Example: +CAUTION: trying to dispatch on trait not initialized will not work: ``` -@traitfn f{X; Tr1{X}}(x::X) = ... -@traitfn f{X; Tr2{X}}(x::X) = ... # this completely shadows the first method - # (even though it still exists in the method table!) -f{X}(x::X) = ... # this would also shadow either of above defs +@traitfn f{X; Tr2{X}}(x::X) = ... # this will never be called ``` + However, this is all fine: ``` -@traitfn f{X; Tr1{X}}(x::X) = ... -@traitfn f{X; !Tr1{X}}(x::X) = ... # ok, to do both the trait and its negation -@traitfn f{X; Tr2{X}}(x::X, y) = ... # ok, as method signature is different to above -@traitfn f{X; !Tr2{X}}(x::X, y) = ... +@traitfn g{X; Tr1{X}}(x::X) +@traitfn g{X; Tr1{X}}(x::X) = ... +@traitfn g{X; !Tr1{X}}(x::X) = ... # ok, to do both the trait and its negation +@traitfn g{X; Tr2{X}}(x::X, y) # ok, as method signature is different to above +@traitfn g{X; Tr2{X}}(x::X, y) = ... +@traitfn g{X; !Tr2{X}}(x::X, y) = ... +``` + +Note, when updating a method, then re-initialize the trait function as +otherwise the old one is cached: +``` +@traitfn g{X; Tr1{X}}(x::X) +@traitfn g{X; Tr1{X}}(x::X) = new-logic +``` + +### Dispatching on several traits + +Is possible using this syntax: +``` +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 ``` +Note that all methods need to feature the same traits (possibly +negated) in the same order. Any method violating that will never be +called (and no error is thrown!). """ macro traitfn(tfn) esc(traitfn(tfn)) @@ -233,13 +306,13 @@ end ## Helpers ###### -# true if :(!(Tr{x})) -isnegated(t::Expr) = t.head==:call +# true if :(!(...)) +isnegated(t::Expr) = t.head==:call && t.args[1]==:! # [:(x::X)] -> [:x] -striparg(args::Vector) = Any[striparg(a) for a in args] -striparg(a::Symbol) = a -striparg(a::Expr) = a.args[1] +strip_tpara(args::Vector) = Any[strip_tpara(a) for a in args] +strip_tpara(a::Symbol) = a +strip_tpara(a::Expr) = a.args[1] # insert dummy: ::X -> gensym()::X insertdummy(args::Vector) = Any[insertdummy(a) for a in args] diff --git a/test/runtests.jl b/test/runtests.jl index 4a37708..b99aba2 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,8 +1,9 @@ using SimpleTraits using Base.Test -trait = SimpleTraits.trait -Inter = SimpleTraits.TraitIntersection +ST = SimpleTraits +trait = ST.trait +Inter = ST.TraitIntersection immutable A end immutable B end @@ -47,11 +48,18 @@ immutable B end @test trait(Tr2{Int, Float32})==Not{Tr2{Int, Float32}} # trait functions +@traitfn f{X; Tr1{X}}(x::X) @traitfn f{X; Tr1{X}}(x::X) = 1 # def 1 @traitfn f{X; !Tr1{X}}(x::X) = 2 @test f(5)==1 -@test f(5.)==2 - +@test f(5.)== 2 +# try adding a trait which was not part of original init: +@traitdef Tr0{X} +@traitimpl Tr0{A} +@traitfn f{X; Tr0{X}}(x::X) = 99 +@test f(A())!=99 + +@traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) @traitfn f{X,Y; Tr2{X,Y}}(x::X,y::Y,z) = 1 @test f(5,5., "a")==1 @test_throws MethodError f(5,5, "a")==2 @@ -59,6 +67,7 @@ immutable B end @test f(5,5, "a")==2 # This will overwrite the definition def1 above +@traitfn f{X; !Tr2{X,X}}(x::X) @traitfn f{X; !Tr2{X,X}}(x::X) = 10 @traitfn f{X; Tr2{X,X}}(x::X) = 100 @test f(5)==10 @@ -67,33 +76,39 @@ immutable B end @test f(5.)==10 @test !(f(5)==100) # need to update method cache: +@traitfn f{X; !Tr2{X,X}}(x::X) @traitfn f{X; Tr2{X,X}}(x::X) = 100 @test f(5)==100 @test f(5.)==10 # VarArg +@traitfn g{X; Tr1{X}}(x::X, y...) @traitfn g{X; Tr1{X}}(x::X, y...) = y @test g(5, 7, 8)==((7,8),) # @test g(5.0, 7, 8)==((7,8),) # hangs because of https://github.com/JuliaLang/julia/issues/13183 # with macro +@traitfn @inbounds gg{X; Tr1{X}}(x::X) @traitfn @inbounds gg{X; Tr1{X}}(x::X) = x @test gg(5)==5 +@traitfn @generated ggg{X; Tr1{X}}(x::X) @traitfn @generated ggg{X; Tr1{X}}(x::X) = X<:AbstractArray ? :(x+1) : :(x) @test ggg(5)==5 @traitimpl Tr1{AbstractArray} @test ggg([5])==[6] # traitfn with Type +@traitfn ggt{X; Tr1{X}}(::Type{X}, y) @traitfn ggt{X; Tr1{X}}(::Type{X}, y) = (X,y) @test ggt(Array, 5)==(Array, 5) # traitfn with ::X +@traitfn gg27{X; Tr1{X}}(::X) @traitfn gg27{X; Tr1{X}}(::X) = X @test gg27([1])==Array{Int,1} -# Tuple traits +# # Tuple traits # typealias STr3{X,Y} Inter{Tuple{Tr1{X}, Tr2{X,Y}}} # @test istrait(STr3{Int,Float64}) # @traitfn f54{X, Y; STr3{X,Y}}(x::X, y::Y) = 1 @@ -106,21 +121,10 @@ immutable B end # @traitimpl Tr2{A, Float64} # @test f54(A(),5.0)==1 -# # nested subtraits -# @traitdef Tr3{X} -# @traitimpl Tr3{A} -# typealias STr4{X,Y} Inter{Tuple{STr3{X,Y}, Tr3{X}}} -# @test istrait(STr4{A, Float64}) -# @test !istrait(STr4{Float64, A}) -# # use traitdef syntax: -# @traitdef STr44{X,Y} <: STr3{X,Y}, Tr3{X} -# @test STr44===STr4 -# @test STr44{A,Int}===STr4{A,Int} - -## several traits -@traitdef TT1{X} +## Trait intersections +@traitdef TT1{X} @traitimpl TT1{A} -@traitdef TT2{Y} +@traitdef TT2{Y} @traitimpl TT2{B} # this gives combinations: @test trait(Inter{Tuple{TT1{A},TT2{B}}})==Inter{Tuple{ TT1{A} , TT2{B} }} @@ -128,18 +132,47 @@ immutable B end @test trait(Inter{Tuple{TT1{B},TT2{B}}})==Inter{Tuple{Not{TT1{B}}, TT2{B} }} @test trait(Inter{Tuple{TT1{B},TT2{A}}})==Inter{Tuple{Not{TT1{B}}, Not{TT2{A} }}} - - - -# @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 -# @traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 -# @traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 -# @traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 - -# @test f55(A(),B())==1 -# @test f55(B(),B())==2 -# @test f55(B(),A())==3 -# @test f55(A(),A())==4 +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 +@traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 + +@test f55(A(),B())==1 +@test f55(B(),B())==2 +@test f55(B(),A())==3 + +@traitfn f55{X, Y; !TT2{Y}, TT1{X}}(x::X, y::Y) = 4 # oops traits are in reverse order! +@test_throws MethodError f55(A(),A())==4 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 +@test f55(A(),A())==4 + +# this fails because the generated-function has already been created above: +@traitimpl TT2{A} +@test !(trait(Inter{Tuple{TT1{A},TT2{A}}})==Inter{Tuple{ TT1{A} , TT2{A} }}) +println("-- This warning is ok:") +ST.@reset_trait_intersections +println("-- endof ok warning.") +@test trait(Inter{Tuple{TT1{A},TT2{A}}})==Inter{Tuple{ TT1{A} , TT2{A} }} + +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@test f55(A(),A())==1 + +# # Subtraits +@test_throws ST.TraitException @traitdef TT4{X,Y} <: Tr2{X,Y}, Tr1{X} # are not allowed +# @traitdef ST4{X,Y} <: TT1{X}, TT2{Y} # just an alias for the Intersection +# X,Y = TypeVar(:XX, true), TypeVar(:YY,true) +# @test ST4{X,Y}===Inter{Tuple{TT1{X}, TT2{Y}}} +# @test !(ST4{X,Y}===Inter{Tuple{TT1{Y}, TT2{X}}}) + +# @traitfn f56{X,Y; ST4{X,Y}}(x::X, y::Y) # note that ST4 is similar to {TT1{X}, TT2{Y}} +# @traitfn f56{X,Y; ST4{X,Y}}(x::X, y::Y) = 1 +# @traitfn f56{X,Y; !ST4{X,Y}}(x::X, y::Y) = 2 + +# @test f56(A(),B())==1 +# @test f56(A(),A())==1 +# @test f56(B(),B())==2 # this does not work because Not{ST4}!={!TT1{X}, TT2{Y}} +# @test f56(B(),A())==3 +# --> needs to be implemented with method II in traitdef. Method III does not work! ###### # Other tests From 2eda65b3867ba83b96e1af1551855344714a300f Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 25 Sep 2015 15:31:30 +0200 Subject: [PATCH 3/6] Fixed Vararg --- README.md | 3 --- src/SimpleTraits.jl | 26 ++++++++++++++++++++++++-- test/runtests.jl | 22 ++++++++++++++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1cecc89..4808a4f 100644 --- a/README.md +++ b/README.md @@ -178,6 +178,3 @@ b2 = f(b) with the multiple inheritance used in Traits.jl? *Now with multi-traits functions this may not be necessary anymore: the intersection of two traits can divide the types into four groups.* -- could there be Vararg traits? Generally yes as there are vararg - methods. However, in the current system it is not that easy as - there are no Vararg types. Maybe `Tr{Tuple{X,Y,...}}` could work. diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index c15ffaa..ac99a8d 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -312,12 +312,34 @@ isnegated(t::Expr) = t.head==:call && t.args[1]==:! # [:(x::X)] -> [:x] strip_tpara(args::Vector) = Any[strip_tpara(a) for a in args] strip_tpara(a::Symbol) = a -strip_tpara(a::Expr) = a.args[1] +function strip_tpara(a::Expr) + if a.head==:(::) + return a.args[1] + elseif a.head==:... + return Expr(:..., strip_tpara(a.args[1])) + else + error("Cannot parse argument: $a") + end +end # insert dummy: ::X -> gensym()::X +# also takes care of ... insertdummy(args::Vector) = Any[insertdummy(a) for a in args] insertdummy(a::Symbol) = a -insertdummy(a::Expr) = (a.head==:(::) && length(a.args)==1) ? Expr(:(::), gensym(), a.args[1]) : a +function insertdummy(a::Expr) + if a.head==:... + dotdot = true + a = a.args[1] + else + dotdot = false + end + if !isa(a, Symbol) && a.head==:(::) && length(a.args)==1 + out = Expr(:(::), gensym(), a.args[1]) + else + out = a + end + dotdot ? Expr(:..., out) : out +end # generates: X1, X2,... or x1, x2.... (just symbols not actual TypeVar) type GenerateTypeVars{CASE} diff --git a/test/runtests.jl b/test/runtests.jl index b99aba2..c2a9e93 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -82,10 +82,20 @@ immutable B end @test f(5.)==10 # VarArg -@traitfn g{X; Tr1{X}}(x::X, y...) -@traitfn g{X; Tr1{X}}(x::X, y...) = y -@test g(5, 7, 8)==((7,8),) -# @test g(5.0, 7, 8)==((7,8),) # hangs because of https://github.com/JuliaLang/julia/issues/13183 +@traitfn vara{X; Tr1{X}}(x::X, y...) +@traitfn vara{X; Tr1{X}}(x::X, y...) = y +@test vara(5, 7, 8)==(7,8) +# @test vara(5.0, 7, 8)==((7,8),) # hangs in lowering because of https://github.com/JuliaLang/julia/issues/13183 +@traitfn vara2{X; Tr1{X}}(x::X...) +@traitfn vara2{X; Tr1{X}}(x::X...) = x +@test vara2(5, 7, 8)==(5, 7, 8) +@test_throws MethodError vara2(5, 7, 8.0) + +@traitfn vara3{X; Tr1{X}}(::X...) +@traitfn vara3{X; Tr1{X}}(::X...) = X +@test vara3(5, 7, 8)==Int +@test_throws MethodError vara3(5, 7, 8.0) + # with macro @traitfn @inbounds gg{X; Tr1{X}}(x::X) @@ -174,6 +184,10 @@ println("-- endof ok warning.") # @test f56(B(),A())==3 # --> needs to be implemented with method II in traitdef. Method III does not work! +## Default arguments + +## Keyword + ###### # Other tests ##### From e418a5c8e951c3f0310afe78cc6e6cde9be606d1 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Fri, 25 Sep 2015 15:39:42 +0200 Subject: [PATCH 4/6] Improved insertdummy --- src/SimpleTraits.jl | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index ac99a8d..75f6581 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -310,6 +310,7 @@ end isnegated(t::Expr) = t.head==:call && t.args[1]==:! # [:(x::X)] -> [:x] +# also takes care of :... strip_tpara(args::Vector) = Any[strip_tpara(a) for a in args] strip_tpara(a::Symbol) = a function strip_tpara(a::Expr) @@ -323,22 +324,17 @@ function strip_tpara(a::Expr) end # insert dummy: ::X -> gensym()::X -# also takes care of ... +# also takes care of :... insertdummy(args::Vector) = Any[insertdummy(a) for a in args] insertdummy(a::Symbol) = a function insertdummy(a::Expr) - if a.head==:... - dotdot = true - a = a.args[1] - else - dotdot = false - end - if !isa(a, Symbol) && a.head==:(::) && length(a.args)==1 - out = Expr(:(::), gensym(), a.args[1]) + if a.head==:(::) && length(a.args)==1 + return Expr(:(::), gensym(), a.args[1]) + elseif a.head==:... + return Expr(:..., insertdummy(a.args[1])) else - out = a + return a end - dotdot ? Expr(:..., out) : out end # generates: X1, X2,... or x1, x2.... (just symbols not actual TypeVar) From 153ac470b194c7dbd262d6639bd150f49841365c Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 28 Sep 2015 13:07:00 +0200 Subject: [PATCH 5/6] Strange function caching bug on l.165 of runtests.jl Depends on whether TT = esc(:TT73840) or TT = esc(gensym()) --- README.md | 4 ++ src/SimpleTraits.jl | 140 +++++++++++++++++++++++++++++++++----------- test/runtests.jl | 82 +++++++++++++------------- 3 files changed, 152 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 4808a4f..8156bfb 100644 --- a/README.md +++ b/README.md @@ -178,3 +178,7 @@ b2 = f(b) with the multiple inheritance used in Traits.jl? *Now with multi-traits functions this may not be necessary anymore: the intersection of two traits can divide the types into four groups.* +- For trait Collections, Intersections, and TUnion, should a Union be + used for the underlying storage as that sorts and purges its inputs? + This would rely on un-supported Union behaviour, however it might be + quite nice. diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index 75f6581..3babc63 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -19,9 +19,9 @@ end ## `abstract Trait{SUPER}` """ All Traits are subtypes of abstract type Trait or a Tuple of Traits. -(SUPER is not used here but in Traits.jl) +SUPER contains the Intersection of its super traits. """ -abstract Trait{SUPER<:Tuple} +abstract Trait{SUPER} # SUPER<:Intersection but that is not possible # a concrete Trait will look like ## immutable Tr1{X,Y} <: Trait end # where X and Y are the types involved in the trait. @@ -31,21 +31,41 @@ abstract Trait{SUPER<:Tuple} # end """ -TraitIntersection is used when defining a trait-method like +The set of all types not belonging to a trait is encoded by wrapping +it with Not{}, e.g. Not{Tr1{X,Y}} +""" +immutable Not{T<:Trait} <: Trait end +# Helper to strip an even number of Not{}s off: Not{Not{T}}->T +stripNot{T<:Trait}(::Type{T}) = T +stripNot{T<:Trait}(::Type{Not{T}}) = Not{T} +stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T) + +""" +Collection of traits is used internally when defining a trait-method +like so ``` @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 @traitfn f55{X, Y; !TT1{X}, TT2{Y}}(x::X, y::Y) = 2 @traitfn f55{X, Y; !TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 ``` -to encode the total trait `{TT1{X}, TT2{Y}}`. + +It encodes the collection of traits `((TT1{X}, TT2{Y}),(!TT1{X}, +TT2{Y}),(TT1{X}, !TT2{Y}),(!TT1{X}, !TT2{Y}))`. Therefore, it is not +a single trait but kind of several traits. This is just used inside +trait-functions and should not be used otherwise. + +(It is just a typealias to a Tuple.) """ -immutable TraitIntersection{S<:Tuple} <: Trait{S} end # https://github.com/JuliaLang/julia/issues/13297 -# function Base.show{T<:Tuple}(io::IO, ti::Type{TraitIntersection{T}}) -# println(io, "TraitIntersection of:") +typealias Collection Tuple +# TODO: should `typealias Collection Union` ? +#immutable Collection{S<:Tuple} end + +# function Base.show{T<:Tuple}(io::IO, ti::Type{Collection{T}}) +# println(io, "Collection of:") # for Tr in T.parameters # print(io, " ") -# if Tr<:TraitIntersection +# if Tr<:Collection # show(io, Tr) # else # invoke(show, Tuple{IO, DataType}, io, Tr) @@ -54,15 +74,27 @@ immutable TraitIntersection{S<:Tuple} <: Trait{S} end # https://github.com/Julia # end # end + """ -The set of all types not belonging to a trait is encoded by wrapping -it with Not{}, e.g. Not{Tr1{X,Y}} +Constructs the intersection of several traits. A type(-tuple) belongs +to a intersection if all its traits are fulfilled. """ -immutable Not{T<:Trait} <: Trait end -# Helper to strip an even number of Not{}s off: Not{Not{T}}->T -stripNot{T<:Trait}(::Type{T}) = T -stripNot{T<:Trait}(::Type{Not{T}}) = Not{T} -stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T) +immutable Intersection{S<:Tuple} <: Trait end +# TODO: should S<:Union? + +# """ +# A new trait can be created from the union of several other +# (sub-)traits. A type(-tuple) belongs to this trait if at least one of its +# sub-traits are fulfilled. + +# TODO: maybe implement this? +# """ +# Constructs the union of several traits. A type(-tuple) belongs +# to a union if at least one of its traits is fulfilled. +# """ +# immutable TUnion{S<:Tuple} <: Trait end + + """ A trait is defined as full filled if this function is the identity @@ -86,28 +118,68 @@ trait{T<:Trait}(::Type{T}) = Not{T} trait{T<:Trait}(::Type{Not{T}}) = trait(T) """ -TraitIntersections use a generated method of the `trait` function to +Collections use a generated method of the `trait` function to evaluate whether a trait is fulfilled or not. If a new type is added to a trait it can mean that this generated function is out of sync. Use this macro to re-initialize it. (Triggers a warning) """ -macro reset_trait_intersections() +macro reset_trait_collections() + # TODO: + # - make specific to one trait-collection/trait-function + # - is it possible to make this cleaner? out = esc(:out46785) # poor man's gensym - TU = esc(gensym()) +# TT = esc(:TT73840) + TT = esc(gensym()) quote - @generated function SimpleTraits.trait{$TU<:Tuple}(::Type{TraitIntersection{$TU}}) - $out = Any[] - for T in $TU.parameters - if !(T<:Trait) - error("Need a tuple of traits") + @generated function SimpleTraits.trait{$TT<:Collection}(::Type{$TT}) + $out = Any[] + for T in $TT.parameters + if !(T<:Trait) + error("Need a tuple of traits") + end + push!($out, trait(T)) end - push!($out, trait(T)) + return :(Collection{$(out46785...)}) end - return :(SimpleTraits.TraitIntersection{Tuple{$(out46785...)}}) + # @generated function SimpleTraits.trait{$TT<:Collection}(::Type{Intersection{$TT}}) # this fn relies on <:Collection + # if trait($TT)===$TT + # return :(Intersection{$TT73840}) + # else + # return :(Not{Intersection{$TT73840}}) + # end + # end + + # @generated function SimpleTraits.trait{$TT<:Tuple}(::Type{SimpleTraits.Not{Collection{$TT}}}) + # $out = Any[] + # for T in $TT.parameters + # if !(T<:Trait) + # error("Need a tuple of traits") + # end + # push!($out, trait(T)) + # end + # return :(SimpleTraits.Collection{Tuple{$(out46785...)}}) + # end end +end +@reset_trait_collections # initialize it + +# @generated function SimpleTraits.trait{TT<:Collection}(::Type{Intersection{TT}}) # this fn relies on <:Collection +# if trait(TT)===TT +# return :(Intersection{TT}) +# else +# return :(Not{Intersection{TT}}) +# end +# end + +function SimpleTraits.trait{TT<:Collection}(::Type{Intersection{TT}}) # this fn relies on <:Collection + if trait(TT)===TT + return Intersection{TT} + else + return Not{Intersection{TT}} end end -@reset_trait_intersections # initialize it + + ## Under the hood, a trait is then implemented for specific types by ## defining: @@ -144,13 +216,13 @@ macro traitdef(tr) :(immutable $(esc(tr)) <: Trait end) elseif tr.head==:tuple ## Error: (I) - return :(throw(TraitException("Sub-traiting is not supported"))) - # supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] - # tr = tr.args[1].args[1] +# return :(throw(TraitException("Sub-traiting is not supported"))) + supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] + tr = tr.args[1].args[1] ## Or proper supertrait (II) - # :(immutable $(esc(tr)) <: Trait{Tuple{$(supert...)}} end) + :(immutable $(esc(tr)) <: Trait{Intersection{Tuple{$(supert...)}}} end) ## Or alias (III) - :(typealias $(esc(tr)) TraitIntersection{Tuple{$(supert...)}}) + # :(typealias $(esc(tr)) Collection{Tuple{$(supert...)}}) else throw(TraitException( "Either define trait as `@traitdef Tr{...}` or as subtrait of at least two supertraits `@traitdef Tr{...} <: Tr1, Tr2`")) @@ -182,7 +254,7 @@ macro traitimpl(tr) fnhead = :($curmod.trait{$(curly...)}($arg)) isfnhead = :($curmod.istrait{$(curly...)}($arg)) quote - $trname <: TraitIntersection && error("Cannot use @traitimpl with TraitIntersection: implement each super-trait by hand.") + $trname <: Collection && error("Cannot use @traitimpl with Trait-Collection: implement each super-trait by hand.") $fnhead = $trname{$(paras...)} $isfnhead = true # Add the istrait definition as otherwise # method-caching can be an issue. @@ -225,11 +297,11 @@ function traitfn(tfn) end traits = fhead.args[1].args[2].args[1:end] trait_args = [isnegated(t) ? :($curmod.Not{$(t.args[2])}) : t for t in traits] - trait_args = length(trait_args)==1 ? trait_args[1] : :($curmod.TraitIntersection{Tuple{$(trait_args...)}}) + trait_args = length(trait_args)==1 ? trait_args[1] : :(Tuple{$(trait_args...)}) # TODO: add test to throw on @traitfn f56{X,Y; !(T4{X,Y}, T{X})}(x::X, y::Y) trait_args_noNot = [isnegated(t) ? :($(t.args[2])) : t for t in traits] - trait_args_noNot = length(trait_args_noNot)==1 ? trait_args_noNot[1] : :($curmod.TraitIntersection{Tuple{$(trait_args_noNot...)}}) + trait_args_noNot = length(trait_args_noNot)==1 ? trait_args_noNot[1] : :(Tuple{$(trait_args_noNot...)}) if init # the wrapper/trait-dispatch function: return :($fname{$(tpara...)}($(args...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait_args_noNot), $(strip_tpara(args)...)))) # TODO: diff --git a/test/runtests.jl b/test/runtests.jl index c2a9e93..d7c9f9b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,7 +3,8 @@ using Base.Test ST = SimpleTraits trait = ST.trait -Inter = ST.TraitIntersection +Collection = ST.Collection +Intersection = ST.Intersection immutable A end immutable B end @@ -118,29 +119,23 @@ immutable B end @test gg27([1])==Array{Int,1} -# # Tuple traits -# typealias STr3{X,Y} Inter{Tuple{Tr1{X}, Tr2{X,Y}}} -# @test istrait(STr3{Int,Float64}) -# @traitfn f54{X, Y; STr3{X,Y}}(x::X, y::Y) = 1 -# @traitfn f54{X, Y; !STr3{X,Y}}(x::X, y::Y) = 2 -# @test f54(4,5.0)==1 -# @test f54("asdf","asdf")==2 - -# @test_throws ErrorException @traitimpl STr3{A, Float64} -# @traitimpl Tr1{A} -# @traitimpl Tr2{A, Float64} -# @test f54(A(),5.0)==1 - -## Trait intersections +## Trait intersections and collections @traitdef TT1{X} @traitimpl TT1{A} @traitdef TT2{Y} @traitimpl TT2{B} -# this gives combinations: -@test trait(Inter{Tuple{TT1{A},TT2{B}}})==Inter{Tuple{ TT1{A} , TT2{B} }} -@test trait(Inter{Tuple{TT1{A},TT2{A}}})==Inter{Tuple{ TT1{A} , Not{TT2{A} }}} -@test trait(Inter{Tuple{TT1{B},TT2{B}}})==Inter{Tuple{Not{TT1{B}}, TT2{B} }} -@test trait(Inter{Tuple{TT1{B},TT2{A}}})==Inter{Tuple{Not{TT1{B}}, Not{TT2{A} }}} + +# intersections +@test trait(Intersection{Tuple{TT1{A},TT2{B}}})==Intersection{Tuple{TT1{A}, TT2{B}}} +@test trait(Intersection{Tuple{TT1{A},TT2{A}}})==Not{Intersection{Tuple{TT1{A}, TT2{A}}}} +@test trait(Intersection{Tuple{TT1{B},TT2{B}}})==Not{Intersection{Tuple{TT1{B}, TT2{B}}}} +@test trait(Intersection{Tuple{TT1{B},TT2{A}}})==Not{Intersection{Tuple{TT1{B}, TT2{A}}}} + +# this gives collection combinations: +@test trait(Collection{TT1{A},TT2{B}})==Collection{ TT1{A} , TT2{B} } +@test trait(Collection{TT1{A},TT2{A}})==Collection{ TT1{A} , Not{TT2{A} }} +@test trait(Collection{TT1{B},TT2{B}})==Collection{Not{TT1{B}}, TT2{B} } +@test trait(Collection{TT1{B},TT2{A}})==Collection{Not{TT1{B}}, Not{TT2{A} }} @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 @@ -158,31 +153,38 @@ immutable B end # this fails because the generated-function has already been created above: @traitimpl TT2{A} -@test !(trait(Inter{Tuple{TT1{A},TT2{A}}})==Inter{Tuple{ TT1{A} , TT2{A} }}) +@test !(trait(Collection{TT1{A},TT2{A}})==Collection{ TT1{A} , TT2{A} }) println("-- This warning is ok:") -ST.@reset_trait_intersections +ST.@reset_trait_collections println("-- endof ok warning.") -@test trait(Inter{Tuple{TT1{A},TT2{A}}})==Inter{Tuple{ TT1{A} , TT2{A} }} +@test trait(Collection{TT1{A},TT2{A}})==Collection{ TT1{A} , TT2{A} } -@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) # clear cached +@traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 +@traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 4 @test f55(A(),A())==1 -# # Subtraits -@test_throws ST.TraitException @traitdef TT4{X,Y} <: Tr2{X,Y}, Tr1{X} # are not allowed -# @traitdef ST4{X,Y} <: TT1{X}, TT2{Y} # just an alias for the Intersection -# X,Y = TypeVar(:XX, true), TypeVar(:YY,true) -# @test ST4{X,Y}===Inter{Tuple{TT1{X}, TT2{Y}}} -# @test !(ST4{X,Y}===Inter{Tuple{TT1{Y}, TT2{X}}}) - -# @traitfn f56{X,Y; ST4{X,Y}}(x::X, y::Y) # note that ST4 is similar to {TT1{X}, TT2{Y}} -# @traitfn f56{X,Y; ST4{X,Y}}(x::X, y::Y) = 1 -# @traitfn f56{X,Y; !ST4{X,Y}}(x::X, y::Y) = 2 - -# @test f56(A(),B())==1 -# @test f56(A(),A())==1 -# @test f56(B(),B())==2 # this does not work because Not{ST4}!={!TT1{X}, TT2{Y}} -# @test f56(B(),A())==3 -# --> needs to be implemented with method II in traitdef. Method III does not work! +# Intersections +typealias SI4{X,Y} Intersection{Tuple{TT1{X}, TT2{Y}}} # just an alias for the Collectionsection +X,Y = TypeVar(:XX, true), TypeVar(:YY,true) +@test SI4{X,Y}===Intersection{Tuple{TT1{X}, TT2{Y}}} +@test trait(SI4{A,B})===SI4{A,B} +@test trait(SI4{B,B})===Not{SI4{B,B}} + +@traitfn f56{X,Y; SI4{X,Y}}(x::X, y::Y) # note that SI4 is similar to {TT1{X}, TT2{Y}} +@traitfn f56{X,Y; SI4{X,Y}}(x::X, y::Y) = 1 +@traitfn f56{X,Y; !SI4{X,Y}}(x::X, y::Y) = 2 + +@test f56(A(),B())==1 +@test f56(A(),A())==1 +@test f56(B(),B())==2 +@test f56(B(),A())==2 + +# Subtraits +@traitdef ST4{X,Y} <: TT1{X}, TT2{Y} # just an alias for the Collectionsection +X,Y = TypeVar(:XX, true), TypeVar(:YY,true) +@test super(ST4{X,Y}).parameters[1]===Intersection{Tuple{TT1{X}, TT2{Y}}} + ## Default arguments From 05a7ad7c825c870fc4d812a3d833c4c24438e65d Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Mon, 28 Sep 2015 15:04:15 +0200 Subject: [PATCH 6/6] Mostly done now --- src/SimpleTraits.jl | 159 +++++++++++++++++++++----------------------- test/runtests.jl | 20 +++++- 2 files changed, 93 insertions(+), 86 deletions(-) diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index 3babc63..cd6d2b4 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -1,9 +1,7 @@ +# This adds a few convenience functions & macros around Holy Traits. module SimpleTraits const curmod = module_name(current_module()) -# This is basically just adding a few convenience functions & macros -# around Holy Traits. - export Trait, istrait, @traitdef, @traitimpl, @traitfn, Not, IsAnything, IsNothing @@ -12,14 +10,10 @@ type TraitException <: Exception msg::AbstractString end - -# All traits are subtypes of this trait. SUPER is not used -# but present to be compatible with Traits.jl. -## @doc """ -## `abstract Trait{SUPER}` """ -All Traits are subtypes of abstract type Trait or a Tuple of Traits. -SUPER contains the Intersection of its super traits. +All Traits are subtypes of the abstract type Trait. The parameter +SUPER contains the Intersection of the super traits for a particular +trait. """ abstract Trait{SUPER} # SUPER<:Intersection but that is not possible # a concrete Trait will look like @@ -31,7 +25,7 @@ abstract Trait{SUPER} # SUPER<:Intersection but that is not possible # end """ -The set of all types not belonging to a trait is encoded by wrapping +The trait of all types not belonging to a trait is encoded by wrapping it with Not{}, e.g. Not{Tr1{X,Y}} """ immutable Not{T<:Trait} <: Trait end @@ -41,7 +35,7 @@ stripNot{T<:Trait}(::Type{Not{T}}) = Not{T} stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T) """ -Collection of traits is used internally when defining a trait-method +`Collection` is used internally when defining a trait-method like so ``` @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) @@ -53,14 +47,18 @@ like so It encodes the collection of traits `((TT1{X}, TT2{Y}),(!TT1{X}, TT2{Y}),(TT1{X}, !TT2{Y}),(!TT1{X}, !TT2{Y}))`. Therefore, it is not a single trait but kind of several traits. This is just used inside -trait-functions and should not be used otherwise. - -(It is just a typealias to a Tuple.) +trait-functions and should not be used directly. """ typealias Collection Tuple # TODO: should `typealias Collection Union` ? -#immutable Collection{S<:Tuple} end + +""" +Constructs the intersection of several traits. A type(-tuple) belongs +to a intersection if all its traits are fulfilled. +""" +immutable Intersection{S<:Tuple} <: Trait end +# TODO: should S<:Union? # function Base.show{T<:Tuple}(io::IO, ti::Type{Collection{T}}) # println(io, "Collection of:") # for Tr in T.parameters @@ -75,18 +73,12 @@ typealias Collection Tuple # end -""" -Constructs the intersection of several traits. A type(-tuple) belongs -to a intersection if all its traits are fulfilled. -""" -immutable Intersection{S<:Tuple} <: Trait end -# TODO: should S<:Union? - +## Trait Union: # """ # A new trait can be created from the union of several other # (sub-)traits. A type(-tuple) belongs to this trait if at least one of its # sub-traits are fulfilled. - +# # TODO: maybe implement this? # """ # Constructs the union of several traits. A type(-tuple) belongs @@ -107,7 +99,7 @@ trait(IsBits{Array}) # returns Not{IsBits{Array}} ``` Instead of using `@traitimpl` one can define a method for `trait` to -implement a trait. If this uses `@generated` functions it will be +implement a trait. If this uses `@generated` functions it should be in-lined away. For example the `IsBits` trait is defined by: ``` @traitdef IsBits{X} @@ -118,7 +110,7 @@ trait{T<:Trait}(::Type{T}) = Not{T} trait{T<:Trait}(::Type{Not{T}}) = trait(T) """ -Collections use a generated method of the `trait` function to +Collections and Intersections use a generated method of the `trait` function to evaluate whether a trait is fulfilled or not. If a new type is added to a trait it can mean that this generated function is out of sync. Use this macro to re-initialize it. (Triggers a warning) @@ -128,8 +120,10 @@ macro reset_trait_collections() # - make specific to one trait-collection/trait-function # - is it possible to make this cleaner? out = esc(:out46785) # poor man's gensym -# TT = esc(:TT73840) +# TT = esc(:TT73840) # leads to strange error! TT = esc(gensym()) + + TTT = esc(:TT73840) quote @generated function SimpleTraits.trait{$TT<:Collection}(::Type{$TT}) $out = Any[] @@ -141,55 +135,17 @@ macro reset_trait_collections() end return :(Collection{$(out46785...)}) end - # @generated function SimpleTraits.trait{$TT<:Collection}(::Type{Intersection{$TT}}) # this fn relies on <:Collection - # if trait($TT)===$TT - # return :(Intersection{$TT73840}) - # else - # return :(Not{Intersection{$TT73840}}) - # end - # end - - # @generated function SimpleTraits.trait{$TT<:Tuple}(::Type{SimpleTraits.Not{Collection{$TT}}}) - # $out = Any[] - # for T in $TT.parameters - # if !(T<:Trait) - # error("Need a tuple of traits") - # end - # push!($out, trait(T)) - # end - # return :(SimpleTraits.Collection{Tuple{$(out46785...)}}) - # end + @generated function SimpleTraits.trait{$TTT<:Collection}(::Type{Intersection{$TTT}}) # this fn relies on <:Collection + if trait($TTT)===$TTT + return :(Intersection{$TT73840}) + else + return :(Not{Intersection{$TT73840}}) + end + end end end @reset_trait_collections # initialize it -# @generated function SimpleTraits.trait{TT<:Collection}(::Type{Intersection{TT}}) # this fn relies on <:Collection -# if trait(TT)===TT -# return :(Intersection{TT}) -# else -# return :(Not{Intersection{TT}}) -# end -# end - -function SimpleTraits.trait{TT<:Collection}(::Type{Intersection{TT}}) # this fn relies on <:Collection - if trait(TT)===TT - return Intersection{TT} - else - return Not{Intersection{TT}} - end -end - - - -## Under the hood, a trait is then implemented for specific types by -## defining: -# trait(::Type{Tr1{Int,Float64}}) = Tr1{Int,Float64} -# or -# trait{I<:Integer,F<:FloatingPoint}(::Type{Tr1{I,F}}) = Tr1{I,F} -# -# Note due to invariance, this does probably not the right thing: -# trait(::Type{Tr1{Integer,FloatingPoint}}) = Tr1{Integer, FloatingPoint} - """ This function checks whether a trait is fulfilled by a specific set of types. @@ -200,10 +156,13 @@ istrait(Tr1{Int,Float64}) => return true or false istrait(::Any) = error("Argument is not a Trait.") istrait{T<:Trait}(tr::Type{T}) = trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265 # thus it is redefine when traits are defined + # with @traitimpl """ -Used to define a trait. Traits, like types, are camel cased. -Often they start with `Is` or `Has`. +Used to define a trait. Style wise I advocate the following: + +- Traits, like types, are camel cased. +- Often they start with `Is` or `Has`. Examples: ``` @@ -214,18 +173,24 @@ Examples: macro traitdef(tr) if tr.head==:curly :(immutable $(esc(tr)) <: Trait end) - elseif tr.head==:tuple + elseif tr.head==:tuple || tr.head==:comparison # trait inheritance + if tr.head==:comparison + supert = Any[esc(tr.args[3])] + tr = tr.args[1] + else + supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] + tr = tr.args[1].args[1] + end + ## There are a few options to handle this: ## Error: (I) -# return :(throw(TraitException("Sub-traiting is not supported"))) - supert = Any[esc(tr.args[1].args[3]), map(esc, tr.args[2:end])...] - tr = tr.args[1].args[1] + # return :(throw(TraitException("Sub-traiting is not supported"))) ## Or proper supertrait (II) :(immutable $(esc(tr)) <: Trait{Intersection{Tuple{$(supert...)}}} end) ## Or alias (III) # :(typealias $(esc(tr)) Collection{Tuple{$(supert...)}}) else throw(TraitException( - "Either define trait as `@traitdef Tr{...}` or as subtrait of at least two supertraits `@traitdef Tr{...} <: Tr1, Tr2`")) + "Either define trait as `@traitdef Tr{...}` or with one or more super-traits `@traitdef Tr{...} <: Tr1, Tr2`")) end end @@ -238,6 +203,8 @@ Example: @traitdef IsFast{X} @traitimpl IsFast{Array{Int,1}} ``` + +This errors if super-traits are not defined. """ macro traitimpl(tr) # makes @@ -254,7 +221,9 @@ macro traitimpl(tr) fnhead = :($curmod.trait{$(curly...)}($arg)) isfnhead = :($curmod.istrait{$(curly...)}($arg)) quote - $trname <: Collection && error("Cannot use @traitimpl with Trait-Collection: implement each super-trait by hand.") + $trname <: Intersection && error("Cannot use @traitimpl with Trait-Intersection: implement each intersected trait by hand.") + check_supertraits($(esc(tr))) + # TODO: allow option to implement all supertypes as well $fnhead = $trname{$(paras...)} $isfnhead = true # Add the istrait definition as otherwise # method-caching can be an issue. @@ -358,7 +327,7 @@ otherwise the old one is cached: ### Dispatching on several traits -Is possible using this syntax: +Is possible to dispatch on several traits using this syntax: ``` @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) @traitfn f55{X, Y; TT1{X}, TT2{Y}}(x::X, y::Y) = 1 @@ -366,8 +335,8 @@ Is possible using this syntax: @traitfn f55{X, Y; TT1{X}, !TT2{Y}}(x::X, y::Y) = 3 ``` -Note that all methods need to feature the same traits (possibly -negated) in the same order. Any method violating that will never be +*Note that all methods need to feature the same traits (possibly +negated) in the same order!* Any method violating that will never be called (and no error is thrown!). """ macro traitfn(tfn) @@ -378,6 +347,30 @@ end ## Helpers ###### +"""Returns the super traits""" +function getsuper{T<:Trait}(t::Type{T}) + S = t.super.parameters[1] + if isa(S,TypeVar) + return Base.Core.svec() + else + return S.parameters[1].parameters + end +end +getsuper{T<:Intersection}(t::Type{T}) = t.parameters[1].parameters + +""" +Checks whether all supertraits are fulfilled. If not, throws an error. +""" +check_supertraits(::Any) = error("Argument to `check_supertraits` is not a Trait.") +function check_supertraits{T<:Trait}(tr::Type{T}) + for ST in getsuper(tr) + if !istrait(ST) + throw(TraitException("Super trait $ST is not fulfilled. If it should be fulfilled, run `@traitimpl $ST` first.")) + end + end +end + + # true if :(!(...)) isnegated(t::Expr) = t.head==:call && t.args[1]==:! diff --git a/test/runtests.jl b/test/runtests.jl index d7c9f9b..01e7fff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -126,7 +126,7 @@ immutable B end @traitimpl TT2{B} # intersections -@test trait(Intersection{Tuple{TT1{A},TT2{B}}})==Intersection{Tuple{TT1{A}, TT2{B}}} +@test trait(Intersection{Tuple{TT1{A},TT2{B}}})== Intersection{Tuple{TT1{A}, TT2{B}}} @test trait(Intersection{Tuple{TT1{A},TT2{A}}})==Not{Intersection{Tuple{TT1{A}, TT2{A}}}} @test trait(Intersection{Tuple{TT1{B},TT2{B}}})==Not{Intersection{Tuple{TT1{B}, TT2{B}}}} @test trait(Intersection{Tuple{TT1{B},TT2{A}}})==Not{Intersection{Tuple{TT1{B}, TT2{A}}}} @@ -169,6 +169,7 @@ typealias SI4{X,Y} Intersection{Tuple{TT1{X}, TT2{Y}}} # just an alias for the X,Y = TypeVar(:XX, true), TypeVar(:YY,true) @test SI4{X,Y}===Intersection{Tuple{TT1{X}, TT2{Y}}} @test trait(SI4{A,B})===SI4{A,B} +@test trait(SI4{A,A})===SI4{A,A} # <-- some julia-issue here! @test trait(SI4{B,B})===Not{SI4{B,B}} @traitfn f56{X,Y; SI4{X,Y}}(x::X, y::Y) # note that SI4 is similar to {TT1{X}, TT2{Y}} @@ -180,12 +181,25 @@ X,Y = TypeVar(:XX, true), TypeVar(:YY,true) @test f56(B(),B())==2 @test f56(B(),A())==2 -# Subtraits -@traitdef ST4{X,Y} <: TT1{X}, TT2{Y} # just an alias for the Collectionsection +# Trait inheritance +@traitdef ST2{X,Y} <: TT1{X} +@traitimpl ST2{A,B} +@test istrait(ST2{A,B}) +@traitdef ST4{X,Y} <: TT1{X}, TT2{Y} X,Y = TypeVar(:XX, true), TypeVar(:YY,true) @test super(ST4{X,Y}).parameters[1]===Intersection{Tuple{TT1{X}, TT2{Y}}} +@traitimpl ST4{A,B} +@test istrait(ST4{A,B}) +@test_throws ST.TraitException @traitimpl ST4{Int,B} +@traitimpl TT1{Int} +@traitimpl ST4{Int,B} +@test istrait(ST4{Int,B}) +###### +# TODO +###### + ## Default arguments ## Keyword