Open StefanKarpinski opened 13 years ago
Concerning the syntax for trait functions: there was a long discussion over on https://github.com/JuliaLang/julia/issues/11310#issuecomment-170421099 about function syntax (also linking into the the type system overhaul: https://github.com/JuliaLang/julia/issues/8974). This should probably be considered together with trait-function syntax.
And whilst multiparameter traits are not on the table, maybe thoughts on syntax should still keep them in mind. Julia has a tendency to get to the most generic implementation eventually (a recent case in point would be not-one-based indexing).
@mauro3 Thanks again fro the reading material. I've tried to understand - but could you please explain what multiparameter traits are? (are they traits of combinations of types?)
Furthermore (and I've tried to understand this one before!) what exactly is UnionAll
planned to be/mean?
Yes, multiparameter traits involve more types. Say, there could be a
trait which contains all Tuples{A,B}
for which A+B
is possible.
If I recall correctly, UnionAll is the following: in Julia parameterized
function signatures are more expressive than how types can be expressed.
The set of allowed argument tuples of say f{X}(x::X,y::X) = ...
cannot
be currently expressed as type. UnionAll would allow to express the
type {X}(::X,::X)
.
It would be good to also be able to express the types constrained by
traits, combining above with your syntax this may be
{X}(::X::T1,::X::T2)
. In Traits.jl, the multiparameter traits use
syntax like {X,Y; Tr{X,Y}}(::X,::Y)
as straight appending to the ::X
does not work anymore.
OK cool. I think we could probably do that trait for Tuple
s already.
abstract CanAdd
immutable Addable<: CanAdd; end
immutable NotAddable <: CanAdd; end
CanAdd{T1,T2}(::Tuple{T1,T2}) = method_exists(+, Tuple{T1,T2}) ? Addable : NotAddable
The question is how to apply this to a function definition, etc. I see all of these syntax and type issues are interrelated now.
EDIT: Fixed typo in code
x::Type::Trait{ trait1, trait2 } Because there ciuld be something else that would follow the second ::, and looks like Union{...}
Indeed, we've decided to use Tuple{}
instead of Trait{}
since it is already a part of the language and that's how collection of types of arbitrarily length necessarily must be stored internally at the moment. It might not seem so important if/when {...}
get's lowered to Tuple{...}
.
So the package allows x::Type::Tuple{ trait1, trait2 }
where trait1
might be a single trait type, or a Union{...}
of them (from the same class). When there is no Tuple
the, the macro inserts it for you (for user-friendliness in the case you only have one trait), and the lack of trait annotation corresponds to Tuple{}
(c.f. Trait{}
).
To be more general, I suppose you could put a further union on the outside to combine multiple dispatch patterns into one signature, but I guess getting diagonal/multi-parameter dispatch etc. working would be just as (or more) important.
@andyferris, yes the trait declaration works but there is no syntax to use it in a trait-function. Consider f(x,y,z)= (G(x,y),H(y,z))
. Traitifying, we'd need (x,y)
part of CanG
trait and (y,z)
part of CanH
trait.
I'd love to see this in Julia. And I think it's necessary if we want to use category theory in Julia. Hlearn / Subhask provide a motivating example of how powerful and flexible multiple inheritance can be for designing machine learning frameworks.
In Hlearn, we can define a new type that is an instance of Metric and contains instances of Equivalence, and suddenly we can do nearest neighbor queries using cover trees among many other algorithms:
data Bp = A | T | C | G | N
type instance Logic Bp = Bool
instance Eq_ Bp where
A==A = True
T==T = True
C==C = True
G==G = True
N==_ = True
_==_ = False
data DNA = DNA [BP]
instance Metric DNA where
distance = levenshtein
Behind the scenes, this is all enabled by multiple inheritance. SubHask takes this to an extreme, allowing for custom non-boolean logics. But the beauty of the type system, is that as algorithm users we can use complicated topologies without needing to touch the underlying algorithm. For additional motivation, see Physics, Topology, Logic and Computation: A Rosetta Stone.
Edit: I realize this also would benefit from formal type interfaces. I don't know Julia well enough to say if anything else is block this, but my impression is that multiple inheritance is the big one
Was closing this on purpose?
Lol, there was a commit in TerminalMenus.jl that fixed "#5", and it closed this issue......
Yikes.
Can this go into 2.0?
Possibly. It’s not decided or designed yet.
So hypothetically how will/could implementing this affect all the compiler and subtyping optimizations/ bug fixes? Will there be a non trivial amount of work that will have to be duplicated? If so, perhaps it would be better to do this sooner rather than later.
Have at it :grin:
Fair enough. Past work is sunk, but I'm just wondering to what extent future compiler work would be rendered moot.
It is unclear to me why you would presuppose meaningful future compiler work would at odds with bringing abstract multiple inheritance to Julia. The use of abstract single inheritance will persist gregariously. The type system is very coherent and self-expressed in the implementation of Julia. That abstract multiple inheritance provides room for the application of augmented/additional compilation techniques is not future compiler work negating in any serious way afaik.
seems everybody agrees traits is the way to go, why not just close this issue?
I think that there is still important functionality that is not possible with traits as they currently exist in Julia but would be possible if we had abstract multiple inheritance.
One example is the ability to dispatch on arrays in which all of the objects have the FooTrait
. With abstract multiple inheritance, this would look something like:
abstract type AbstractA end
abstract type AbstractB end
abstract type AbstractC end
abstract type AbstractD end
abstract type FooTrait end
abstract type BarTrait end
struct A <: AbstractA, FooTrait end
struct B <: AbstractB, FooTrait end
struct C <: AbstractC, BarTrait end
struct D <: AbstractD, BarTrait end
f(::FooTrait) = "foo"
f(::BarTrait) = "bar"
f(::AbstractArray{<:FooTrait}) = "foo array"
f(::AbstractArray{<:BarTrait}) = "bar array"
f(A()) # this would return "foo"
f(B()) # this would return "foo"
f(C()) # this would return "bar"
f(D()) # this would return "bar"
f([A(), A()]) # this would return "foo array"
f([B(), B()]) # this would return "foo array"
f([C(), C()]) # this would return "bar array"
f([D(), D()]) # this would return "bar array"
f([A(), B()]) # this would return "foo array"
f([C(), D()]) # this would return "bar array"
As discussed in https://github.com/andyferris/Traitor.jl/issues/9 and https://github.com/mauro3/SimpleTraits.jl/issues/63, this is not possible with traits as they currently exist in Julia.
julia> abstract A abstract B
type C <: A, B
end
ERROR: syntax: extra token "A" after end of expression Stacktrace: [1] top-level scope @ none:1
Is this still an issue? Is it worth closing?
Nothing seems to have changed here.
Hi!
I finally hit a case in which this feature would be very nice.
I have a DCM
(Direction Cosine Matrix) which is <: StaticMatrix
. However, it defines a rotation inside the package ReferencceFrameRotations. Hence, it would be very good if I can have:
struct DCM <: StaticMatrix{3, 3, T}, ReferenceFrameRotation
Hi, any chance to revisit this in the near future? I think it's worth if only for enabling the creation of an Iterator abstract type. thanks
It is hard to see with all of the hidden comments here, but the current thinking is that https://github.com/mauro3/SimpleTraits.jl often is better, including for BaseTraits.IsIterable
there already
It is hard to see with all of the hidden comments here, but the current thinking is that https://github.com/mauro3/SimpleTraits.jl often is better, including for
BaseTraits.IsIterable
there alreadytrait functions which dispatch on more than one trait. This allows to remove the need for generated functions, as well as removing the rules for trait-dispatch.
Is a pretty significant limitation which something in Base would hopefully avoid. Obviously this can be handled by cascading the dispatches, but that's not fantastic UX.
See also Dilum's comment a few comments back
Unfortunately SimpleTraits.jl is not a full replacement as you can't (can, but shouldn't) use it on functions defined in Base
: https://github.com/mauro3/SimpleTraits.jl/issues/87
If traits are out of the picture until Julia 2.0, maybe a simpler and non-breaking alternative would be to introduce a special AbstractUnion
type that would trigger multiple dispatch to consider each abstract type, one by one:
struct Quantity{T,N} <: AbstractUnion{AbstractQuantity{T},AbstractArray{T,N}}
...
end
which would allow instances of Quantity
to be passed to methods defined on ::AbstractQuantity
or ::AbstractArray
, with the priority given by the order.
The C4 linearization algorithm I recently added to Gerbil Scheme combines multiple inheritance of classes/traits and single inheritance of structs/classes, so you can have the best of both worlds:
C4 extends the well-known C3 algorithm (for multiple inheritance of classes/traits) with an additional constraint to support structs/classes. https://github.com/mighty-gerbils/gerbil/blob/master/src/gerbil/runtime/c3.ss
NB: Common Lisp, and the earlier tradition of multiple inheritance, calls "classes" the things with multiple inheritance and "structs" the things with single inheritance only. Smalltalk and after it Java, and the earlier and more prevalent tradition of single inheritance, calls "classes" the things with single inheritance and "traits" the things with multiple inheritance. Wonderful nomenclature, right?
You're welcome!
@fare well that's all kinds of groovy
In an email discussion we came to the conclusion that it made sense to have multiple inheritance in Julia with one fairly simple restriction:
This restriction, together with Julia not allowing inheritance from non-abstract types, seems to address all the practical issues one typically encounters with multiple inheritance. The following, for example, would be disallowed:
Note that a generic function is an object external to all types, not a name inside of a type as it would be in a traditional object-orientation language. Thus, one can have
f(a::A)
in one namespace andf(b::B)
in another namespace without problems, so long as thef
s in these two namespaces are distinct generic function objects.