How to use a type wrapping a Symbol as a type parameter?

This question has some lengthy background where I explain my understanding of the problem. If you already know how the rules Julia uses for using values inside type parameters you can skip to the end:


My understanding is that currently in Julia, if you want to use a value in a type parameter, that value must be a bit value satisfying isbits(v) == true. If that works, then for instance you can do

julia> isbits(5)
true

julia> Val(5)
Val{5}()

julia> typeof(Val(5)) == Val{5}
true

This also works for types which wrap isbits types

julia> struct MyInt a::Int end

julia> isbits(MyInt(5))
true

julia> Val(MyInt(5))
Val{MyInt(5)}()

On the other hand, non bit types can’t be used in type prameters

julia> isbits("hi")
false

julia> Val("hi")
ERROR: TypeError: in Type, in parameter, expected Type, got String
Stacktrace:
 [1] Val(::String) at ./essentials.jl:728
 [2] top-level scope at none:0

an exception to this rule is Symbols which as far as I understand have been special-cased such that they are allowed in type parameters

julia> isbits(:hi)
false

julia> Val(:hi)
Val{:hi}()

However, it seems unlike isbits types, I can’t wrap a Symbol in another type and have that work in a type parameter:

julia> struct MySymbol s::Symbol end

julia> Val(MySymbol(:hi))
ERROR: TypeError: in Type, in parameter, expected Type, got MySymbol
Stacktrace:
 [1] Val(::MySymbol) at ./essentials.jl:728
 [2] top-level scope at none:0

The Actual Question
Is there any way for me to tell the compiler that a value of type

struct MySymbol s::Symbol end

is safe to put in a type parameter? Or is the special casing for Symbol not replicable at the user / package level?

2 Likes

You may have mixed up the concept of field and type parameter.

Your definition struct MySymbol s::Symbol end has a field called s but no type parameters. If you want to use a type parameter, it would be defined like this:

julia> struct MySymbol{s} end

julia> Val(MySymbol{:hi})
Val{MySymbol{:hi}}()
1 Like

I don’t believe I have.

What I asked was how I can put a struct which wraps a Symbol into a type parameter. Not how I can put a symbol in a type parameter.

Notice that

julia> struct MyInt a::Int end

julia> isbits(MyInt(5))
true

julia> Val(MyInt(5))
Val{MyInt(5)}()

works fine. I want to do the analogous operation with a struct that wraps a Symbol.

I know that I can put the symbol in the type parameter of MySymbol but that wasn’t what I wanted.

Yeah, this is an interesting question. As far as I know, you can’t do what you’re asking for, and Symbol is a bit special in this way. If you were allowed to do this, it would probably have some surprising results, since Bar{foo1} would presumably only be the same type as Bar{foo2} if foo1 === foo2 (not simply if foo1 == foo2), so you would have:

s = :hello
s === s # true

but non-bitstypes have their own identities, so Foo(s) !== Foo(s) and thus Bar{Foo(s)} !== Bar{Foo(s)} and also Bar{Foo(s)} !<: Bar{Foo(s)}. That would be somewhat awkward.

Some relevant experiments related to object identity:

julia> struct Foo; a::Symbol; end
julia> isbitstype(Foo)
false
julia> Foo(:hello) === Foo(:hello)
true
julia> mutable struct Bar; a::Symbol; end
julia> isbitstype(Bar)
false
julia> Bar(:hello) === Bar(:hello)
false

I understand OP’s point as saying that because Foo objects don’t have identity, there should be no reason they couldn’t appear in a type parameter. I would agree.

FWIW, tuples of symbols can appear as type parameters (and I use that a lot in my package GitHub - tkluck/PolynomialRings.jl: A library for arithmetic and algebra with multi-variable polynomials.):

julia> Val((:x, :y, :z))
Val{(:x, :y, :z)}()
2 Likes

The relevant check appears to be this runtime function:

static int valid_type_param(jl_value_t *v)
{
    if (jl_is_tuple(v)) {
        // NOTE: tuples of symbols are not currently bits types, but have been
        // allowed as type parameters. this is a bit ugly.
        jl_value_t *tt = jl_typeof(v);
        size_t i, l = jl_nparams(tt);
        for(i=0; i < l; i++) {
            jl_value_t *pi = jl_tparam(tt,i);
            if (!(pi == (jl_value_t*)jl_sym_type || jl_isbits(pi)))
                return 0;
        }
        return 1;
    }
    if (jl_is_vararg_type(v))
        return 0;
    // TODO: maybe more things
    return jl_is_type(v) || jl_is_typevar(v) || jl_is_symbol(v) || jl_isbits(jl_typeof(v));
}

I wonder if you could just add stuff there…

3 Likes

Would Julia need to be rebuilt if this was modified?

Yes, of course, it is a C function so you would need to run make.