Dynamic Union


#1

Hi,

I can do this in coding phase.

const SymExpr = Union{Symbol,Expr}
:s isa SymExpr # returns true
:(x+1) isa SymExpr # returns true
1 isa SymExpr # returns false

I want a dynamic Union so that I can add Int to it at runtime and the check 1 isa SymExpr return true?

Can I do this? If so what is the proper way

Thanks


#2

Don’t think that is possible, since Union types are immutable

julia> x = Union{Float64,Array}
Union{Float64, Array}

julia> x.a
Float64

julia> x.a = Int
ERROR: type Union is immutable
Stacktrace:
 [1] setproperty!(::Type, ::Symbol, ::Type) at ./sysimg.jl:16
 [2] top-level scope at none:0

You could keep an array of types and check typeof(x) in SymExpr, where SymExpr = [Symbol,Expr,Int], then you could add to the array.


#3

You want to keep a variable keeping a list of types that you can add Int to?

Just SymExpr = Union{SymExpr,Int}?


#4

If you want to change SymEpr at runtime but do not want to incur type-instability costs you can make it a reference.

julia> const SymExpr = Ref(Union{Symbol,Expr})
Base.RefValue{Union}(Union{Expr, Symbol})

julia> 1 isa SymExpr[]
false

julia> SymExpr[] = Union{SymExpr[],Int}
Union{Int64, Expr, Symbol}

julia> 1 isa SymExpr[]
true

#5

You could use a trait:

struct SymExpr end
SymExpr(::Expr) = SymExpr()
SymExpr(::Symbol) = SymExpr()
SymExpr(::Any) = nothing

SymExpr(:a) isa SymExpr # -> true
SymExpr(1) isa SymExpr # false
SymExpr(::Int) = SymExpr()
SymExpr(1) isa SymExpr# true

see https://docs.julialang.org/en/v1/manual/methods/#Trait-based-dispatch-1.


#6

I am sorry maybe I haven’t conveyed the idea to the fullest extent.

What I want to do is:

SymExpr = Union{Symbol,Expr}
f(x::SymExpr) = println(x)
f(:a) #prints a
f(:(x+1)) #prints x+1
f(1) # ERROR: MethodError: no method matching f(::Int64)
SymExpr = Union{SymExpr,Int}
f(1) # Still ERROR.

I want the last f(1) call to dispatch correctly.

Thanks


#7

Why would you not want to have Int in your union in the first place? Why does it need to be dynamic? Are you only going to add Int to it, or is there a more general pattern you need to satisfy?

I’m trying to understand more about your motivations behind this.


#8

The only way to change dispatch is to define (or delete…) methods so no you can’t do anything else and change dispatch. You can always add your own type check that is as dynamic as you want though.


#9

Have you read the link I posted above? It tells you how to do it:

struct SymExpr end
SymExpr(::Expr) = SymExpr()
SymExpr(::Symbol) = SymExpr()
SymExpr(::Any) = nothing

# Do the trait dispatch
# f(::SymExpr) = 1
# needs to be written as
f(x) = _f(SymExpr(x), x)
_f(::SymExpr, x) = 1
_f(::Nothing, x) = 2 # or leave it away for a method error

f(:a) # 1
f(1)  # 2
SymExpr(::Int) = SymExpr()
f(1)  # 1

If you can’t be bothered with writing out the trait-dispatch (and you just need a simple trait-dispatch setup) then you can use https://github.com/mauro3/SimpleTraits.jl

using SimpleTraits
@traitdef SymExpr{X}
@traitimpl SymExpr{Expr}
@traitimpl SymExpr{Symbol}

@traitfn f(::::SymExpr) = 1
@traitfn f(::::!(SymExpr)) = 2 # again, not defining this will just give a Method error
f(:a) # 1
f(:(1+1)) # 1
f(1) # 2
@traitimpl SymExpr{Int}
f(1) # 1

#10

I thank you very much for your kind answer mauro3.
I have figured out a similar way to yours, I have my own traits package.
One of the reasons I asked that question to see what type level programming utilities Julia is providing.
Thanks