mauro3 / Traits.jl

Exploration of traits in Julia
Other
39 stars 6 forks source link

Dispatch on types instead of values #21

Open eschnett opened 9 years ago

eschnett commented 9 years ago

I want to define eltype via @traitfn. eltype takes a type as argument, not a value. Could you give some advice?

For clarity, I start by defining an AbstractVS trait, an abstract vector space over a scalar field:

@traitdef AbstractVS{V} begin
    veltype(Type{V}) -> Type{S}
    vnewtype(Type{V}, Type{R}) -> Type{W}
    vdim(V) -> Int

    vnull(Type{V}) -> V
    vscale(S, V) -> V
    vadd(V, V) -> V

    @constraints begin
        S = veltype{V}
        istrait(AbstractScalar{S})
    end
end

veltype should return the type of the scalar field of the vector space, and takes a type as argument. For example, veltype(Array{Int}) = Int. This seems to work fine.

I then want to implement eltype as trait function. The naive version looks like this:

@traitfn eltype{V; AbstractVS{V}}(::Type{V}) = veltype(V)

This leads to an error since Traits.jl wants to convert Type{V} to a symbol. I tried a few other approaches, including this:

_gettype{T}(::Type{Type{T}}) = T
@traitfn eltype{TypeV; AbstractVS{_gettype(TypeV)}}(V::TypeV) = veltype(V)

but this also leads to an error (somewhere in translate_heads make_trans).

So now I'm trying to define this trait function manually. I'm not worried about a nice syntax at the moment -- this can be added later. Could you give me a hint?

mauro3 commented 9 years ago

This should work:

@traitfn eltype{V; AbstractVS{V}}(dummy::Type{V}) = veltype(V)

It is an error in the parsing which should be fixed...

Also, I think your first constraint doesn't work. Constraints need to evaluate to a Bool. I think what you want is an associated type:

@traitdef AbstractVS{V} begin
    S = veltype{V}
    veltype(Type{V}) -> Type{S}
    vnewtype(Type{V}, Type{R}) -> Type{W}
    vdim(V) -> Int

    vnull(Type{V}) -> V
    vscale(S, V) -> V
    vadd(V, V) -> V

    @constraints begin
        istrait(AbstractScalar{S})
    end
end

The Rust documentation is quite good: https://doc.rust-lang.org/book/associated-types.html

Thanks for the interest in Traits!

eschnett commented 9 years ago

Thanks for the pointer to associated types; I know them as "fundeps" in Haskell. I was confused by the syntax (tried to put the S = ... into the @constraints sections) which didn't work.

Apart from eltype, everything is working fine for me now.

mauro3 commented 9 years ago

Yes, functional dependencies and type families. I looked at those a little but couldn't say that I understand them.

Good to hear that some works. About eltype, did my suggestion above using dummy not work?

eschnett commented 9 years ago

I didn't realize that adding dummy should help. It does indeed, in the sense that this makes the parse error go away. However, this version of eltype is then not called.

eltype is already defined in Base, and I import this definition and am trying to extend it. Based on what I see from macroexpand(@traitfn ...) and methods(eltype), I don't think this is going to work.

mauro3 commented 9 years ago

I think it should work. This is what I get (using a drop-in trait Tr):

julia> using Traits
  This warning is ok:
WARNING: Method definition func_for_method(Method, Any, Any) in module Inference at inference.jl:606 overwritten in module Traits at /home/mauro/.julia/v0.4/Traits/src/base_fixes.jl:8.
WARNING: Method definition eltype(Type{Base.Associative{#K<:Any, #V<:Any}}) in module Base at dict.jl:240 overwritten in module Traits at /home/mauro/.julia/v0.4/Traits/src/base_fixes.jl:20.
  endof ok-warnings.

julia> @traitdef Tr{X} begin
       end

julia> import Base: eltype

julia> @traitfn eltype{V; Tr{V}}(dummy::Type{V}) = veltype(V)
eltype (generic function with 50 methods)

so the Base eltype function is extended. But I guess you're referring to:

julia> eltype(Int)
Int64

julia> @which eltype(Int)
eltype{T<:Number}(::Type{T<:Number}) at number.jl:9

even though istrait(Tr{Int}) is true. This is expected: first normal type dispatch is done. If type dispatch selects a traitfn-method only then does trait-dispatch come into play. I can see how this is a problem here and potentially in other cases too. I'm not sure though how/whether this can be solved, I'll need to ponder it for a bit.

eschnett commented 9 years ago

When I extended eltype and looked at methods(eltype), I think I saw at least generic versions that should match everything. (Why -- shouldn't this have been caught as an error by Julia, or shouldn't the second definition have overridden the first?) In particular, Base defines

eltype(::Type{Any})

as a fallback.

The Traits-generated version looked like

eltype{S}(dummy::Type{S})

which then dispatched on the type of S. This looked to me as if @traitfn assumed that it provided the only definition of a function, and didn't interact well with previous definitions.

mauro3 commented 9 years ago

Looking into this a bit further (without using any Traits):

julia> @which eltype(Vector{Int})
eltype(t::DataType) at operators.jl:184

julia> methods(eltype, (Type{Vector{Int}},))
1-element Array{Any,1}:
 eltype(t::DataType) at operators.jl:184

julia> f(t::DataType) = 1
f (generic function with 1 method)

julia> f{X}(::Type{X}) = 2
f (generic function with 2 methods)

julia> f(Int)
1

julia> methods(f, (Type{Int},))
1-element Array{Any,1}:
 f(t::DataType) at none:1

julia> g{X}(::Type{X}) = 2
g (generic function with 1 method)

julia> g(Int)
2

julia> methods(g, (Type{Int},))
1-element Array{Any,1}:
 g{X}(::Type{X}) at none:1

So it looks like the parameterized method is completely shadowed by the DataType method. Maybe or maybe not a bug in Julia? Generally dispatch using Type and DataType has not been quite ironed out: https://github.com/JuliaLang/julia/issues?utf8=%E2%9C%93&q=DataType+is%3Aissue+author%3Amauro3

Hmm, this doesn't help, does it?