Stream: helpdesk (published)

Topic: Inference of Nothing


view this post on Zulip DrChainsaw (Feb 17 2023 at 16:00):

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.

view this post on Zulip DrChainsaw (Feb 17 2023 at 16:02):

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})

view this post on Zulip Sukera (Feb 17 2023 at 17:04):

Nothing is insofar special cased that it's the default sentinel value signifying the end of iteration

view this post on Zulip Sukera (Feb 17 2023 at 17:05):

so the compiler is hesitant to promote Unions containing it to Any, which would make iteration (like for loops) extremely bad

view this post on Zulip Cédric Belmant (Feb 17 2023 at 19:30):

Since Missing also results in a tight union, could it be that the special-casing applies to singleton types and not only Nothing?

view this post on Zulip mbaz (Feb 17 2023 at 19:34):

No; even with singleton types the returned type is Vector{Any}

view this post on Zulip DrChainsaw (Feb 17 2023 at 21:35):

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.

view this post on Zulip Sukera (Feb 17 2023 at 21:41):

in 95% of cases, you do not want to generate new missings

view this post on Zulip Sukera (Feb 17 2023 at 21:41):

in your example, why not use filter instead of map in the first place?

view this post on Zulip DrChainsaw (Feb 17 2023 at 21:52):

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 :).

view this post on Zulip Sukera (Feb 17 2023 at 21:53):

depends on how you want to prune

view this post on Zulip Sukera (Feb 17 2023 at 21:53):

but yes, that's just a filter

view this post on Zulip Sukera (Feb 17 2023 at 21:54):

usually, pruning in a tree results in the child trees no longer being considered for expansion

view this post on Zulip Sukera (Feb 17 2023 at 21:55):

so the filter is just an additional check to be performed during expansion

view this post on Zulip DrChainsaw (Feb 17 2023 at 22:00):

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.

view this post on Zulip Sukera (Feb 17 2023 at 22:02):

that sounds like a bit of a weird map, doing filtering and mapping at the same time?

view this post on Zulip DrChainsaw (Feb 17 2023 at 22:06):

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: Oct 02 2023 at 04:34 UTC