Hi, sorry for my late reply. This is an old thread, but it is not resolved so I’d bump. Let me introduce the context a little, it seems trivial but actually not that trivial…
The context is I was doing some macro-based code generation, and there are two requirements.
My package’s first requirement is that I want to distinguish nothing
and “not found” from get
. Although get(...) do
seems working, but when it comes to code generation, due to the lack of a get
returning Union{Some{T}, Nothing}
, I have to choose one of the following three options:
- put the whole subsequent code in the inner function. The problem here is that my code generation involves
goto
and label
statements, which does not work cross-function.
- use
get(dict, k, default)
where default
is a secret_table_token
. The last few comments in this thread shows secret_table_token
works in practice, but as a library author I’d like to avoid including such code in the library. See julia#34821.
- use
get(dict, k, default)
where default
is guaranteed to be a different from the value looked up from the generic dict. This can be done via defining a new struct MyNothing
.
These options actually do not work. The 1st one is impossible in my case, the 2nd is evil. The 3rd looks nice but unfortunately does not work due to my package’s second requirement: My final goal is to develop a macro package that exists only at development time but not runtime (like babel in JS world), i.e., making a “runtime-free” package.
an example of "runtime-free" macro expansion
The following macro-expanded code can totally replace the original macrocall @switch
. If we expand macrocalls at development time, @switch
and its source package can be safely removed!
@macroexpand @switch x begin
@case Dict(1 => x) && if x < 5 end
println(x)
@case _
end
quote
true
var"##323" = x
#= REPL[33]:2 =#
if var"##323" isa Dict && (begin
var"##324" = if haskey(var"##323", 1)
Some(var"##323"[1])
else
nothing
end
var"##324" isa Some
end && (var"##324" !== nothing && begin
var"##325" = (var"##324").value
let x = var"##325"
x < 5
end
end))
x = var"##325"
var"##return#321" = begin
#= REPL[33]:3 =#
println(x)
end
$(Expr(:symbolicgoto, Symbol("####final#322#326")))
end
#= REPL[33]:4 =#
begin
var"##return#321" = begin
end
$(Expr(:symbolicgoto, Symbol("####final#322#326")))
end
(error)("matching non-exhaustive, at #= REPL[33]:1 =#")
$(Expr(:symboliclabel, Symbol("####final#322#326")))
var"##return#321"
end
The reason(s) why introducing MyNothing
breaks “runtime-free” can be illustrated by the following example:
module MyMod
function f()
# the first time expanding this macro in `MyMod`,
# surprisingly, defines a global struct `MyNothing` in `MyMod`
@codegenhere!
end
Above code intends to define MyNothing
when expanding codegenhere!
. However, it is not “runtime-free”, because obviously you cannot put a struct definition inside a local function, which means that MyNothing
should be defined using side-effects in the macrocall. Finally, the expanded code does not contain MyNothing
but does reference my MyNothing
. We have to provide runtime support that supplies MyNothing
.
P.S: see the generated code in the collapse section, I finally choose to generate 1 haskey
and 1 getindex
instead, which performs 2 hash search and makes MLStyle.jl significant slower than crafted code in this certain case.