-
-
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
[RFC] Map for Nullable #9446
[RFC] Map for Nullable #9446
Conversation
In what context did you feel the need for I'd say |
@@ -56,3 +56,9 @@ function hash(x::Nullable, h::UInt) | |||
return hash(x.value, h + nullablehash_seed) | |||
end | |||
end | |||
|
|||
# Specialised map over Nullable | |||
function map(f::Callable, x) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd make this x::Nullable{T}
I think this captures a useful pattern in which you might want to apply |
|
||
# test map | ||
@test map(x->x, Nullable()) == Nullable() | ||
@test map(x->x) Nullable(1.0)) == Nullable(1.0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, Nullable()
does not construct a Nullable
, also, parens are not balanced in the last line.
716ad8f
to
3cc28ec
Compare
Thanks for the feedback, one does not simply submit a PR before heading straight to bed. As a return_type I decided on Nullable{Union()}() since that should be more applicable then Nullable{T}(). |
(My secret wish is that |
@nalimilan But the return type will always be depending on the return type of |
3cc28ec
to
b48975a
Compare
I updated the PR with the helpful comments from @nalimilan, The only thing that is weird is that sometimes the value of a Possible related to that is #9147
|
I think the situation is different here, because there are no return type declarations, and thus no conversion of |
Yeah, you're right about @JeffBezanson's point: |
Instead of implementing @nalimilan You need this when you write code that acts on containers, and that is independent of the type of container used. In this case, it is convenient to have a common API for all container-like types. |
b48975a
to
4ab6603
Compare
So I changed things back to @eschnett I was wondering that too, but I always felt that any container function besides |
That's why I don't really like this approach: I don't see |
This implementation is not type stable. Also, I agree with @nalimilan that it is strange to consider |
It may seem strange at first to consider |
Let's keep separate the issues of whether With that proviso, |
@lindahua wrt to this implementation not being type stable. Without proper return types I don't think that one could implement a = Nullable{Int64}(1)
b = Nullable{ASCIIString}("1")
map(cos, a)
map(parseint, b) In the functional programming languages I know For Based on a #9364 (comment) by Jeff I used |
For me |
?map says:
I think we're abusing the definition if As an example of the lacks that this PR does not fill, how are we going to apply |
@nalimilan Maybe this is abusing the definition of But this divide whether or not |
4ab6603
to
b631aeb
Compare
Sure, but the difference is that collections of collections are relatively rare (and when they are commonly needed they are wrapped in more usable types, like But I get your point that in functional languages nullable/option types are considered as collections. I'm not completely sold on that idea, though, as it seems like an unnecessarily verbose way of operating on nullable values. R's semantics are quite efficient on that front -- though with the drawback that all values are nullable, which is a nightmare when writing complex systems. A middle-term solution sounds more reasonable for all use cases. |
travis failed on osx due to a timeout.
True and the ways functional programming languages handle that problem are slightly verbose. You have to use either a double map or a map and pattern matching.
How does R handle that problem? I have not coded enough in it to encounter this or maybe I did, but I did not recognized it as such.
I think that especially in the context of |
R's semantics generalize what you suggest for |
That is not an accurate description of R's semantics. -- John Sent from my iPhone
|
I wanted to write a long and passionated post on the mailing list, about the semantics and possible usages of I don't like the I would rather see a null coalescing operator like
you can write.
Of course both styles solve a different problem and are orthogonal to each other and I would prefer having both |
I think this is the crux of the problem. My goal with the current design was to push people towards handling null values as soon as they occur, but there is obviously a lot of code in the world based on propagating null values from the start of a computation all the way to the end. |
That's indeed a use of
http://blog.codefx.org/jdk/dev/design-optional/ Indeed when working with statistical data, in many cases "no other way exists', i.e. you don't have any default value to provide, you just want to keep missing values as they are. When you are working with a But I completely agree that in other situations (more frequent in "normal" coding), propagating the missingness is not the best solution. The question is, can we make these two usages cohabit happily? Doesn't sound impossible. At least, contrary to what happens when |
Yes, one needs both cases: Substituting a "default" value for a missing value, and propagating "missingness". Nullable types are probably just a special case of a more general pattern. My earlier generalization to "containers" didn't fly so well (since containers usually can contains many values, while nullable types hold at most one). So I'm trying to look for other cases in Julia where types "hold" another value but are not containers. Maybe "wrappers" would be a better name for them:
Both RemoteRef and Task not only "contain" a value, but their main purpose is something else: a RemoteRef knows on which process the value lives, and a Task holds the administrative data to manage a task. But in both cases, there are ways to: These correspond to the two cases for nullable types, i.e. substituting a default, or propagating missingness. Maybe "map" isn't a good name for this (although it's called "map" in other languages), but there seems to be a generic pattern here, and a simple syntax would be convenient not just for nullable types. Concurrent programming (with Julia's efficient tasks) or distributed programming (simplifying handling RemoteRefs) would also benefit from that. |
What about the following proposal?
This means that propagation continues to be opt-in (which we wouldn't get from |
@johnmyleswhite That would work, but overloading @eschnett The parallel with julia> r = @spawn rand(2,2)
RemoteRef(1,1,0)
julia> s = @spawn exp(r) # instead of exp(fetch(r))
RemoteRef(1,1,1)
|
The advantage is that the current definition of That said, I'm starting to think that the software engineering use cases for For now, I would really prefer that we try using |
The more I think about this, the more convinced I'm becoming that we should separate out a concept of |
@johnmyleswhite, I thought our Generally, why do we need an "error code wrapper around a value" if we have exceptions? I guess there are a few cases like |
I think our We've started talking about using
Using the type system to force callers to check results for nullness is a big part of the argument for the Maybe type that |
@johnmyleswhite Actually I have hit this problem in R when developing software, which made me consider it's not a reasonable language for anything beyond stats. But in Julia at least when you have an I can see at least two ways forward here:
The advantages of 2) is that it does not require the API developer to decide what kind of missingness should apply to a returned value (in some cases it might be ambiguous). But more fundamentally, it also seems that even when not dealing with statistical data, the pattern |
What about an immutable immutable NullableFunction{T}
func::Function
end
Base.get(η::NullableFunction) = η.func
? = NullableFunction{Union()} # using 'typealias'' runs into ternary operator troubles
# Concatenation as a means of instance construction
function Base.(:*){T}(::Type{NullableFunction{T}}, f::Function)
return NullableFunction{T}(f)
end
# Overload 'call' for 'NullableFunction's with the moral equivalent of
# @vchuravy's original 'map' function
function Base.call{T}(η::NullableFunction{T}, x::Nullable)
isnull(x) && return Nullable{T}()
return Nullable(get(η)(get(x)))
end which allows for the following behavior:
Furthermore, by instead setting ? = NullableFunction one allows for user-specified values of
|
An interesting read about |
That was a good read. |
If we are able to use
Syntax-wise this isn't as satisfying as haskell style do-block/monadic expressions. |
IIRC |
Now that we have a |
The solution for |
Why? For example, if the final choice is to return |
For me
Nullable
is very similar to the Option type used in functional languages. Most of the functional languages I encountered treat Option as a container with one array and define operations like map over it.One possible way would be to define the iterator interface for
Nullable
thus allowingfor value in Nullable(value)
and all other method that depend on it. I personally see little to no value in that and thus only implemented the one operation that I find most useful:map
map
over Nullables allows for using Nullable unaware function with Nullable values and codifies a common pattern.This PR only implements
map
over a singular Nullable and for me the question is open whether amap
for multiple Nullable has a well defined meaning and is useful.