-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
A function to convert an optional Union{X,Nothing}
to an iterable Union{Tuple{X},Tuple{}}
#44864
base: master
Are you sure you want to change the base?
Conversation
flatmap is the composition of map and flatten. It is important for functional programming patterns. Some tasks that can be easily attained with list-comprehensions, including the composition of filter and mapping, or flattening a list of computed lists, can only be attained with do-syntax style if a flatmap functor is available. (Or appending a `|> flatten`, etc.) Filtering can be implemented by outputing empty lists or singleton lists for the values to be removed or kept. A more proper approach would be the optional monad, though, usually implemented in Julia as a union of Some and Nothing. This patch therefore also implements iteration methods for Some and Nothing, to enable the filtermap pattern with flatmap.
Union{X,Nothing}
to an iterable Union{Tuple{X},Tuple{}}
This concept of filter & map together is definitely useful, I find myself often reaching for it in data processing tasks. julia> Iterators.flatmap(monuple.(data)) do optx
Iterators.flatmap(optx) do x
x[1] == "x" ? () : (x,)
end
end |> collect Compare to a basic julia> filter(data) do x
!isnothing(x) && x[1] != "x"
end As for more general cases that include both Taking another example from your post, flatmap(data) do x
y = f(x)
if p(y) Some(y) else None() end
end that actually involves a julia> filtermap(data) do x
y = f(x)
if p(y) y else nothing end
end This doesn't involve any extra types such as |
@aplavin thanks for the comment. You are completely right, the It is true There's value in being able to see optional types, and other types, as objects similar to vectors. That's just what I'm trying to bring up. I'm not trying to force anyone to change their ways. And i can use tuples if I want to. The language already supports what I'm trying to do. I cannot convert my code to use The whole point of the patch is this actually: I'm claiming it's fine if I hope this clarifies that I am completely aware that By the way, Julia does offer the I'm perfectly fine with anyone who prefers |
But... You can? It's just
There are "iterable optional types" in Julia, as you point out as well. A 0/1 tuple, or a 0/1 vector, depending on your type stability requirements, is this optional type already.
Please, not
It would be nice to see concrete examples comparison with/without your proposal, where the benefit is clear. The one from the first post, julia> Iterators.flatmap(monuple.(data)) do optx
Iterators.flatmap(optx) do x
x[1] == "x" ? () : (x,)
end
end |> collect looks far from intuitive to me. It's hard to see that it's just |
Split from #44792
Union{X, Nothing}
is a popular way to represent missing values in Julia. A list of optionals can be filtered withfilter(!isnothing, data)
, or[x for x in data if !isnothing(x)]
.Comprehensions are handy because they enable simultaneous filtering and mapping in a terse notation:
[f(x) for x in 1:11 if p(x)==true]
is equivalent with for-loops to
Slightly more complex tasks quickly challenge the power of comprehensions. For instance, consider your test depends on the computed value, you can easily adapt a for-loop to write:
A comprehension has no simple way to store an intermediate value, and you'd be forced to write
[f(x) for x in 1:11 if p(f(x))==true]
or a hacky
[y for x in 1:11 for y in (f(x),) if p(y)==true]
Using do-syntax, this could be written with
filter(p, map(f, data))
, what is probably OK if you're using iterators.There's another style that allows us to filter-map in a single go, relying on
flatten
This is interesting because do-syntax gives us more freedom to write a body, similar to the for-loop, but we are still avoiding to write an explicit vector declaration and a
push!
.Please notice I'm not discussing anything related to using the
|>
operator, even though the topic easily goes that way. This is not the point, though. In the previous code, we might have written|> flatten
, but simply havingflatmap
available solves this.Some languages offer an optional type that is iterable. The equivalent to
Nothing
would represent an empty list, the equivalent toSome
a container with exactly one element. If something like that were available in Julia, we could writeIn my opinion, it would be nice to have this iterable optional type available in the Julia standard library. This is not the point of this PR, though. The idea here is that this "tuptional" approach is already pretty handy, and good enough to bring flatmap closer to the power of comprehensions and for-loops. I wouldn't like to see libraries using this as an API.
Union{X,nothing}
orUnion{Some{X}, Nothing}
is just fine. But given the ubiquity of using the non-iterablenothing
as optional, and the natural desire to map that toUnion{Tuple{X}, Tuple{}}
if you're coding in that style, it would be nice to offer a function to do that. That's whatmonuple
does. I'd be glad to call the function whatever, but here's an example of how you could use it in practice with a standard library function that returnsnothing
.Notice how mapping over the tuples we are able to compose functions that return optional values, and the flattening hides it away, no need for explicit
!isnothing
filtering. In my opinion, it's a big deal. Also it's pretty close to standard Julia.Anyways, that's the point of the PR, which was split away from #44792. With
Iterators.flatmap
available, it would be nice to offer something that enables this "flatmap + tuples" approach for mapping and filtering tasks.