How does instance of a type works/interact with type parameters?

Hi everyone,

I am studying the code from repo ReinforcementLearningAnIntroduction.jl.
In notebook Chapter01_Tic_Tac_Toe.jl, there is this line that confuses me

E = DefaultStateStyleEnv{Observation{Int}()}(env)

Why can we pass Observation{Int}() in to the brace of DefaultStateStyleEnv{}?

From my understanding, the stuff inside the brace {} should be some kind of type (e.g. DataType) or literal values, but Observation{Int}() is an instance of Observation{Int64}

typeof(Observation{Int}()) # Observation{Int64}
typeof(typeof(Observation{Int}()))  # DataType

For example, these will not work

# they will produce
# ERROR: TypeError: in Array, in element type, expected Type, got a value of type Float64
Array{1.0, 1}();
x=()->1.0; Array{x(), 1}()

So what is happening there?
I am guessing it is something to do with “UnionAll Types” and “Value types” but I couldn’t figure it out.

Thank you in advance.

(There is a 2 links max limit for new users, so I will paste the link to def of DefaultStateStyleEnv as raw text here https://github.com/JuliaReinforcementLearning/ReinforcementLearning.jl/blob/2e1de3e5b6b8224f50b3d11bba7e1d2d72c6ef7c/src/ReinforcementLearningEnvironments/src/environments/wrappers/DefaultStateStyle.jl#L7-L11)

Int aliases Int64 on 64-bit systems and aliases Int32 on 32-bit systems.

julia> Int
Int64
julia> typeof(Int64)
DataType
julia> typeof(Int)
DataType

Any type can be a parameter in the brace, it does not have to be an instance of DataType, e.g. Vector{Union{Int, Nothing}}.
Or are you referring to something else?

Hi, yes I was referring to something else, specifically:
Why can we pass Observation{Int}() in to the brace of DefaultStateStyleEnv{}

I have edited my question to make it clearer.
And thanks for the help!

From here: Types · The Julia Language

Both abstract and concrete types can be parameterized by other types. They can also be parameterized by symbols, by values of any type for which isbits returns true (essentially, things like numbers and bools that are stored like C types or structs with no pointers to other objects), and also by tuples thereof.

1 Like

Check isbitstype(Observation{Int}), is it true? Then instances of it can be a type parameter.

Note that in your own example you are parameterizing Array with the value 1 to get a 1-dimensional array.

Thank you for the help and yes, I notice that, but that is a literal value, which very often treated differently than runtime values in programming languages.
But yes, I don’t fully understand what are the allowed values inside {} in Julia and would be great if you can provide some examples to clarify it. I have read the document you quoted but don’t fully understand it.

I thought only types and literal values are allowed and if you want a instace as type, you need to convert it using the value type Val{x} (provided that it can be converted).

Yes, indeed it is, so that qualify it as a type parameter.

What about Val{x} and Val{x}() I think they are kinda related, do you mind explaining their relation and/or differences with some examples?

Value-parameters do not need to be literal, but in order to have type stable code, the parameters must be compile-time constants, and of course literal numbers are constant at compile time.

I think I now know how to check for valid type parameter (by isbitstype()), but the relation of all these with Val{x} and Val{x}() still confuses me.

Thanks for all the help anyways!

Well, isbits doesn’t cover it all, types, Symbols, and tuples of Symbols and isbits values also work. Actually, the easiest way to check if an instance X is a valid parameter is running Val{X} in the REPL and see if it throws an TypeError.

There’s no special relation, Val{x} is just one of many types with a parameter. Type parameter values, unlike argument values, are known at a method’s compile-time. Since you can put some non-type values in the parameters, that allows for some compile-time calculations in the method.

You can do that with any parametric type, but in cases where you just need to move a value into compile-time and don’t need to create a new type with new features, Val{x} exists for consistency. Otherwise, people would just be creating their own struct MyVal{T} end versions that are incompatible with each other. Val{x}() is just the type’s constructor call that makes its instance, in other words Val{x}() isa Val{x} the same way 1 isa Int.

1 Like

Thank you for clarification, I think I have some rough ideas on the relation/differences now. :grinning: