@@ -26,57 +26,67 @@ mentioned in (1). But Julia does not support (2) or (3) yet. (2) is
26
26
fairly easy to implement. However, dispatch on a "contract" is not
27
27
easily possible, but Tim Holy recently came up with
28
28
[ a trick] ( https://github.com/JuliaLang/julia/issues/2345#issuecomment-54537633 ) .
29
- The cool thing about that trick is that the code for a trait-dispatch
30
- function should be identical to a duck-typed function, i.e. there is
31
- no loss in performance.
29
+ The cool thing about that trick is that the generated machine- code for
30
+ a trait-dispatch function should be identical to a duck-typed
31
+ function, i.e. there is no loss in performance.
32
32
33
33
` Traits.jl ` adds those kind of traits to Julia, using Tim's trick
34
- combined with stagedfunctions. See also the Julia-issue
34
+ combined with stagedfunctions and extensive facilities to define
35
+ traits. See also the Julia-issue
35
36
[ #6975 ] ( https://github.com/JuliaLang/julia/issues/6975 ) concerning
36
37
interfaces/traits.
37
38
38
- Example:
39
+ Example ` examples/ex1.jl ` :
39
40
``` julia
40
41
using Traits
41
- # Check Cmp-trait (comparison) which is implemented in src/commontraits.jl
42
- @assert istrait (Cmp{Int,Float64})
43
- @assert istrait (Cmp{Int,String})== false
42
+ # Check Cmp-trait (comparison) which is implemented in Traits.jl/ src/commontraits.jl
43
+ @assert istrait (Cmp{Int,Float64}) # Int and Float64 can be compared
44
+ @assert istrait (Cmp{Int,String})== false # Int and String cannot be compared
44
45
45
46
# make a new trait and add a type to it:
46
47
@traitdef MyTr{X,Y} begin
47
- foobar (X,Y) -> Bool
48
+ foobar (X,Y) -> Bool # All type-tuples for which there is a method foo
49
+ # with that signature belong to MyTr
48
50
end
49
51
type A
50
52
a:: Int
51
53
end
52
- foobar (a:: A , b:: A ) = a. a== b. a
53
- @assert istrait (MyTr{A,A}) # true
54
+ @assert istrait (MyTr{A,A})== false # foobar not implement yet
55
+ foobar (a:: A , b:: A ) = a. a== b. a # implement it
56
+ @assert istrait (MyTr{A,A}) # voila!
54
57
@assert istrait (MyTr{Int,Int})== false
55
58
56
59
# make a function which dispatches on traits:
57
60
@traitfn ft1 {X,Y; Cmp{X,Y}} (x:: X ,y:: Y ) = x> y ? 5 : 6
58
61
@traitfn ft1 {X,Y; MyTr{X,Y}} (x:: X ,y:: Y ) = foobar (x,y) ? - 99 : - 999
59
62
60
- ft1 (4 ,5 ) # 6
61
- ft1 (A (5 ), A (6 )) # -999
63
+ ft1 (4 ,5 ) # ==6 i.e. dispatches to first definition
64
+ ft1 (A (5 ), A (6 )) # == -999 i.e. dispatches to second definition
62
65
63
66
ft1 (" asdf" , 6 )
64
67
# -> ERROR: TraitException("No matching trait found for function ft1")
65
68
```
66
69
67
- This is an experimental package and I will not try to keep backwards
68
- compatibility as I move on. But please give it a try in your code and
69
- give feedback. I will try to document the new features in [ NEWS] ( NEWS.md ) .
70
+ # Package status
71
+
72
+ New features are documented in [ NEWS] ( NEWS.md ) as they are added. I
73
+ keep some notes, musings and plans in [ dev_notes.md] ( docs/dev_notes.md ) .
74
+
75
+ This is a fairly experimental package and I will not try to keep
76
+ backwards compatibility as I move on. Please try it out and give me
77
+ feedback, issues or pull requests!
70
78
71
79
# Syntax
72
- (source in ` examples/ex2.jl ` )
80
+ The source of below examples is in ` examples/ex2.jl ` . Most of the
81
+ important functions are documented and will respond to ` ? ` in the REPL.
73
82
74
- Trait definition:
83
+ Trait definition (for details see [ traitdef.md ] ( docs/traitdef.md ) ) :
75
84
``` julia
76
85
using Traits
77
86
# simple
78
87
@traitdef Tr1{X} begin
79
- fun1 (X) -> Number
88
+ fun1 (X) -> Number # this means a method with signature fun1(::X)
89
+ # returning a Number
80
90
end
81
91
@traitdef Tr2{X,Y} begin
82
92
fun2 (X,Y) -> Number
@@ -101,13 +111,12 @@ end
101
111
end
102
112
```
103
113
Note that return-type checking is quite experimental. It can be
104
- turned off by defining ` Main.Traits_check_return_types=false ` before
105
- ` using Traits ` .
114
+ turned off with ` check_return_types(false) ` .
106
115
107
116
108
- Trait implementation:
117
+ Trait implementation and checking with ` istrait ` :
109
118
``` julia
110
- # manual, i.e. just define the functions
119
+ # manual definiton , i.e. just define the functions
111
120
fun1 (x:: Int ) = 5 x
112
121
@assert istrait (Tr1{Int})
113
122
@@ -159,45 +168,50 @@ catch e
159
168
end
160
169
```
161
170
162
- Trait functions & dispatch:
171
+ Trait functions & dispatch (for details see [ traitfns.md ] ( docs/traitfns.md ) ) :
163
172
``` julia
164
- @traitfn tf1 {X, Y; Tr1{X}, Tr1{Y}} (a:: X , b:: Y ) = fun1 (a) + fun1 (b)
165
- @traitfn tf1 {X, Y; Tr2{X,Y}} (a:: X , b:: Y ) = fun2 (a,b)
173
+ @traitfn tf1 {X, Y; Tr1{X}, Tr1{Y}} (a:: X , b:: Y ) = fun1 (a) + fun1 (b) # I
174
+ @traitfn tf1 {X, Y; Tr1{X}, Tr1{Y}} (a:: X , b:: Y , c:: Int ) = fun1 (a) + fun1 (b) + c # II
175
+ @traitfn tf1 {X, Y; Tr2{X,Y}} (a:: X , b:: Y ) = fun2 (a,b) # III
166
176
# Note that all the type-parameters are in the {} and that all
167
177
# arguments need a type parameter (a limitation of the
168
- # macro-parser). Bad examples are :
178
+ # macro-parser). This doesn't work :
169
179
#
170
180
# julia> @traitfn ttt1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c) = fun1(a) + fun1(b) + c
171
181
# ERROR: type Symbol has no field args
172
182
#
173
- # julia> @traitfn ttt1{X, Y; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Int) = fun1(a) + fun1(b) + c
174
- # ERROR: X3 not defined
175
- #
176
183
# But this works:
177
184
#
178
185
# julia> @traitfn ttt1{X, Y, Z; Tr1{X}, Tr1{Y}}(a::X, b::Y, c::Z) = fun1(a) + fun1(b) + c
179
186
# ttt1 (generic function with 6 methods)
180
187
181
188
182
189
# tf1 now dispatches on traits
183
- tf1 (5. ,6. ) # -> 77 (Float64 is part of Tr1 but not Tr2)
190
+ @assert tf1 (5. ,6. )== 77. # -> 77 ; dispatches to I because istrait(Tr1{Float64})
191
+ # but not istrait(Tr2{Float64,Float64})
192
+ @assert tf1 (5. ,6. ,77 )== 154. # -> 154. ; dispatches to II because of the extra argument
184
193
185
194
# Errors because of dispatch ambiguity:
186
195
try
187
- tf1 (5 ,6 ) # Int is part of Tr1{Int} and Tr2{Int, Int}
196
+ tf1 (5 ,6 ) # istrait( Tr1{Int}) and istrait( Tr2{Int,Int}) are both true!
188
197
catch e
189
198
println (e)
190
199
end
191
200
192
- # adding a type to Tr1 will make it work with tf1:
201
+ # Implementing Tr1 for a type will make it work with tf1:
193
202
type MyType
194
203
a:: Int
195
204
end
205
+ try
206
+ tf1 (MyType (8 ), 9 ) # not implemented yet
207
+ catch e
208
+ println (e)
209
+ end
196
210
@traitimpl Tr1{MyType} begin
197
211
fun1 (x:: MyType ) = x. a+ 9
198
212
end
199
213
200
- tf1 (MyType (8 ), 9 ) # -> 62
214
+ @assert tf1 (MyType (8 ), 9 )== 62 # -> 62 ; dispatches to I
201
215
```
202
216
203
217
# Generated code
221
235
```
222
236
223
237
However, for more complicated functions code is not quite the same,
224
- see ` test/traitdispatch .jl ` .
238
+ see ` test/perf/perf .jl ` .
225
239
226
240
# Inner workings
227
241
@@ -248,12 +262,12 @@ In Julia dispatch works on types, to extend this to traits I use
248
262
His trick uses a function to check whether its input types satisfy
249
263
certain conditions (only dependent on their type) and returns one type
250
264
or another depending on the outcome. That check-function is then used
251
- for dispatch in another function. Example of Tim's trick:
265
+ for dispatch in another function. Example of Tim's trick ( ` examples/ex_tims_traits.jl ` ) :
252
266
``` julia
253
267
type Trait1 end
254
268
type Trait2 end
255
269
type Trait3 end
256
- # now define function
270
+ # now define function f which should dispatch on those traits
257
271
f (x,y) = _f (x,y, checkfn (x,y))
258
272
_f (x,y,:: Type{Trait1} ) = x+ y
259
273
_f (x,y,:: Type{Trait2} ) = x- y
@@ -265,12 +279,12 @@ checkfn(::Int, ::Int) = Trait1
265
279
checkfn (:: Int , :: FloatingPoint ) = Trait2
266
280
checkfn (:: FloatingPoint , :: FloatingPoint ) = Trait3
267
281
# use
268
- f (3 ,4 ) # 7
269
- f (3 ,4. ) # -1.0
270
- f (3. ,4. ) # 12.0
282
+ @assert f (3 ,4 )== 7 # Trait1
283
+ @assert f (3 ,4. )== - 1.0 # Trait2
284
+ @assert f (3. ,4. )== 12.0 # Trait3
271
285
# add another type-tuple to Trait3
272
286
checkfn (:: String , :: String ) = Trait3
273
- f (" Lorem " , " Ipsum" ) # "Lorem Ipsum"
287
+ @assert f (" Lorem " , " Ipsum" )== " Lorem Ipsum"
274
288
```
275
289
276
290
What does this add compared to what we had before with usual dispatch?
@@ -334,30 +348,6 @@ trait-hierarchies into account. Although, note that it is easily
334
348
possible to have unsolvable ambiguities with trait-dispatch as traits
335
349
do not have a strict hierarchy like types.
336
350
337
- # To ponder
338
-
339
- - For many "traits" in Julia, only a few functions need to be
340
- implemented to provide many more. For example for comparison only
341
- ` isless ` and ` == ` need to be implemented to automatically get ` > ` ,
342
- ` < ` , ` >= ` , ` <= ` . It would be nice to somehow specify or query those
343
- automatic functions.
344
-
345
- - Are there better ways for trait-dispatch?
346
-
347
- - Sometimes it would be good to get at type parameters, for instance
348
- for Arrays and the like:
349
- ``` julia
350
- @traitdef Indexable{X{Y}} begin
351
- getindex (X, Any) -> Y
352
- setindex! (X, Y, Any) -> X
353
- end
354
- ```
355
- This problem is similar to triangular dispatch and may be solved
356
- by: https: // github. com/ JuliaLang/ julia/ issues/ 6984 # issuecomment-49751358
357
-
358
- # Issues
359
-
360
-
361
351
# Other trait implementations
362
352
363
353
See the Julia-issue
0 commit comments