Structs and parametric constructors

Well, yes :slight_smile: Please let me try again, may be I am a bit slow :slight_smile:

when I do a b=range(...) the type of a is well known

  julia> b=range(1.0,stop=2.0, length=10)
1.0:0.1111111111111111:2.0

julia> typeof(b)
StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}}

I pass this to gamma. At the time I call the default constructor with b as an argument the type is known. I do not see a reason why julia could not specialise the type based on the argument by default. Instead I apparently have to explicitely “allow” (?)/ “force” (?) the type specialization by setting <:Any as the parametric type known at construction time (or typing statically with the StepRangeLen{Float64,Base.TwicePrecision{Float64},Base.TwicePrecision{Float64}} which is quite difficult to manually type correctly at first try in by hand :-).

Or to paraphrase:
The way everything else in julia appears to be designed is to specialize at runtime on the concrete types of the arguments given by compiling a specialized version for each combination of types of the actual arguments. Opposed to that the behaviour for constructors is apparently to throw the type information away instead by default, unless not explicitely requested

This appears to be a very strange decision, the behaviour of constructors is diametrically opposed compared to “normal” functions ?

Unfortunately, I can’t give you an explanation of why these design decisions were made, but it seems reasonable to me, since there might only be a few field types that need to be specialized on. For example, in the Fatou.Define object, I only need to specialize on the Function parameters and the Bool parameters, all the other fields do not need type parameters, because they are not referenced in the iteration function, and so it is not necessary to know their types when the iteration function is compiled. Not all of the fields are performance critical, some of them are only used for printing the label of a plot.

As you can see, parametric types can grow to be quite large. If Julia automatically specializes on any ambiguous type, then all of the types would automatically have a huge amount of parameters in their definition, which can be a lot of un-necessary information to pass around to the compiler and in other places. I assume this is why it is that way, for sake of minimalism?

A struct with an untyped field b must be able to hold a b of any type without the type of the struct itself changing. Julia does still specialize the constructor just like any other function, but the struct that it returns must not, by definition, depend on the type of b. If you want the type of the struct to depend on the type of b then you need a type parameter.

A couple of other notes:

  1. There are lots of cases in which untyped fields can be desirable, like when a field might need to store elements of varying types and accessing that field is not critical for performance. You could imagine making parametric structs the default, but aside from breaking all sorts of code, you would still presumably want some way to refer to those parameters by name, in which case you’d be back to exactly the system we have.

  2. You don’t need <:Any when declaring a type parameter. struct F{T} and struct F{T <: Any} are identical.

  3. If the type of a particular field is fixed but simply hard to type, you can just give it a shorter name:

const T = typeof(1:2)

struct F
  b::T
end

# this also works:
struct F
  b::typeof(1:2)
end
4 Likes

Thank you for the explanation. As far as I understand what you write (I do not have a computer science background) the argument might simply boil down to the immutability of the struct ? I see the utility of the current behaviour for general storage containers.

That’s a nice trick.

Mm, not exactly. The same argument applies to both mutable and immutable structs: there are legitimate use cases where you might not want a given field to have a concrete type, so Julia allows this. The fact that it’s the default is ultimately a design choice, but I think it’s a good one: if the language allowe a field to be silently or anonymously parameterized (i.e. didn’t have a corresponding {T} in the struct definition), then we would have no way to refer to the actual type associated with that field, which would make life harder in many cases.

1 Like

Right. I think I see where this argument is going, it starts to make sense. Probably with more experience this will become entirely clear. Thank you !