SumTypes.jl icon indicating copy to clipboard operation
SumTypes.jl copied to clipboard

Module prefixes error in `@cases` even when lack of export would make prefixing desirable

Open simsurace opened this issue 9 months ago • 3 comments

When defining sum types inside modules, arguably it would be desirable to be able to use the module prefix in order to make it clear where they are coming from. However, this currently does not seem to work:

julia> module MySumTypes
       using SumTypes
       @sum_type MySumType begin
           Variant1
           Variant2
       end
       end
Main.MySumTypes

julia> using .MySumTypes, SumTypes

julia> a = MySumTypes.Variant1
Variant1::MySumType

julia> @cases a begin
           Variant1 => 1
           Variant2 => 2
       end
1

julia> @cases a begin
           MySumTypes.Variant1 => 1
           MySumTypes.Variant2 => 2
       end
ERROR: LoadError: Invalid variant MySumTypes.Variant1
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] (::SumTypes.var"#69#72"{Expr, Base.RefValue{Any}, Vector{Any}})(::Tuple{Int64, Expr})
   @ SumTypes ~/.julia/packages/SumTypes/aO6qd/src/cases.jl:69
 [3] foreach(f::SumTypes.var"#69#72"{Expr, Base.RefValue{Any}, Vector{Any}}, itr::Base.Iterators.Enumerate{Vector{Any}})
   @ Base ./abstractarray.jl:3187
 [4] _cases(to_match::Symbol, block::Expr)
   @ SumTypes ~/.julia/packages/SumTypes/aO6qd/src/cases.jl:35
 [5] var"@cases"(__source__::LineNumberNode, __module__::Module, to_match::Any, block::Any)
   @ SumTypes ~/.julia/packages/SumTypes/aO6qd/src/cases.jl:22
in expression starting at REPL[29]:1

simsurace avatar Mar 24 '25 15:03 simsurace

The left hand side of the => in a @cases block is always the symbols associated with a. Namespacing them like this is actually unnecessary.

When you write

@cases a begin
    Variant1 => 1
    Variant2 => 2
end 

here's the macroexpansion:

julia> @macroexpand1 @cases a begin
           Variant1 => 1
           Variant2 => 2
       end
quote
    #= /home/mason/Dropbox/Julia/SumTypes/src/cases.jl:115 =#
    let var"##data#264" = a
        #= /home/mason/Dropbox/Julia/SumTypes/src/cases.jl:116 =#
        var"##Typ#268" = (typeof)(var"##data#264")
        #= /home/mason/Dropbox/Julia/SumTypes/src/cases.jl:117 =#
        (SumTypes.check_sum_type)(var"##Typ#268")
        #= /home/mason/Dropbox/Julia/SumTypes/src/cases.jl:118 =#
        (SumTypes.assert_exhaustive)(Val{(SumTypes.tags)(var"##Typ#268")}, Val{(:Variant1, :Variant2)})
        #= /home/mason/Dropbox/Julia/SumTypes/src/cases.jl:119 =#
        var"##unwrapped#270" = (SumTypes.unwrap)(var"##data#264")
        #= /home/mason/Dropbox/Julia/SumTypes/src/cases.jl:120 =#
        if var"##unwrapped#270" isa (SumTypes.Variant){:Variant1}
            #= REPL[13]:2 =#
            nothing
            1
        elseif var"##unwrapped#270" isa (SumTypes.Variant){:Variant2}
            #= REPL[13]:3 =#
            nothing
            2
        end
    end
end

or a bit more readable:

julia> prettify(ans)
:(let fly = a
      penguin = typeof(fly)
      check_sum_type(penguin)
      assert_exhaustive(Val{tags(penguin)}, Val{(:Variant1, :Variant2)})
      locust = unwrap(fly)
      if locust isa (SumTypes.Variant){:Variant1}
          nothing
          1
      elseif locust isa (SumTypes.Variant){:Variant2}
          nothing
          2
      end
  end)

The point here is that it is only ever checking that the type tag associated with the first argument is :Variant1 or :Variant2, it's not checking the object itself.

This system cannot get confused about namespacing because the lhs is always the one associated with the object you apply @cases to.

MasonProtter avatar Mar 24 '25 16:03 MasonProtter

Thanks, the point about the internal consistency is well taken. My point was more about human readability. If neither the name of the sum type nor the module appear in the @cases expression, it is somewhat hard to figure out what is happening and where one should look for the definitions. I think a work-around could be that one only uses @cases inside a function that is specialized to the sum type with an explicit type annotation.

simsurace avatar Mar 24 '25 19:03 simsurace

If neither the name of the sum type nor the module appear in the @cases expression, it is somewhat hard to figure out what is happening and where one should look for the definitions.

Yeah, this is definitely true, but I see it as directly analogous to getproperty, i.e. it's the same problem as when you write x.prop without writing out what the type of x is or where it came from.

I think a work-around could be that one only uses @cases inside a function that is specialized to the sum type with an explicit type annotation.

Yeah, I think that's generally a good idea when working with @cases, because using it is only valid if you list every single one of the valid variants anyways, so there's not really any such thing as a "generic" function that applies @cases.

MasonProtter avatar Mar 24 '25 19:03 MasonProtter