Type unstable & factory constructor

203 views Asked by At

Say, I have a type hierarchy

abstract A
immutable B <: A end
immutable C <: A end

The constructor of A follows factory pattern:

function A(x::Int)
    if x > 0
        B()
    else
        C()
    end
end

It returns different subtypes based on the input as expected. However, it is also type unstable as I cannot find a way to force the return type to be A.

So, is it bad to have factory pattern here? Does the type instability only affects immutable types rather than mutable types, since the latter is reference type anyway.

Do I have to opt to the parametric type for this?

immutable D{T <: A}
    type::T
end

function D(x::Int)
    if x > 0
        D(B())
    else
        D(C())
    end
end

It feels a bit bad.

Actually, how bad it is to have type unstable functions? Is is worthwhile to trade for better code readability?

Alternatively, should I define typealias A Union{B,C} instead?

1

There are 1 answers

0
tholy On

Well, you could do this:

function A(x::Int)
    if x > 0
        B()::A
    else
        C()::A
    end
end

but it doesn't help:

julia> @code_warntype A(5)
Variables:
  x::Int64

Body:
  begin  # none, line 2:
      unless (Base.slt_int)(0,x::Int64)::Bool goto 0 # none, line 3:
      return $(Expr(:new, :((top(getfield))(Main,:B)::Type{B})))
      goto 1
      0:  # none, line 5:
      return $(Expr(:new, :((top(getfield))(Main,:C)::Type{C})))
      1: 
  end::Union{B,C}

You can't create instances of an abstract type. Moreover, in current julia, any abstract type is automatically "type-unstable," meaning that the compiler can't generate optimized code for it. So there is no such thing as "forcing the return type to be A" and then having that somehow make the function type-stable (in the sense of obtaining great performance).

You can implement a type-stable factory pattern, but the output type should be determined by the input types, not the input values. For example:

A(x::Vector) = B()
A(x::Matrix) = C()

is a type-stable constructor for objects of the A hierarchy.

If there aren't obvious types to use to signal your intent, you can always use Val:

A(x, ::Type{Val{1}}) = B()
A(x, ::Type{Val{2}}) = C()

A(1, Val{1})   # returns B()
A(1, Val{2})   # returns C()