Hi,
The following is not possible:
let T <: Real
foo(t::T) = t + t
bar(t::T) = t - t
baz(t::T) = t * t
end
Is there a way to avoid writing the same type parameter restrictions again and again for each function?
julia> let X = T where T <: Real
global f(x::X) = x
global g(x::X) = x
end
g (generic function with 1 method)
julia> f(1)
1
julia> g(2.0)
2.0
Thanks! :-)
Fredrik Ekre said:
julia> let X = T where T <: Real
Which in this case is equivalent to let X = Real
.
So... how to define blocks with sets of dependent parametric types - e.g. (T1, T2) where {T1 <: Number,T2 <: AbstractVector{T1}}
?
julia> let T = T1 where {T1 <: Number}, TVector = T2 where {T2 <: AbstractVector{T}}
global function foo(x::T)
@show typeof(x)
end
global function foo(v::TVector)
@show typeof(v)
end
end
foo(2)
foo(2.0)
foo(vec([1 2 3]))
typeof(x) = Int64
typeof(x) = Float64
ERROR: MethodError: no method matching foo(::Vector{Int64})
Closest candidates are:
foo(::Number) at REPL[36]:3
foo(::AbstractVector{Number}) at REPL[36]:6
Letting TVector = AbstractVector
(without the parameterization on T
), and defining foo(v::TVector{T})
also does not define foo
for ::Vector{Int64}
.
Note that
julia> Vector{Int} <: Vector{Real}
false
julia> Vector{Int} <: Vector{<:Real}
true
see https://docs.julialang.org/en/v1/manual/types/#man-parametric-composite-types
Let me answer myself...
This works at least (the generic function must have a where T
):
let T = Number, TVector = AbstractVector
global function foo(x::T)
@show typeof(x)
end
global function foo(v::TVector{T}) where T
@show typeof(v)
end
end
foo(2)
foo(2.0)
foo(vec([1 2 3]))
... but all I achieved for AbstractVector
/TVector
was temporarily renaming it...
parametric types in julia are invariant (except tuples)
what would you like to achieve?
if you want to define a function that takes vectors with elements of any number type, you can define foo(v::AbstractVector{<: Number})
I would just like to avoid writing the same where {T1 <: AbstractT1, T2 <: AbstractT2{T1}}
over and over for a block of methods.
do you actually require T2
in the method?
if not, the way I've written it above is equvialent
Yes, I think so - see for a very simple example the two functions for SerializationTypeSerializer: https://github.com/IHPSystems/TypeSerializers.jl/blob/main/src/TypeSerializers.jl
those don't use TSerializer
in the method itself though, only T
in which case foo(s::SerializationTypeSerializer{T})::T where T
should be the same
or more concrete for your example ::Type{<:SerializationTypeSerializer{T}}
, I think
This is what got me started (using those "type serializers"):
https://github.com/IHPSystems/SerializingChannels.jl/blob/main/src/SerializingChannels.jl
(though intuitively, to me that already reads like relying a lot of type hierarchies, complicating things)
if you're already restricting the type at construction, why check for the same requirement on method definition as well?
e.g., the struct and the first function should be equivalent to this
struct SerializingChannel{T, TChannel <: AbstractChannel{Vector{UInt8}}, <: AbstractTypeSerializer{T}}
channel::TChannel
end
function Base.put!(channel::SerializingChannel{T, _, TSerializer}, value::T) where {T, TSerializer}
stream = serialize(TSerializer, value)
data = take!(stream)
put!(channel.channel, data)
end
if I'm not mistaken
you don't have to give things a name if you're not going to use them in the function
Ah yes - I see. Thanks a lot!
you're welcome!
also, if you want to avoid allocations, you may want to pass in an IO
object instead of constructing one in serialize
, only for it to be destroyed immediately again :)
though that may be harder to work into your current design, since you have a channel of Vector{UInt8}
(I'm also only speculating here, so may not be applicable to your situation)
But I guess the programmer is still left to repetitive type constraints in the general case? I.e. where the type parameterization include abstract parametric types - like in the Number, AbstractVector{<:Number}
example above.
you mean across methods?
Sukera said:
also, if you want to avoid allocations, you may want to pass in an
IO
object instead of constructing one inserialize
, only for it to be destroyed immediately again :)
Yeah - might be possible. It's highly targeted on being able to use FlatBuffers
for serialization and Zmq
as a transport.
It was also a bit of a struggle to make it play nice with the Serialization
module...
julia> const T = Number
Number
julia> const TVec = AbstractVector{<:T}
AbstractVector{<:Number} (alias for AbstractArray{<:Number, 1})
julia> foo(v::TVec) = "nope"
foo (generic function with 1 method)
julia> foo(Int[])
"nope"
though that only works when you don't have interdependencies between two types in a signature
so if you require one type parameter of one type and another of another type to be the same, you _will_ need that where
there's no way to express two different types having the same "slot" without specifying in which method that should apply
(i.e. there's no real dependent typing in julia)
also note that those const T
are really only constant aliases - they're _not_ their own "versions" of that type
It's highly targeted on being able to use
FlatBuffers
for serialization
well right now our serialize
is always allocating an IOBuffer
with its corresponding internal Vector{UInt8}
, right?
so my guess would be that it would be more efficient to directly write to the FlatBuffer instead of having those intermediary stores
Sukera said:
julia> const T = Number Number julia> const TVec = AbstractVector{<:T} AbstractVector{<:Number} (alias for AbstractArray{<:Number, 1}) julia> foo(v::TVec) = "nope" foo (generic function with 1 method) julia> foo(Int[]) "nope"
Excellent. A bit puzzled though, that I did not manage to make those T, TVec
definitions work in a let
block...
Should they just be nested let
blocks? And how then to global
'ize the method definitions?
Why doesn't it work with let
?
julia> let T = Number, TVec = AbstractVector{<:T}
global foo(::TVec) = "nope"
end
foo (generic function with 1 method)
julia> foo(Int[])
"nope"
Ah - it was probably just the parametric type invariance that I messed up (defining TVec = AbstractVector{T}
instead of TVec = AbstractVector{<:T}
).
Thanks again :-)
Last updated: Dec 28 2024 at 04:38 UTC