Are there some special low level hardcoded heuristics related to nothing
which makes it more suitable as a sentinel value than other types? I'm trying to avoid it since it is easy to return it by accident and I would rather throw an error in that case.
Example
julia> map(1:100) do x
isodd(x) && return x
nothing
end |> typeof
Vector{Union{Nothing, Int64}} (alias for Array{Union{Nothing, Int64}, 1})
julia> const mysentinel = "aa"
"aa"
julia> map(1:100) do x
isodd(x) && return x
mysentinel
end |> typeof
Vector{Any} (alias for Array{Any, 1})
Is it possible to make e.g. MySentinelType
get the same treatment as nothing in the above?
Its probably not a big deal in the grand scheme of things if not. It just threw me off a bit.
I guess one option is to use missing instead which is a little bit more difficult to return by accident:
julia> map(1:100) do x
isodd(x) && return x
missing
end |> typeof
Vector{Union{Missing, Int64}} (alias for Array{Union{Missing, Int64}, 1})
Nothing
is insofar special cased that it's the default sentinel value signifying the end of iteration
so the compiler is hesitant to promote Unions containing it to Any
, which would make iteration (like for loops) extremely bad
Since Missing
also results in a tight union, could it be that the special-casing applies to singleton types and not only Nothing
?
No; even with singleton types the returned type is Vector{Any}
So I recon it is some hardcoded special handling then? Anyways, missing is probably a safer choice in this case so I'll go with that.
in 95% of cases, you do not want to generate new missings
in your example, why not use filter
instead of map
in the first place?
Example was just the simplest way to show what I meant. But yeah, I try to avoid sentinel values whenever I can and maybe I also can in this case.
Real use case is I have an immutable tree structure which may be modified with an fmap
like function but then I realized I also want to prune away stuff. What is the fmap
equivalent of filter
? ffilter
?
It's not a very important project so I'd rather not redesign the whole thing just for type stability in the pruning case, but it wouldn't hurt to gain a bit of knowledge if it is served :).
depends on how you want to prune
but yes, that's just a filter
usually, pruning in a tree results in the child trees no longer being considered for expansion
so the filter
is just an additional check to be performed during expansion
The tree is just a deeply nested structure and the fmap
applies a function recursively to every node. With pruning I just meant that when fmap
:ing the user (me in this case) sometimes just want the node gone instead of modifying it.
There are nodes in the tree which can have an arbitrary amount of children so currently the missings just propagate upwards until they hit such a node which filters them out (and maybe return missing themselves if they are empty).
It's probably mostly a matter of hacking away at the problem before thinking through the use cases before making the above choices, but the current situation is workable for me.
that sounds like a bit of a weird map, doing filtering and mapping at the same time?
Yeah, it is. It just happened to be quite convenient, kind of like how select from dataframes both modifies and removes columns.
I guess I could just create another function with a very similar structure as the fmap
but which expects true
or false
from the provded function and uses that to decide whether to keep the child node or no. Not sure if it is just that simple since the propagation of the missings help a little bit (e.g. some nodes can only have a certain set of children so removal of one of their children must be propagated upwards).
Last updated: Nov 06 2024 at 04:40 UTC