It's kind of tricky talking about "the compiler", since there's a lot that 
goes on to get from user-facing Julia code to native machine code. That 
being said, here's how I'd explain the issue to myself, under the very 
rough and ready approximation that "the compiler" is the engine that 
produces low-level code that the computer can understand (more or less) 
directly from the high-level Julia code that we write. Hopefully somebody 
else will chime in to help correct any errors/fill in the gaps. 

Let's say you write a script with some functions and load that into a Julia 
session. Now you call one of those functions on a particular object, call 
it `x`, of type `T`. At this point, Julia makes what you could call a 
preliminary pass through that function's code seeing what happens to the 
*type* of an object of type `T` as that object is passed through the 
function and through any other functions subsequently called on `x`. This 
is Julia's type inference system at work, gathering information for the 
compiler to use when it produces the machine code that will handle the 
actual *value* of `x`.  The more information that is available to the 
compiler at this point in time, the better, since the compiler can use that 
information to justify performing any number of fancy optimizations that 
result in faster machine code. After the compiler has gathered all the 
information it can, it passes that information off to its various parts 
which produce the machine code that handles the actual value of `x`.

So, why is `MyType` preferable to `MyStillAmbiguousType`? Well, take a look 
at the types of `m` and `t`:

julia> type MyStillAmbiguousType 
    a::FloatingPoint 
end 

julia> type MyType{T<:FloatingPoint} 
    a::T 
end 

julia> t = MyStillAmbiguousType(3.2) 
MyStillAmbiguousType(3.2) 

julia> m = MyType(3.2) 
MyType{Float64}(3.2) 

julia> typeof(t) 
MyStillAmbiguousType 

julia> typeof(m) 
MyType{Float64}

When the compiler gets to the type inference stage, it can figure out a lot 
more about what a value like `m` will do when passed around than it can for 
a value like `t`, even though at this point in time the compiler doesn't 
know the value of either `m` or `t`. This is because `m`'s type carries 
that `Float64` parameter. Now, there are a couple of difficulties with your 
proposed solution, i.e. branching on the type of `t.a`. The first is that 
introducing unnecessary branching can hurt performance in other ways. The 
second -- and more pertinent -- is that, though the compiler may be able to 
reason about `f1` and `f2` at compile time (though this will be limited for 
reasons discussed below), it still can't reason about `f` at compile time. 
All the compiler can say about `f` is that it might return a Float32 or 
that it might return a Float64, and nothing more. Now all downstream 
interactions with whatever gets returned by `f`will have to take that 
ambiguity into account -- the returned object will have to be "boxed" and 
its type checked to make sure that the proper instructions are followed. 
However, if the compiler knew, for instance, that `f` could *only* return 
an object of type Float64, then any downstream interactions with that 
object could be streamlined based on that knowledge.

As far as your second question goes, note that there's nothing any more 
restrictive about declaring `x::Float64` on its own or wrapped inside of a 
type. The point here is that you could, within a function, set `t.a = 
Float32(3.2)`, and t will still be of type `MyStillAmbiguousType`. So, the 
compiler may still be limited in its ability to reason about what can go 
happen to t in `f1` or `f2` even after you branch on the initial value of 
`t.a`. However, you can't change `m.a` to a Float32 -- any attempt to do so 
will just result in that Float32 value being reinterpreted back into a 
Float64.

On Monday, July 20, 2015 at 6:21:17 PM UTC-4, Mathew wrote:
>
> I'm not clear on the following element in Julia FAQ: 
> http://julia.readthedocs.org/en/latest/manual/faq/#how-do-abstract-or-ambiguous-fields-in-types-interact-with-the-compiler
>
> In a nutshell, the FAQ defines two types:
>   
>
> >     type MyStillAmbiguousType
> >              a::FloatingPoint
> >     end
> >     t = MyStillAmbiguousType(3.2)
> > 
> >     type MyType{T<:FloatingPoint}
> >              a::T
> >            end
> >     m = MyType(3.2)
> > 
> > The fact that the type of m.a is known from m‘s type—coupled with the
> > fact that its type cannot change mid-function—allows the compiler to
> > generate highly-optimized code for objects like m but not for objects
> > like t.
>
> I don't really understand this passage (I don't know anything about 
> compilers to begin with!) 
>
> - If the acceleration comes from the fact the type is known before the 
> function is runned, can't the compiler create itself functions with all 
> fields of the type as argument, rather than the type as an argument, ie 
> transforming
>
>         f(t::MyStillAMbiguousType)
>       
>         into
>
>         f(t::MyStillAMbiguousType) = f(fa::FloatingPoint = t.a)
>
>      More bluntly, another solution would be something like
>
>         function f(t::MyStillAmbiguousType)
>            if typeof(f.a) == Float64
>              f1(t)
>            elseif typeof(f.a) == Float32
>              f2(t)
>            end
>         end
>
>
> - If the acceleration comes from the fact that "its type cannot change 
> mid-function", why don't we replace every arguments of a function by an 
> type that encloses its fields, to declare that the type won't change in the 
> body of the function. For instance, instead of `f(x::Float64)`, define
>  
>         type EnclosedFloat
>              x::Float64
>         end
>         f(x::EnclosedFloat)
>  
>
>
>

Reply via email to