Skip to content

Commit 2d5112b

Browse files
committed
+/- working
Need to consider whether it makes sense to have subtraits or whether an intersection would be better.
1 parent afcec3b commit 2d5112b

File tree

3 files changed

+101
-29
lines changed

3 files changed

+101
-29
lines changed

src/SimpleTraits.jl

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const curmod = module_name(current_module())
77
# This is basically just adding a few convenience functions & macros
88
# around Holy Traits.
99

10-
export Trait, istrait, @traitdef, @traitimpl, @traitfn, Not
10+
export Trait, istrait, supertraits, @traitdef, @traitimpl, @traitfn, Not
1111

1212
# All traits are concrete subtypes of this trait. SUPER is not used
1313
# but present to be compatible with Traits.jl.
@@ -24,26 +24,35 @@ immutable Tr1{X,Y} <: Trait end
2424
where X and Y are the types involved in the trait.
2525
2626
27-
(SUPER is not used here but in Traits.jl, thus retained for possible
28-
future compatibility.)
27+
The type parameter SUPER of Trait is needed to specify super-traits (a
28+
tuple).
2929
"""
3030
abstract Trait{SUPER}
3131

32+
"Return super-traits"
33+
supertraits{T<:Trait}(t::Type{T}) = t.super.parameters[1]
34+
3235
"""
3336
The set of all types not belonging to a trait is encoded by wrapping
3437
it with Not{}, e.g. Not{Tr1{X,Y}}
3538
"""
36-
abstract Not{T<:Trait} <: Trait
39+
immutable Not{T<:Trait} <: Trait end
3740

3841
# Helper to strip an even number of Not{}s off: Not{Not{T}}->T
3942
stripNot{T<:Trait}(::Type{T}) = T
4043
stripNot{T<:Trait}(::Type{Not{T}}) = Not{T}
4144
stripNot{T<:Trait}(::Type{Not{Not{T}}}) = stripNot(T)
4245

4346
"""
47+
This is the function used to do the trait-dispatch. Note that
48+
anything but a constant method will probably not be inlined away by
49+
the JIT and will lead to slower dynamic dispatch.
50+
4451
A trait is defined as full-filled if this function is the identity
4552
function for that trait. Otherwise it returns the trait wrapped in
46-
`Not`.
53+
`Not`. Note that *no super-trait checking* is done when this function
54+
is called. Thus, if appropriate, before this function is defined for
55+
a trait a check that its super-traits are defined should be done.
4756
4857
Example:
4958
```
@@ -70,35 +79,61 @@ trait{T<:Trait}(::Type{Not{T}}) = trait(T)
7079

7180
"""
7281
This function checks whether a trait is fulfilled by a specific
73-
set of types.
82+
set of types:
7483
```
7584
istrait(Tr1{Int,Float64}) => return true or false
7685
```
86+
87+
or that all traits of a Tuple of traits are fulfilled
88+
```
89+
istrait( Tuple{Tr1{Int,Float64}, Tr2{Int}} ) => return true or false
90+
```
7791
"""
7892
istrait(::Any) = error("Argument is not a Trait.")
79-
istrait{T<:Trait}(tr::Type{T}) = trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265
93+
function istrait{T<:Trait}(tr::Type{T})
94+
!isleaftype(tr) && error("Not all parameters of $tr are specified, thus not a valid trait.")
95+
trait(tr)==stripNot(tr) ? true : false # Problem, this can run into issue #265
96+
end
8097
# thus is redefine when traits are defined
98+
istrait{T<:Tuple}(tr::Type{T}) = mapreduce(istrait, &, true, tr.parameters)
99+
81100
"""
82101
83-
Used to define a trait. Traits, like types, are camel cased. I
84-
suggest to start them with a verb, e.g. `IsImmutable`, to distinguish
85-
them from actual types, which are usually nouns.
102+
Defines a trait. Traits need to have one or more (type-)parameters to
103+
specify the type to which the trait is applied. For instance
104+
`IsImmutable{Int}` signifies that `Int` is part of `IsImmutable`
105+
(although whether that is true needs to be checked with the `istrait`
106+
function). Most traits will be one-parameter traits, however, several
107+
parameters are useful when there is a "contract" between several
108+
types.
109+
110+
Traits can be sub-traits of one or several other traits. A trait is
111+
then fulfilled if it is fulfilled itself and all its super-traits are
112+
also fulfilled.
113+
114+
Traits, like types, are camel cased. I suggest to start them with a
115+
verb, e.g. `IsImmutable`, to distinguish them from actual types, which
116+
are usually nouns.
86117
87-
Traits need to have one or more (type-)parameters to specify the type
88-
to which the trait is applied. For instance `IsImmutable{Int}`
89-
signifies that `Int` is part of `IsImmutable` (although whether that
90-
is true needs to be checked with the `istrait` function). Most traits
91-
will be one-parameter traits, however, several parameters are useful
92-
when there is a "contract" between several types.
93118
94119
Examples:
95120
```julia
96121
@traitdef IsFast{X}
97-
@traitdef IsSlow{X,Y}
122+
@traitdef IsSuperFast{X} <: IsFast{X}
123+
@traitdef IsHyper{X}
124+
@traitdef IsHyperFast{X} <: IsSuperFast{X}, IsHyper{X}
98125
```
99126
"""
100127
macro traitdef(tr)
101-
:(immutable $(esc(tr)) <: Trait end)
128+
Tr, Pr, SUPER = @match tr begin
129+
Tr_{Pr__} <: SUPER__ => (Tr, Pr, SUPER)
130+
Tr_{Pr__} <: SUPER1_,SUPER__ => (Tr, Pr, [SUPER1, SUPER...])
131+
Tr_{Pr__} => (Tr, Pr, [])
132+
end
133+
tmp = :(Tuple{})
134+
append!(tmp.args, SUPER)
135+
T = esc(:($Tr{$(Pr...)}))
136+
:(immutable $T <: Trait{$(esc(tmp))} end)
102137
end
103138

104139
"""
@@ -112,8 +147,10 @@ Example:
112147
```
113148
"""
114149
macro traitimpl(tr)
115-
# makes
150+
# makes from @traitimpl Tr1{Int,Float64}
151+
# istrait(supertrait(Tr1{Int,Float64})) || error("...")
116152
# trait{X1<:Int,X2<:Float64}(::Type{Tr1{X1,X2}}) = Tr1{X1,X2}
153+
# istrait{X1<:Int,X2<:Float64}(::Type{Tr1{X1,X2}}) = true
117154
if tr.args[1]==:Not || isnegated(tr)
118155
tr = tr.args[2]
119156
negated = true
@@ -129,17 +166,21 @@ macro traitimpl(tr)
129166
push!(paras, esc(v))
130167
end
131168
arg = :(::Type{$trname{$(paras...)}})
132-
fnhead = :($curmod.trait{$(curly...)}($arg))
133-
isfnhead = :($curmod.istrait{$(curly...)}($arg))
169+
fnhead = :(trait{$(curly...)}($arg))
170+
isfnhead = :(istrait{$(curly...)}($arg))
171+
trr = :($trname{$(paras...)})
172+
errstr = :("Not all super-traits of $($(esc(tr))) fulfilled")
134173
if !negated
135174
return quote
136-
$fnhead = $trname{$(paras...)}
175+
!istrait(supertraits($(esc(tr)))) && error($errstr)
176+
$fnhead = $trr
137177
$isfnhead = true # Add the istrait definition as otherwise
138178
# method-caching can be an issue.
139179
end
140180
else
141181
return quote
142-
$fnhead = Not{$trname{$(paras...)}}
182+
!istrait(supertraits($(esc(tr)))) && error($errstr)
183+
$fnhead = Not{$trr}
143184
$isfnhead = false# Add the istrait definition as otherwise
144185
# method-caching can be an issue.
145186
end

src/base-traits.jl

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
module BaseTraits
22
using SimpleTraits
3+
import SimpleTraits: trait
34

45
export IsLeafType, IsBits, IsImmutable, IsContiguous, IsFastLinearIndex,
56
IsAnything, IsNothing, IsCallable
67

78
"Trait which contains all types"
89
@traitdef IsAnything{X}
9-
SimpleTraits.trait{X}(::Type{IsAnything{X}}) = IsAnything{X}
10+
trait{X}(::Type{IsAnything{X}}) = IsAnything{X}
1011

1112
"Trait which contains no types"
1213
typealias IsNothing{X} Not{IsAnything{X}}
1314

1415

1516
"Trait of all isbits-types"
1617
@traitdef IsBits{X}
17-
@generated SimpleTraits.trait{X}(::Type{IsBits{X}}) =
18+
@generated trait{X}(::Type{IsBits{X}}) =
1819
isbits(X) ? :(IsBits{X}) : :(Not{IsBits{X}})
1920

2021
"Trait of all immutable types"
2122
@traitdef IsImmutable{X}
22-
@generated SimpleTraits.trait{X}(::Type{IsImmutable{X}}) =
23+
@generated trait{X}(::Type{IsImmutable{X}}) =
2324
X.mutable ? :(Not{IsImmutable{X}}) : :(IsImmutable{X})
2425

2526
"Trait of all callable objects"
2627
@traitdef IsCallable{X}
27-
@generated SimpleTraits.trait{X}(::Type{IsCallable{X}}) =
28+
@generated trait{X}(::Type{IsCallable{X}}) =
2829
(X==Function || length(methods(call, (X,Vararg)))>0) ? IsCallable{X} : Not{IsCallable{X}}
2930

3031
"Trait of all leaf types types"
@@ -33,12 +34,12 @@ typealias IsNothing{X} Not{IsAnything{X}}
3334

3435
"Types which have contiguous memory layout"
3536
@traitdef IsContiguous{X} # https://github.com/JuliaLang/julia/issues/10889
36-
@generated SimpleTraits.trait{X}(::Type{IsContiguous{X}}) =
37+
@generated trait{X}(::Type{IsContiguous{X}}) =
3738
Base.iscontiguous(X) ? :(IsContiguous{X}) : :(Not{IsContiguous{X}})
3839

3940
"Array indexing trait."
4041
@traitdef IsFastLinearIndex{X} # https://github.com/JuliaLang/julia/pull/8432
41-
@generated function SimpleTraits.trait{X}(::Type{IsFastLinearIndex{X}})
42+
@generated function trait{X}(::Type{IsFastLinearIndex{X}})
4243
if Base.linearindexing(X)==Base.LinearFast()
4344
return :(IsFastLinearIndex{X})
4445
elseif Base.linearindexing(X)==Base.LinearSlow()

test/runtests.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,36 @@ println("")
165165
@test f12t(1)==1
166166
@test f12t(5.5)==2
167167

168+
####
169+
# Trait inheritiance
170+
###
171+
@traitdef TI1{X}
172+
@test supertraits(TI1)==Tuple{}
173+
@test supertraits(TI1{Int})==Tuple{}
174+
@traitdef TI2{X,Y}
175+
@test supertraits(TI2)==Tuple{}
176+
@traitdef TI_12{X,Y} <: TI1{X}, TI1{Y}, TI2{X,Y}
177+
@test supertraits(TI_12{Int,Float64})==Tuple{TI1{Int}, TI1{Float64}, TI2{Int,Float64} }
178+
@test !istrait(TI_12{Int,Float64})
179+
180+
@test_throws ErrorException @traitimpl TI_12{Int,Float64}
181+
182+
@traitimpl TI1{Int}
183+
@traitimpl TI1{Float64}
184+
@traitimpl TI2{Int,Float64}
185+
@traitimpl TI_12{Int,Float64}
186+
187+
@test istrait(TI_12{Int,Float64})
188+
189+
190+
######
191+
# brocken
192+
######
193+
194+
@traitimpl TI2{Dict}
195+
@test_broken !istrait(TI2{Dict})
196+
# ideally an error should be thrown if tr in trait(tr) in not leaftype.
197+
168198
######
169199
# Other tests
170200
#####

0 commit comments

Comments
 (0)