What does this mean in function signatures, for example:
convert(::Type{T}, z::Complex) where {T<:Real}
Strictly speaking, one should differentiate between the predicate Base.:(<:), as described in #Saqib's answer, and the syntactic usage of <: for describing constraints.
This syntactic usage can occur in type parameter declarations of methods, to constrain a type variable to be a subtype of some other type:
f(x::T) where {T<:Real} = zero(x)
A sort of special case of this is when you constrain the type parameter of a struct (struct Foo{T<:Real} ... end) -- that constrains the methods of the generated constructor, and allows the type constructor to be applied only to the constrained subtypes.
On the other hand, outside of type parameters, <: can be used to declare a new type as a subtype of some other (necessarily abstract) type:
struct Foo <: Real end
Although both cases are in line with the meaning of the subtyping predicate, you can't replace them with other arbitrary expressions (e.g., you can't write ... where {isreal(T)} in f).
<:(T1, T2)
Subtype operator: returns true if and only if all values of type T1 are
also of type T2.
Examples:
Float64 <: AbstractFloat
=> true
Vector{Int} <: AbstractArray
=> true
Matrix{Float64} <: Matrix{AbstractFloat}
=> false
Related
For the Julia manual parametric composite type example
struct Point{T}
x::T
y::T
end
it is possible to write an outer constructor such as
Point(x::T, y::T) where {T} = Point{T}(x, y)
Why is the where {T} part needed, i.e. why isn't
Point(x::T, y::T) = Point{T}(x, y)
possible? Julia complains that T is not defined, but could it not figure out that T is a type from the :: syntax? I am a newcomer to Julia so I might be missing something pretty basic.
T is a variable that has to be defined. If you do not define it in where Julia starts looking for it in an outer scope. Here is an example:
julia> struct Point{T}
x::T
y::T
end
julia> Point(x::T, y::T) = Point{T}(x, y)
ERROR: UndefVarError: T not defined
Stacktrace:
[1] top-level scope at REPL[2]:1
julia> T = Integer
Integer
julia> Point(x::T, y::T) = Point{T}(x, y)
Point
julia> Point(1,1)
Point{Integer}(1, 1)
Note that in this example you do not get what you expected to get, as probably you have hoped for Point{Int64}(1,1).
Now the most tricky part comes:
julia> Point(1.5,1.5)
Point{Float64}(1.5, 1.5)
What is going on you might ask. Well:
julia> methods(Point)
# 2 methods for type constructor:
[1] (::Type{Point})(x::Integer, y::Integer) in Main at REPL[4]:1
[2] (::Type{Point})(x::T, y::T) where T in Main at REPL[1]:2
The point is that The (::Type{Point})(x::T, y::T) where T already got defined by default when the struct as defined so with the second definition you have just added a new method for T = Integer.
In short - always use where to avoid getting surprising results. For example if you would write:
julia> T = String
String
julia> Point(1,1)
ERROR: MethodError: Cannot `convert` an object of type Int64 to an object of type String
you get a problem. As the signature of Point is fixed when it is defined but in the body T is taken from a global scope, and you have changed it from Integer to String, so you get an error.
Expanding on what #Oscar Smith noted the location of where is also sensitive to scope, and tells Julia on what level the "wildcard" is introduced. For example:
ulia> T1 = Vector{Vector{T} where T<:Real}
Array{Array{T,1} where T<:Real,1}
julia> T2 = Vector{Vector{T}} where T<:Real
Array{Array{T,1},1} where T<:Real
julia> isconcretetype(T1)
true
julia> isconcretetype(T2)
false
julia> T1 isa UnionAll
false
julia> T2 isa UnionAll
true
julia> x = T1()
0-element Array{Array{T,1} where T<:Real,1}
julia> push!(x, [1,2])
1-element Array{Array{T,1} where T<:Real,1}:
[1, 2]
julia> push!(x, [1.0, 2.0])
2-element Array{Array{T,1} where T<:Real,1}:
[1, 2]
[1.0, 2.0]
and you can see that T1 is a concrete type that can have an instance, and we created one calling it x. This concrete type can hold vectors whose element type is <:Real, but their element types do not have to be the same.
On the other hand T2 is a UnionAll, i.e. some of its "widlcards" are free (not known yet). However, the restriction is that in all concrete types that match T2 all vectors must have the same element type, so:
julia> Vector{Vector{Int}} <: T2
true
julia> Vector{Vector{Real}} <: T2
true
but
julia> T1 <: T2
false
In other words in T2 the wildcard must have one concrete value that can be matched by a concrete type.
I'm going to take a different tack from BogumiĆ's excellent answer (which is 100% correct, but might be missing the crux of the confusion).
There's nothing special about the name T. It's just a common convention for a short name for an arbitrary type. Kinda like how we often use the name A for matrices or i in for loops. The fact that you happened to use the same name in the struct Point{T} and in the outer constructor is irrelevant (but handy for the readers of your code). You could have just as well done:
struct Point{SpecializedType}
x::SpecializedType
y::SpecializedType
end
Point(x::Wildcard, y::Wildcard) where {Wildcard <: Any} = Point{Wildcard}(x, y)
That behaves exactly the same as what you wrote. Both of the above syntaxes (the struct and the method) introduce a new name that will behave like a "wildcard" that specializes and matches appropriately. When you don't have a where clause, you're no longer introducing a wildcard. Instead, you're just referencing types that have already been defined. For example:
Point(x::Int, y::Int) = Point{Int}(x, y)
That is, this will reference the Int that was already defined.
I suppose you could say, but if T wasn't defined, why can't Julia just figure out that it should be used as a wildcard. That may be true, but it introduces a little bit of non-locality to the syntax, where the behavior is drastically different depending upon what happens to be defined (or even exported from a used package).
I need to define a Vector such that all elements in it need to be of the same type, though the type itself can be of any type. I tried the below:
["1", 2] isa AbstractVector{T} where T <: Any
but this returns true.
The following works in this case and correctly returns false as needed:
["1", 2] isa AbstractVector{T} where T <: Union{AbstractString, Number}
But, I don't want to restrict the type to be only Strings, Numbers etc. So, how else can I restrict all elements of a Vector to be of the same type though the type itself can be flexible?
["2", 2] is of type Vector{Any} and T <: Any is true because setting T = Any gives Any <: Any which should evaluate to true.
"1" is a String and String <: AbstractString is true. But Julia's type system only works like this
T{S} <: T'{S} is true if T <: T' but is not true if T{S} <: T{S'} even if S <: S'. I don't know the technical term for that in type theory but it should be detailed in here https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)
Is it possible in Julia to have two structs with the same name but be assigned different types and thus be distinguishable?
I have been reading https://docs.julialang.org/en/v1/manual/types/#Parametric-Types-1 and it seems to be leading towards what I want but I can't get it to work...
In force-fields for molecular simulation there are dihedral parameters to describe torsion angles in molecules. There are different kinds for example purposes lets limit them to 2 kinds: proper and improper. I would like to have two structures, both called dihedral, but given the types "proper" and "improper". I would then have methods specific to each type to calculate the forces due to dihedrals. I think abstract parametric types get me the closest to what I want but I can't get them sorted...
abstract type proper end
abstract type improper end
struct Dihedral <: proper
ai::Int64
kparam::Vector{Float64}
end
struct Dihedral <: improper
ai:Int64
kparam::Float64
end
The above code does not work... I have tried using
abstract type dihedral end
abstract type proper <: dihedral end
abstract type improper <: dihedral end
struct Dihedral <: dihedral{proper}
...
end
struct Dihedral <: dihedral{improper}
...
end
But I always get in trouble for redefining Dihedral
ERROR: LoadError: invalid redefinition of constant Dihedral
Stacktrace:
[1] top-level scope at none:0
My thought is that I can add in more types of dihedrals and all i need to do is also add in their methods and the simulation will automatically use the new dihedral.methods. If I try making structs of different names, then I start having to use if statements to direct the program to the correct structure and later to the correct methods... This is what I want to avoid i.e.,
if dihedraltype == "proper"
struct_proper(...)
elseif dihedraltype =="improper"
struct_improper()
elseif dihedraltype == "newStyle"
struct_newStyle()
end
using this method I would have to find all places in my code where I call dihedral and add in the new type... dihedral is just an example, there are many "phenomenas" that have different methods for calculating the phenomena.
I would use the following approach if you want to use a parametric type:
abstract type DihedralType end
struct Proper <: DihedralType
ai::Int64
kparam::Vector{Float64}
end
struct Improper <: DihedralType
ai::Int64
kparam::Float64
end
struct Dihedral{T<:DihedralType}
value::T
end
Dihedral(ai::Int64, kparam::Vector{Float64}) = Dihedral(Proper(ai, kparam))
Dihedral(ai::Int64, kparam::Float64) = Dihedral(Improper(ai, kparam))
and now you can write e.g.:
Dihedral(1, [1.0, 2.0])
Dihedral(1, 1.0)
The parameter of type Dihedral passes you information what kind of the object you are working with. Then some methods may be generic and call Dihedral e.g.:
julia> ai(d::Dihedral) = d.value.ai
ai (generic function with 1 method)
julia> ai(Dihedral(1, 1.0))
1
julia> ai(Dihedral(1, [1.0, 2.0]))
1
julia> kparam(d::Dihedral) = d.value.kparam
kparam (generic function with 1 method)
julia> kparam(Dihedral(1, 1.0))
1.0
julia> kparam(Dihedral(1, [1.0, 2.0]))
2-element Array{Float64,1}:
1.0
2.0
and some may be type parameter specific:
julia> len(d::Dihedral{Proper}) = length(kparam(d))
len (generic function with 1 method)
julia> len(Dihedral(1, [1.0, 2.0]))
2
julia> len(Dihedral(1, 1.0))
ERROR: MethodError: no method matching len(::Dihedral{Improper})
Closest candidates are:
len(::Dihedral{Proper}) at REPL[15]:1
Stacktrace:
[1] top-level scope at none:0
Does this approach give you what you have expected?
EDIT
Actually maybe an even simpler approach may be enough for you (depending on the use case). Just define:
abstract type AbstractDihedral end
struct Proper <: AbstractDihedral
ai::Int64
kparam::Vector{Float64}
end
struct Improper <: AbstractDihedral
ai::Int64
kparam::Float64
end
and then implement methods in terms of DihedralType if they are generic for all dihedrals and if you want to add some specific method to a given concrete type just add this method with this concrete type in the signature. For example:
ai(d::AbstractDihedral) = d.ai
kparam(d::AbstractDihedral) = d.kparam
len(d::Proper) = length(d.kparam) # will not work for Improper
In this approach you do not need to use a parametric type. The difference is that in the parametric type approach you can extract out the parameters that are the same for all dihedrals to the "parent" struct and define only dihedral specific parameters in the "wrapped" struct. In the second approach you have define all fields every time for each struct.
Given some complex object, e.g.:
> d = Dict(("a", "b")=>3, ("c", "d")=>2)
Dict{Tuple{String,String},Int64} with 2 entries:
("c","d") => 2
("a","b") => 3
I could check the type with:
> isa(d, Dict{Tuple{String, String},Int64})
true
But when I underspecify the Tuple type, it failed the check:
> isa(d, Dict{Tuple,Int64})
false
Is it possible to check for underspecified types in Julia? If so, how? If not, why?
In Julia, dictionaries, like arrays, are invariant. For example, see this question on the concept as it applies to arrays.
This means that:
julia> Int <: Number
true
but,
julia> Vector{Int} <: Vector{Number}
false
and similarly,
julia> Dict{Int, String} <: Dict{Number, String}
false
However, note that Dict on its own is abstract, so
julia> Dict{Int, String} <: Dict
true
and in the code you provide,
julia> isa(d, Dict)
true
As far as I know, if you want to reason specifically about the pair of types in your dictionary, you will need to reference them explicitly. You can do this using keytype(d) and valtype(d). For example, from your question, note that
julia> keytype(d) <: Tuple
true
Of course, if your dictionary is passed into a function, then you can use Dict{T1, T2} in the function signature, and then just reason about T1 and T2...
EDIT: See #FengyangWang's answer for a neat little syntax shortcut being introduced in v0.6
In Julia 0.6 you can simply do
isa(d, Dict{<:Tuple, Int64})
which means a Dict with key type a subtype of Tuple, and value type Int64.
Note that Dict{Tuple, Int64} is not an "underspecified" type: it is a concrete type and can have instances. However, Dict{Tuple{String, String}, Int64} is not the same thing. The former is a type that accepts all tuples as keys, whereas the latter only accepts Tuple{String, String}. This is an example of parametric invariance.
For simplicity's sake, suppose I have the following function defined:
function returnVectorOrMatrix()
vals = Array(Array{Float32,1}, 10) # vector in this definition
return vals::Array{Array{Float32},1}
end
arr = returnVectorOrMatrix()
Which to my big surprise generates the following error:
ERROR: type: typeassert: expected Array{Array{Float32,N},1},
got Array{Array{Float32,1},1}
Does anyone have a good logical reasoning as to why this is happening / was designed in such way?
Because Array{Array{Float32,1},1} is just a special case of Array{Array{Float32,N},1} with N = 1 so given that Julia is multiple-dispatch I would expect such function to work fine (and seems logical/intuitive as well)
Hint:
julia> Array{Float32,1} <: Array{Float32}
true
julia> Array{Array{Float32,1},1} <: Array{Array{Float32},1}
false
julia> Array{Array{Float32,1},1} <: Array{Array{Float32,1}}
true
could you find any clue now?
in fact, the Array{Array{Float32,1},1} is a parametric type,
julia> Array{Array{Float32,1},1} <: Array{Array{Float32},1}
false
I think the mechanism here is the same case as:
julia> Array{Int32,1} <: Array{Int,1}
false
even if Int32 is a special case of Int, julia will return a false here.
Bacause Array{Array{Float32,1},1} is just a special case of Array{Array{Float32,N},1} with N = 1
so this statement is not true because Array{Float32,1} and Array{Float32,N} are type parameters which are invariant in julia.
I think the problem is simply that Array{Float32} is really Array{Float32,N}, and not Array{Float32,1} (which is the same as Vector{Float32}).