Upon reflection, I realized that your second one wasn't working because it
is badly posed. Your definition implicitly assumes a lot about the
structure and fields of any subtype that may not be true, or may have
multiple answers.

For example, with the following abstract definition:

abstract MyAbstract{T}

I can define all of the following:

type MyType1{T} <: MyAbstract{T} end
type MyType2 <: MyAbstract{Int} end
type MyType3{T} <: MyAbstract{Float32} end
type MyType4{T,S} <: MyAbstract{S} end

In short, the type parameter of a subtype has little or nothing to do with
the type parameter (or eltype) of the abstract type. Therefore, it is not a
meaningful statement to write a generic promote_type function in the way
that you were proposing.

Your original eltype function would have returned the incorrect eltype for
all of these, but the eltype based upon super, is able to use dispatch to
return the correct eltype, regardless of how the subtypes are defined.


On Fri, Aug 22, 2014 at 3:45 PM, Tim Holy <tim.h...@gmail.com> wrote:

> Got it now, that's a good strategy.
>
> Of course, it's the second one I'm more concerned about.
>
> --Tim
>
> On Friday, August 22, 2014 03:18:19 PM Jameson Nash wrote:
> > For the first, you are missing the base case for the recursion, so it
> > defaults to eltype(::Any) instead of eltype(::Type{MyAbstract{T}})
> >
> > On Friday, August 22, 2014, Tim Holy <tim.h...@gmail.com> wrote:
> > > On Friday, August 22, 2014 01:17:53 PM Jameson Nash wrote:
> > > > Instead of eltype as written, use the following format:
> > > > eltype{T<:...}(::Type{T}) = eltype(super(T))
> > >
> > > With this definition:
> > > Base.eltype{T<:MyAbstractType}(::Type{T}) = eltype(super(T))
> > >
> > > eltype(MyType{Float32}) yields "Any".
> > >
> > >
> > > And for the second:
> > > julia> f{M<:MyAbstractType}(::Type{M}) = (M.name){Float32}
> > > f (generic function with 1 method)
> > >
> > > julia> f(MyType{Float64})
> > > ERROR: type: instantiate_type: expected TypeConstructor, got TypeName
> > >
> > >  in f at none:1
> > >
> > > It was a good suggestion, it's just that I had already tried it.
> > >
> > > --Tim
> > >
> > > > This should help with the type inference issue too.
> > > >
> > > > I think M.name is already the type, so you don't need to use eval
> (which
> > > > doesn't return the right thing anyways, in the general case)
> > > >
> > > > Sorry I can't type more clearly on a phone, but I'm hoping this will
> be
> > > > enough to get you going in the right direction.
> > > >
> > > > On Friday, August 22, 2014, Tim Holy <tim.h...@gmail.com
> <javascript:;>>
> > >
> > > wrote:
> > > > > Hi all,
> > > > >
> > > > > I've come up against a couple of surprising type-manipulation
> issues
> > >
> > > and
> > >
> > > > > I'm
> > > > > wondering if I'm just missing something. Hoping someone out there
> > >
> > > knows a
> > >
> > > > > better way of doing these manipulations.
> > > > >
> > > > > The issues arise when I try writing generic algorithms on abstract
> > > > > parametric
> > > > > types that need to make decisions based on both the concrete
> > > > > parameters
> > > > > and
> > > > > the concrete type. Let's start with a simple example:
> > > > >
> > > > > abstract MyAbstractType{T}
> > > > >
> > > > > immutable MyType{T} <: MyAbstractType{T}
> > > > >
> > > > >     a::T
> > > > >     b::T
> > > > >
> > > > > end
> > > > >
> > > > > Now let's write a generic "eltype" method:
> > > > >
> > > > > Base.eltype{M<:MyAbstractType}(::Type{M}) = M.parameters[1]
> > > > >
> > > > > It's a little ugly to have to use M.parameters[1] here. While I'm
> > >
> > > curious
> > >
> > > > > to
> > > > > know whether I'm missing a simple alternative, I can live with
> this.
> > > > >
> > > > > Now let's define some arithmetic operations:
> > > > >
> > > > > (*)(x::Number, m::MyType) = MyType(x*m.a, x*m.b)
> > > > > (.*)(x::Number, m::MyType) = MyType(x*m.a, x*m.b)
> > > > >
> > > > > And then try it out:
> > > > > julia> y = MyType(3,12)
> > > > >
> > > > > julia> 2y
> > > > > MyType{Int64}(6,24)
> > > > >
> > > > > julia> 2.1y
> > > > > MyType{Float64}(6.300000000000001,25.200000000000003)
> > > > >
> > > > >
> > > > > So far, so good. Now let's try something a little more
> sophisticated:
> > > > >
> > > > > julia> A = [MyType(3,12), MyType(4,7)]
> > > > >
> > > > > 2-element Array{MyType{Int64},1}:
> > > > >  MyType{Int64}(3,12)
> > > > >  MyType{Int64}(4,7)
> > > > >
> > > > > julia> 2A
> > > > >
> > > > > 2-element Array{Any,1}:
> > > > >  MyType{Int64}(6,24)
> > > > >  MyType{Int64}(8,14)
> > > > >
> > > > > The problem here is we're getting an Array{Any,1} back. No problem
> you
> > > > > say,
> > > > > let's try to help type inference out, by specifying that a
> > > > >
> > > > > ::MyType{Int}*::Float64 is a MyType{Float64}. If we were willing to
> > >
> > > write
> > >
> > > > > this
> > > > > using concrete types, it would be easy:
> > > > >
> > > > > Base.promote_array_type{T<:Real, S}(::Type{T}, ::Type{MyType{S}}) =
> > > > > MyType{promote_type(T, S)}
> > > > >
> > > > > But if we want to just use the abstract type, then I've been
> > >
> > > unsuccessful
> > >
> > > > > in
> > > > > avoiding this construction:
> > > > >
> > > > > Base.promote_array_type{T<:Real, M<:MyAbstractType}(::Type{T},
> > > ::
> > > ::Type{M})
> > > ::
> > > > > =
> > > > > eval(M.name.name){promote_type(eltype(M), T)}
> > > > >
> > > > > Ouch! Do we really have to use eval there? This seems likely to be
> a
> > > > > runtime
> > > > > bottleneck, which seems confirmed by
> > > > >
> > > > > code_llvm(Base.promote_array_type, (Type{Float64},
> Type{eltype(A)}))
> > > > >
> > > > > Compare it against the version generated when I instead use the
> > >
> > > concrete
> > >
> > > > > type,
> > > > > and you'll see what I mean.
> > > > >
> > > > > Of course these issues would be easily solved by triangular
> dispatch
> > > > > or
> > > > > staged
> > > > > functions, but I'm hoping there's another good way that exists
> today.
> > > > >
> > > > > --Tim
>
>

Reply via email to