From 2d5112b3d50ec9d67eec7c9a5a06073fb079ff87 Mon Sep 17 00:00:00 2001 From: Mauro Werder Date: Wed, 19 Oct 2016 10:57:46 +0200 Subject: [PATCH] +/- working Need to consider whether it makes sense to have subtraits or whether an intersection would be better. --- src/SimpleTraits.jl | 87 +++++++++++++++++++++++++++++++++------------ src/base-traits.jl | 13 +++---- test/runtests.jl | 30 ++++++++++++++++ 3 files changed, 101 insertions(+), 29 deletions(-) diff --git a/src/SimpleTraits.jl b/src/SimpleTraits.jl index a91fc1f..d5b15e2 100644 --- a/src/SimpleTraits.jl +++ b/src/SimpleTraits.jl @@ -7,7 +7,7 @@ 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, supertraits, @traitdef, @traitimpl, @traitfn, Not # All traits are concrete subtypes of this trait. SUPER is not used # but present to be compatible with Traits.jl. @@ -24,16 +24,19 @@ immutable Tr1{X,Y} <: Trait end where X and Y are the types involved in the trait. -(SUPER is not used here but in Traits.jl, thus retained for possible -future compatibility.) +The type parameter SUPER of Trait is needed to specify super-traits (a +tuple). """ abstract Trait{SUPER} +"Return super-traits" +supertraits{T<:Trait}(t::Type{T}) = t.super.parameters[1] + """ 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 @@ -41,9 +44,15 @@ stripNot{T<:Trait}(::Type{Not{T}}) = Not{T} stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T) """ +This is the function used to do the trait-dispatch. Note that +anything but a constant method will probably not be inlined away by +the JIT and will lead to slower dynamic dispatch. + 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`. +`Not`. Note that *no super-trait checking* is done when this function +is called. Thus, if appropriate, before this function is defined for +a trait a check that its super-traits are defined should be done. Example: ``` @@ -70,35 +79,61 @@ trait{T<:Trait}(::Type{Not{T}}) = trait(T) """ This function checks whether a trait is fulfilled by a specific -set of types. +set of types: ``` istrait(Tr1{Int,Float64}) => return true or false ``` + +or that all traits of a Tuple of traits are fulfilled +``` +istrait( Tuple{Tr1{Int,Float64}, Tr2{Int}} ) => 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 +function istrait{T<:Trait}(tr::Type{T}) + !isleaftype(tr) && error("Not all parameters of $tr are specified, thus not a valid trait.") + trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265 +end # thus is redefine when traits are defined +istrait{T<:Tuple}(tr::Type{T}) = mapreduce(istrait, &, true, tr.parameters) + """ -Used to define a trait. Traits, like types, are camel cased. I -suggest to start them with a verb, e.g. `IsImmutable`, to distinguish -them from actual types, which are usually nouns. +Defines a trait. Traits need to have one or more (type-)parameters to +specify the type to which the trait is applied. For instance +`IsImmutable{Int}` signifies that `Int` is part of `IsImmutable` +(although whether that is true needs to be checked with the `istrait` +function). Most traits will be one-parameter traits, however, several +parameters are useful when there is a "contract" between several +types. + +Traits can be sub-traits of one or several other traits. A trait is +then fulfilled if it is fulfilled itself and all its super-traits are +also fulfilled. + +Traits, like types, are camel cased. I suggest to start them with a +verb, e.g. `IsImmutable`, to distinguish them from actual types, which +are usually nouns. -Traits need to have one or more (type-)parameters to specify the type -to which the trait is applied. For instance `IsImmutable{Int}` -signifies that `Int` is part of `IsImmutable` (although whether that -is true needs to be checked with the `istrait` function). Most traits -will be one-parameter traits, however, several parameters are useful -when there is a "contract" between several types. Examples: ```julia @traitdef IsFast{X} -@traitdef IsSlow{X,Y} +@traitdef IsSuperFast{X} <: IsFast{X} +@traitdef IsHyper{X} +@traitdef IsHyperFast{X} <: IsSuperFast{X}, IsHyper{X} ``` """ macro traitdef(tr) - :(immutable $(esc(tr)) <: Trait end) + Tr, Pr, SUPER = @match tr begin + Tr_{Pr__} <: SUPER__ => (Tr, Pr, SUPER) + Tr_{Pr__} <: SUPER1_,SUPER__ => (Tr, Pr, [SUPER1, SUPER...]) + Tr_{Pr__} => (Tr, Pr, []) + end + tmp = :(Tuple{}) + append!(tmp.args, SUPER) + T = esc(:($Tr{$(Pr...)})) + :(immutable $T <: Trait{$(esc(tmp))} end) end """ @@ -112,8 +147,10 @@ Example: ``` """ macro traitimpl(tr) - # makes + # makes from @traitimpl Tr1{Int,Float64} + # istrait(supertrait(Tr1{Int,Float64})) || error("...") # trait{X1<:Int,X2<:Float64}(::Type{Tr1{X1,X2}}) = Tr1{X1,X2} + # istrait{X1<:Int,X2<:Float64}(::Type{Tr1{X1,X2}}) = true if tr.args[1]==:Not || isnegated(tr) tr = tr.args[2] negated = true @@ -129,17 +166,21 @@ macro traitimpl(tr) push!(paras, esc(v)) end arg = :(::Type{$trname{$(paras...)}}) - fnhead = :($curmod.trait{$(curly...)}($arg)) - isfnhead = :($curmod.istrait{$(curly...)}($arg)) + fnhead = :(trait{$(curly...)}($arg)) + isfnhead = :(istrait{$(curly...)}($arg)) + trr = :($trname{$(paras...)}) + errstr = :("Not all super-traits of $($(esc(tr))) fulfilled") if !negated return quote - $fnhead = $trname{$(paras...)} + !istrait(supertraits($(esc(tr)))) && error($errstr) + $fnhead = $trr $isfnhead = true # Add the istrait definition as otherwise # method-caching can be an issue. end else return quote - $fnhead = Not{$trname{$(paras...)}} + !istrait(supertraits($(esc(tr)))) && error($errstr) + $fnhead = Not{$trr} $isfnhead = false# Add the istrait definition as otherwise # method-caching can be an issue. end diff --git a/src/base-traits.jl b/src/base-traits.jl index 400bd28..60407b1 100644 --- a/src/base-traits.jl +++ b/src/base-traits.jl @@ -1,12 +1,13 @@ module BaseTraits using SimpleTraits +import SimpleTraits: trait 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{X}(::Type{IsAnything{X}}) = IsAnything{X} "Trait which contains no types" typealias IsNothing{X} Not{IsAnything{X}} @@ -14,17 +15,17 @@ typealias IsNothing{X} Not{IsAnything{X}} "Trait of all isbits-types" @traitdef IsBits{X} -@generated SimpleTraits.trait{X}(::Type{IsBits{X}}) = +@generated trait{X}(::Type{IsBits{X}}) = isbits(X) ? :(IsBits{X}) : :(Not{IsBits{X}}) "Trait of all immutable types" @traitdef IsImmutable{X} -@generated SimpleTraits.trait{X}(::Type{IsImmutable{X}}) = +@generated trait{X}(::Type{IsImmutable{X}}) = X.mutable ? :(Not{IsImmutable{X}}) : :(IsImmutable{X}) "Trait of all callable objects" @traitdef IsCallable{X} -@generated SimpleTraits.trait{X}(::Type{IsCallable{X}}) = +@generated trait{X}(::Type{IsCallable{X}}) = (X==Function || length(methods(call, (X,Vararg)))>0) ? IsCallable{X} : Not{IsCallable{X}} "Trait of all leaf types types" @@ -33,12 +34,12 @@ typealias IsNothing{X} Not{IsAnything{X}} "Types which have contiguous memory layout" @traitdef IsContiguous{X} # https://github.com/JuliaLang/julia/issues/10889 -@generated SimpleTraits.trait{X}(::Type{IsContiguous{X}}) = +@generated trait{X}(::Type{IsContiguous{X}}) = Base.iscontiguous(X) ? :(IsContiguous{X}) : :(Not{IsContiguous{X}}) "Array indexing trait." @traitdef IsFastLinearIndex{X} # https://github.com/JuliaLang/julia/pull/8432 -@generated function SimpleTraits.trait{X}(::Type{IsFastLinearIndex{X}}) +@generated function trait{X}(::Type{IsFastLinearIndex{X}}) if Base.linearindexing(X)==Base.LinearFast() return :(IsFastLinearIndex{X}) elseif Base.linearindexing(X)==Base.LinearSlow() diff --git a/test/runtests.jl b/test/runtests.jl index 8a6e2c4..e9168e3 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -165,6 +165,36 @@ println("") @test f12t(1)==1 @test f12t(5.5)==2 +#### +# Trait inheritiance +### +@traitdef TI1{X} +@test supertraits(TI1)==Tuple{} +@test supertraits(TI1{Int})==Tuple{} +@traitdef TI2{X,Y} +@test supertraits(TI2)==Tuple{} +@traitdef TI_12{X,Y} <: TI1{X}, TI1{Y}, TI2{X,Y} +@test supertraits(TI_12{Int,Float64})==Tuple{TI1{Int}, TI1{Float64}, TI2{Int,Float64} } +@test !istrait(TI_12{Int,Float64}) + +@test_throws ErrorException @traitimpl TI_12{Int,Float64} + +@traitimpl TI1{Int} +@traitimpl TI1{Float64} +@traitimpl TI2{Int,Float64} +@traitimpl TI_12{Int,Float64} + +@test istrait(TI_12{Int,Float64}) + + +###### +# brocken +###### + +@traitimpl TI2{Dict} +@test_broken !istrait(TI2{Dict}) +# ideally an error should be thrown if tr in trait(tr) in not leaftype. + ###### # Other tests #####