|
| 1 | +Development notes |
| 2 | +================= |
| 3 | + |
| 4 | +Road blocks |
| 5 | +----------- |
| 6 | + |
| 7 | +### Expressiveness of types and how to describe method signatures |
| 8 | + |
| 9 | +see branch m3/para-methods |
| 10 | + |
| 11 | +The definition of traits hinges on being able to specify the types of |
| 12 | +function arguments. This is currently not possible because of |
| 13 | +parameterized functions: |
| 14 | +```julia |
| 15 | +f{T}(a::Int, b::T, c::T) = ... |
| 16 | +``` |
| 17 | + |
| 18 | +The signature of the function cannot be captured in a type-tuple, |
| 19 | +instead it needs something like a constrained tuple: |
| 20 | +```julia |
| 21 | +{T}(a::Int, b::T, c::T) |
| 22 | +``` |
| 23 | + |
| 24 | +Specifying a trait involves specifying method signatures |
| 25 | +```julia |
| 26 | +@traitdef Tr1{X} begin |
| 27 | + f{T}(a::X, b::T, c::T) = ... |
| 28 | +end |
| 29 | +``` |
| 30 | + |
| 31 | +which then need to be compared to the signatures of the method of `f` |
| 32 | +for a specific `X`: for example, `istrait(Tr1{Int})` |
| 33 | +would need to check whether at least one method signature `sig` of `f` |
| 34 | +satisfies `tsig<:sig` where `tsig={T}(a::Int, b::T, c::T)` (i.e. for |
| 35 | +all allowed types in the trait there is a method which fits). Syntax |
| 36 | +for this is being discussed in issue |
| 37 | +[#6984](https://github.com/JuliaLang/julia/issues/6984#issuecomment-49804492). |
| 38 | + |
| 39 | +Actually, I'm not sure whether `tsig<:sig` is right. For above `Tr1`, |
| 40 | +and a method with `sig=(a::Int, b, c)`, then the set of allowed type |
| 41 | +signatures by `Tr1` is a subset of `sig`, thus `tsig={T}(a::Int, b::T, |
| 42 | +c::T)<:(a::Int, b, c)=sig`. However, I'm not sure that is what should |
| 43 | +be intended by the traitdef of `Tr1`. Conversely, for non-parametric |
| 44 | +types, say `tsig=(a::Integer)` & `sig=(a::Real)` means `tsig<:sig`, |
| 45 | +which should mean that a trait would be satisfied. |
| 46 | + |
| 47 | +So, I think, for a trait-signature to be satisfied the following |
| 48 | +condition need to hold: |
| 49 | + |
| 50 | +- `tsig<:sig` for just the types themselves (sans parametric constraints) |
| 51 | +- `tsig==sig` for the parametric constraints. I.e. the constraints on `sig` |
| 52 | + need to be identical to `tsig`. |
| 53 | + |
| 54 | +The reason for the second rule is: If the constraints are weaker on |
| 55 | +`tsig` then on `sig`, it can happen that a argument tuple is not |
| 56 | +accepted by the method with `sig` even though it would be on a method |
| 57 | +with `tsig`. Conversely, if the constrains are weaker on |
| 58 | +`sig` then on `tsig`, then not all the trait-constraints are |
| 59 | +fulfilled and thus the trait is not fulfilled. |
| 60 | + |
| 61 | +Let's call this `tsig<<:sig`. |
| 62 | + |
| 63 | +Compare this to ordinary method dispatch: |
| 64 | +``` |
| 65 | +julia> g(a,b) = 1 |
| 66 | +g (generic function with 1 method) |
| 67 | +
|
| 68 | +julia> g{T}(a::T,b::T) = 2 |
| 69 | +g (generic function with 2 methods) |
| 70 | +
|
| 71 | +julia> g(5,6) # here the parametric constrained one gets called, as it is more specific |
| 72 | +2 |
| 73 | +
|
| 74 | +julia> g(5,6.) |
| 75 | +1 |
| 76 | +``` |
| 77 | + |
| 78 | +This means `{I}(I, I)<:(Any, Any)` but not `(Any, Any)<:{I}(I, I)`, |
| 79 | +which does not help for the traits-problem... Having a trait: |
| 80 | + |
| 81 | +```julia |
| 82 | +@traidef Tr{X} begin |
| 83 | + g{T<:X}(T, T) |
| 84 | +end |
| 85 | +``` |
| 86 | + |
| 87 | +I think should mean that defining `g{T}(a::T,b::T) = 2` fulfils `Tr` for all |
| 88 | +types but defining `g(a,b) = 1` does not. |
| 89 | + |
| 90 | +How about |
| 91 | +```Julia |
| 92 | +@traidef Tr{X} begin |
| 93 | + g{T<:X}(T, T, Integer) |
| 94 | +end |
| 95 | +``` |
| 96 | +would `g{T<:Integer}(T, T, T)` fulfil `Tr{Integer}`? Probably yes as |
| 97 | +the parametric constraints are stronger than needed. |
| 98 | + |
| 99 | +And what about the special-casing of dispatch of type parameters in |
| 100 | +invariant positions? |
| 101 | +``` |
| 102 | +f{T}(y::T, x::A{T}) = 1 # does match f(1, A{Number}()) |
| 103 | +``` |
| 104 | +I think there is nothing special about it for traits? |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +#### Ideas |
| 109 | +Instead of using `method_exists` compare a fake method with the |
| 110 | +methods at hand. Also use a Type-variant method to encode the return |
| 111 | +type: |
| 112 | + |
| 113 | +Methods-cache may do something similar. |
| 114 | +Base._methods (video at 28min) |
| 115 | + |
| 116 | +#### References: |
| 117 | +- https://github.com/JuliaLang/julia/issues/9043 |
| 118 | +-[Add syntactic sugar for covariant actual type parameters #6984](https://github.com/JuliaLang/julia/issues/6984) |
| 119 | +- [use { } for tuple types? #8470](https://github.com/JuliaLang/julia/issues/8470) |
| 120 | +- [WIP: redesign of tuples and tuple types #10380](https://github.com/JuliaLang/julia/pull/10380) |
| 121 | +- Jeff's talk: "Introduction to Julia Internals" |
| 122 | + |
| 123 | + |
| 124 | +### Return types |
| 125 | + |
| 126 | +[Variance of function types](https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Function_types) |
| 127 | +suggests that "it is safe to substitute a function `f` instead of a |
| 128 | +function `g` if `f` accepts a more general type of arguments and |
| 129 | +returns a more specific type than `g`." I.e. the `->` type constructor |
| 130 | +is contravariant in the input type and covariant in the output type. |
| 131 | + |
| 132 | +However, when using muliple dispatch, |
| 133 | +[wikipedia says](https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29#Avoiding_the_need_for_covariant_argument_types): |
| 134 | +"... types used for runtime method selection are covariant while types |
| 135 | +not used for runtime method selection of the method are |
| 136 | +contravariant." So, bottom line is that Julia's generic functions are |
| 137 | +*covariant* in both *argument types* and *return types*: |
| 138 | +``` |
| 139 | +(X1,Y1)->Z1 <: (X2,Y2)->Z2 |
| 140 | +=> |
| 141 | +(X1,Y1,Z1) <: (X2,Y2,Z2) |
| 142 | +``` |
| 143 | + |
| 144 | +This means, that return types can be analysed in the same way as |
| 145 | +argument types, at least in principle. However, the devil may be in |
| 146 | +the details as the return type of function can only be queried with |
| 147 | +`Base.return_types`. Example: |
| 148 | +```julia |
| 149 | +@traitdef Tr{X} begin |
| 150 | + g{T<:X}(::T, ::T) = T |
| 151 | +end |
| 152 | +g(::Int, ::Int) = Int |
| 153 | +istrait(Tr{Int}) # == true |
| 154 | +``` |
| 155 | + |
| 156 | + |
| 157 | +#### References: |
| 158 | + |
| 159 | +- [adding a `@typeof f(..)`](https://github.com/JuliaLang/julia/issues/8027#issuecomment-52519612) |
| 160 | +- [Wikipedia about variance](https://en.wikipedia.org/wiki/Covariance_and_contravariance_%28computer_science%29) |
| 161 | + |
| 162 | +Monotonic return types: |
| 163 | + |
| 164 | +- [julia-dev thread](https://groups.google.com/forum/#!msg/julia-dev/OGTUtAeozVw/cRQyuJQSFFgJ) |
| 165 | +- [return type declarations #1090](https://github.com/JuliaLang/julia/issues/1090#issuecomment-35642896) |
| 166 | + |
| 167 | +Ideas |
| 168 | +----- |
| 169 | +### Merging implicit interfaces specified on types with traits |
| 170 | + |
| 171 | +see branch m3/type-traits |
| 172 | + |
| 173 | +How can traits be used for what types are used? Say what is the |
| 174 | +interface for an `AbstractArray`? Say it is made up of a bunch of |
| 175 | +single parameter traits, then `AbstractArray => BunchOfTraits{AbstractArray}`. |
| 176 | +However, the converse is not necessarily true, namely when |
| 177 | +`BunchOfTraits{X}` does not imply `X==AbstractArray`. |
| 178 | + |
| 179 | +References: |
| 180 | + |
| 181 | +- [Tim](https://github.com/JuliaLang/julia/pull/10458#issuecomment-77957672) |
| 182 | + about ambiguities and having general methods. |
| 183 | +- [PR 10312](https://github.com/JuliaLang/julia/pull/10312) case about duck-typing |
| 184 | + maps and such as now many things are callable |
| 185 | +- [Issue #5](https://github.com/JuliaLang/julia/issues/5#issuecomment-37901282) |
| 186 | + old discussion on multiple inheritance |
| 187 | +- there is one discussion about the interface for AbstractArray |
| 188 | + [#10064](https://github.com/JuliaLang/julia/issues/10064) and related |
| 189 | + - [#987](https://github.com/JuliaLang/julia/issues/987) |
| 190 | + - [#9586](https://github.com/JuliaLang/julia/issues/9586) |
| 191 | + |
| 192 | + |
| 193 | +- instead of creating a new trait, define one for an abstact type |
| 194 | +- check for a concrete subtype will consist of checking that the trait |
| 195 | + is fulfilled. |
| 196 | +- traitfns can use these type-traits `f{X; X<:AbstractArray}` instead |
| 197 | + of `f{X<:AbstractArray}` to check that all of the interfaces of its |
| 198 | + abstract super-types are fullfilled. |
| 199 | + |
| 200 | +How is the istrait checking done? Dispatch on type, if a DataType is |
| 201 | +found look up the corresponding trait. |
| 202 | + |
| 203 | +How are the traits stored? |
| 204 | +- make a normal trait, say AbstractArrayTrait. Insert them into |
| 205 | + `Traits` module, that way they will be available irrespective of |
| 206 | + where the original type was defined. |
| 207 | + |
| 208 | + |
| 209 | +Todo: |
| 210 | +- [ ] implement issue [#8](https://github.com/mauro3/Traits.jl/issues/8) |
0 commit comments