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

#83 · open · 3 comments

View on GitHub ↗

simsurace

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 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 ```

Comments

MasonProtter

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 ```julia @cases a begin Variant1 => 1 Variant2 => 2 end ``` here's the macroexpansion: ```julia 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 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.

simsurace

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.

MasonProtter

> 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`.