Add a parametric Nullable{T} type #8152

Merged
merged 1 commit into from Sep 20, 2014

Projects

None yet
@johnmyleswhite
Member

This adds a parametric Nullable{T} type that can represent a value of type T that may be missing. It's got a very minimal interface with the hope that this will encourage you to resolve the uncertainty of whether a value is missing as soon as possible.

Work left to do:

I also did a little bit of whitespace removal along the way, which is hopefully forgivable.

@JeffBezanson
Member

👍

Is the error behavior of == part of the "encourage you to resolve the uncertainty" design?

I think we could get by without Null and NotNull. It will be simpler in the long run.

@johnmyleswhite
Member

Yeah, the error behavior was based on my thought that, since we should raise errors when comparing anything with a null value, it's easier to just not try comparing Nullable objects at all than to wait for a run-time error when your equality comparison hits its first null.

Agree that we can just use Nullable. Null and NotNull are an inheritance from a time when this code tried to imitate a standard Option type more closely.

@porterjamesj
Member

Once this is merged it's probably worth being very clear in the manual section precisely what the semantic differences are between Null{T} and Nothing. Having two types to represent absence is sure to be a point of confusion for many.

@ivarne
Contributor
ivarne commented Aug 27, 2014

We will have 3 concepts for nothing. Nothing/nothing, None/Void and NullableTypes. Looking forward to make http://docs.julialang.org/en/latest/manual/faq/#nothingness-and-missing-values even more complicated.

@porterjamesj
Member

Right, I forgot about None and Void.

@ivarne
Contributor
ivarne commented Aug 27, 2014

I wonder if get is the correct function to overload in this case. Currently get is part of the interface for collections, and Null{T} does not feel like a collection to me. Maybe a new function val or value?

@toivoh
Member
toivoh commented Aug 27, 2014

Well, it's a collection with zero or one element. Perhaps it should be
iterable and maybe even indexable. Not sure what that implies with regards
to get, though.
On Aug 27, 2014 8:44 AM, "Ivar Nesje" notifications@github.com wrote:

I wonder if get is the correct function to overload in this case.
Currently get is part of the interface for collections, and Null{T} does
not feel like a collection to me. Maybe a new function val or value?


Reply to this email directly or view it on GitHub
#8152 (comment).

@rfourquet
Contributor

I find the name slightly misleading, as a nullable value is immutable and as such can not be nulled after construction. I re-read rapidly the thread on julia-users, and I'm not sure if the question of supporting both "ontological/statistical missingness" as been decided. But if Nullable only supports statistical, someone will come request an Optional{T} (a 4th concept of nothing) for ontological missingness. I wouldn't implement differently Optional from Nullable, so why not support both with the same type? Morever @johnmyleswhite's view that "I’ve come to really like the interpretion of Option{T} [now Nullable{T}] as a 0-or-1 element container type" is the very concept behind C++'s (ontological) optional.

@rfourquet
Contributor

With the view of container with 0-or-1 elements, I would personally find getindex(x::Nullable) = get(x) quite natural, similar to Jameson Ref{T} type and to C++'s optional dereference operator. And it could allow to bypass the ugly unsafe_get via @inbounds.

@nalimilan
Contributor

Regarding ==: what's the suggested pattern to compare two Nullable? If that's get(x) == get(y), then I don't see why you wouldn't implement x == y as an equivalent, shorter syntax.

@JeffBezanson
Member

The main advantage of get is that you can specify a default value.

I don't think an Option type or this Nullable type can or should specify exactly what value-missingness means. It's for any case where there could be a value, but there isn't one right now.

In C# Nullable is a value type, so mutability is not supposed to be implied here. But I think it would be ok to call this Option or Optional instead.

The name None has become very unfortunate since it is so confusingly different from python. Python's None is actually our nothing. Our None should be renamed not to sound like a generic null-ish value. We could use VoidType or EmptyType. Nothing should probably also be renamed NothingType or something like that, so the nothing/Nothing distinction is clearer. (A related change is that the Void alias for ccall should really refer to NothingType and not None.)

@StefanKarpinski
Member

I actually very much like our choices of names for None, Nothing and nothing and have found that people are mostly not confused by them since they are so semantically apt. Perhaps Python should change it's naming instead ;-)

@kmsquire
Member

Regular expressions matching would be another good target for this once
it's merged.

On Wednesday, August 27, 2014, Stefan Karpinski notifications@github.com
wrote:

I actually very much like our choices of names for None, Nothing and
nothing and have found that people are mostly not confused by them since
they are so semantically apt. Perhaps Python should change it's naming
instead ;-)


Reply to this email directly or view it on GitHub
#8152 (comment).

@JeffBezanson
Member

Yes the names are apt, but I have seen people use Nothing instead of nothing several times and I can hardly blame them.
None is guilty of stealing a short, generic word for something that you almost never use, and should almost never use. Its aptness only gets it partially off the hook.

@johnmyleswhite
Member

Responses to various comments:

  • Should we allow ==? I'm not inclined to encourage people to compare Nullable objects. I'd rather that they test isnull and only compare values if actual values exist. == is kind of nuts no matter which perspective we take since all three possible implementations are weird: (1) == always raises an error, (2) == raises an error if at least one of the inputs is null, (3) == returns a Nullable{Bool} object when you do any comparison so that NULL == NULL => NULL. All seem bad. Universally raising errors seemed least bad.
  • There's not meant to be any direct commitment to either an epistemological or an ontological view of missingness here. To commit to an epistemological view, you need to implement three-valued logic, which I'd rather not do. Because this code mostly raises errors, it's closer to an ontological view. But I'd rather just offer a building block that lets you implement either.
  • I don't see a big need to implement functionality like getindex or iteration for Nullable since it won't increase the expressivity of the construct. But I could do it for symmetry if desired.
  • I personally think Nullable is a better name because it makes it easier to see that this construct is the basis for representing NULL. It is a little strange that you can't "null" a Nullable object, but I think it's at least defensibly strange.
  • I'm very much in favor of renaming None to EmptyType and Nothing to NothingType. The current names make those types seem much more useful than they are.
@StefanKarpinski
Member

I agree with the choice of Nullable as most intuitive to the most people. Option is a weirdly broad term that only suggests the right meaning to a very small set of people who will be using this. None doesn't actually need a name – we can write it as Union(). I don't care for renaming Nothing – yes, people misuse it, but that's going to happen.

@johnmyleswhite
Member

Updated:

  • Tests run now
  • Null and NotNull are both replaced with Nullable
@johnmyleswhite
Member

I think the fact that nothing and Nothing differ only in case is the source of misuse, though. Imagine if int were a value of type Int.

@JeffBezanson
Member

+1 to John's responses.

I kind of like the idea of using only Union(). That should help clarify the un-useful nature of the beast. Adding lots of names for things was fun back when there were only 200 of them, but now removing names is a much greater virtue.

@johnmyleswhite
Member

+1 to Union

@IainNZ
Member
IainNZ commented Aug 27, 2014

None => EmptyType is not bad though, or NoneType, or anything verbose, because you rarely if ever want to type it.

@IainNZ
Member
IainNZ commented Aug 27, 2014

If we change the meaning of x = [] where will users even see None?

@JeffBezanson
Member

Here's an interesting idea: rename Nothing to Void. In C, void is for things that return but don't return a value, which is what we use nothing for. A ccall with return type Void actually returns nothing in julia. There are various hacks in the system to patch around the fact that None is not actually the correct type to map C's void. I used None at first because I figured Nothing would be a lie --- the C code does not return a julia nothing value. But None is a worse lie. We should just fix this.

Void is much less problematic because it's close to the C usage, and doesn't collide with expectations from other dynamic languages.

@johnmyleswhite
Member

I like the mix of Void and Union() a lot.

@StefanKarpinski
Member

So nothing is the singleton instance of Void? And None is just Union()? Not bad. I worry that the difference between nothing and Void is going to be very confusing though. In C, void is the type with no instances, so it's pretty confusing that it would have an instance in Julia. Ptr{Void} would no mean what it means now.

@JeffBezanson
Member

That's a fair point --- in the case of Ptr{Void}, Ptr{None} is actually correct: dereferencing it is an error. We could instead hack in an error for dereferencing Ptr{Nothing}, but then of course the hack has just moved elsewhere.

Interestingly the following C code seems to be legal:

void f() {
    void *p = 0;
    return *p;
}

int main() {
    f();
    return 0;
}

This compiles, runs, and doesn't segfault. So giving nothing and not touching memory when deref'ing a Ptr{Void} is actually not so different from C.

@johnmyleswhite
Member

Updated with a draft of a manual section. I'm really bad with RST, so please make sure I haven't done anything very stupid. In particular, I'm worried about the interaction of doctest with a snippet of code that's supposed to throw errors.

@StefanKarpinski
Member

So, I'm a bit concerned that Nullable(T) where T is a type is ambiguous: did you want Nullable{T}() or Nullable{DataType}(T)?

@johnmyleswhite
Member

Do you want to remove it completely and expose Nullable{T}() instead?

@StefanKarpinski
Member

Maybe. Although I was ok with the whole Null and NotNull arrangement though. Have to think about it.

@johnmyleswhite
Member

One other issue is whether we should provide a generic NULL for situations in which the type of a Nullable object doesn't matter. I was pretty unsure what to think about that: https://github.com/johnmyleswhite/NullableTypes.jl/pull/3

@StefanKarpinski
Member

const NULL = Nullable{None}()?

@johnmyleswhite
Member

Or const NULL = Nullable{Union()}? I used to have NULL = Nullable{Any}, which I don't like.

@StefanKarpinski
Member

I think that None == Union() is the right type since it conveys to the compiler that the value can't possibly be used anywhere, which is the nature of that particular nullable value. Do you want a type or a value?

@johnmyleswhite
Member

A value I'd say. If we were to do this, we'd want to write things like:

s = sum(@data([1, 2, NA])

if isnull(s)
  foo[i] = NULL
else
  foo[i] = 1.0
end

Whether we should support that kind of thing isn't clear to me. But if we decide to allow it, we should try to get it right in this pass through the interface.

@JeffBezanson
Member

I'm very unsure about NULL since it is so different from what NULL means in other systems that have it. At the very least it needs a different name: NoValue, NullValue, missing, etc.

I assume we also want conversions between Nullable types? That way a Nullable{None} can be assigned to a Nullable{Int} location, and so on.

@johnmyleswhite
Member

Yeah, I'm very uncomfortable with NULL. I gave up on it after thinking about it for a few days. Let's can that idea.

Conversion between Nullable types seems complicated to me. In particular, should they succeed if the value is missing? I used to provide these methods, but was pretty unsure whether they were worth keeping: https://github.com/johnmyleswhite/NullableTypes.jl/blob/6f01d07a9f88cf1fbed563c4226fdebbeb54dee4/src/03_conversion.jl

@JeffBezanson
Member

Yeah, my head hurts thinking about whether you can convert the absence of a string to an absence of an integer.

@StefanKarpinski
Member

Let's keep it initially minimal and only add features when we have real use cases – that usually helps answer these kinds of questions.

@eschnett
Contributor

I like to think of Nullable types as weird arrays to decide what makes
sense, i.e. immutable arrays with a fixed size (that happens to be 0 or 1).
Thus, conversion should be allowed, but promotion should not.

-erik

On Wed, Aug 27, 2014 at 4:00 PM, John Myles White notifications@github.com
wrote:

Yeah, I'm very uncomfortable with NULL. I gave up on it after thinking
about it for a few days. Let's can that idea.

Conversion between Nullable types seems complicated to me. In particular,
should they succeed if the value is missing? I used to provide these
methods, but was pretty unsure whether they were worth keeping:
https://github.com/johnmyleswhite/NullableTypes.jl/blob/6f01d07a9f88cf1fbed563c4226fdebbeb54dee4/src/03_conversion.jl

Reply to this email directly or view it on GitHub
#8152 (comment).

Erik Schnetter schnetter@cct.lsu.edu
http://www.perimeterinstitute.ca/personal/eschnetter/

@JeffBezanson
Member

I think conversion may turn out to be reasonable, but I agree we should start with a minimal implementation.

@nalimilan
Contributor

It would make sense to call Nullable{None} NA instead of NULL, in a possible future harmonization of DataArrays and Nullable. But that's just a vague idea for later, better merge the basics now.

@StefanKarpinski
Member

The main concern is that you'd like convert and get to commute:

convert(T, get(s::Nullable{S}, S_default)) == get(convert(Nullable{T}, s::Nullable{S}), T_default)

That is true as long as convert(T, S_default) == T_default. There are situations where there is no choice of S_default or T_default that can make this true, but I think that may be ok.

@johnmyleswhite
Member

I'd rather rename DataArrays to NullableArrays.

@nalimilan
Contributor

@johnmyleswhite Possibly, too.

@StefanKarpinski
Member

NullableArrays is a much better name than DataArrays. All arrays hold data.

@johnmyleswhite
Member

Dropped the WIP, since I've finished my own checklist of tasks. Rebased to work again after the Grisu change.

@StefanKarpinski
Member

Perhaps a better argument for why it's ok to define

convert{S,T}(::Type{Nullable{T}}, x::Nullable{S}) =
    isnull(x) ? Nullable{T}() : Nullable(convert(T,get(x)))

is that with this definition conversion and get without defaults always commute. That is

convert(T, get(s::Nullable{S}))
get(convert(Nullable{T}, s::Nullable{S}))

will either produce the same value or both throw an exception, which is exactly the sort of commutativity you'd want for this kind of thing. If you provide defaults, this only commutes if convert maps one default to the other.

@johnmyleswhite johnmyleswhite changed the title from [WIP] Add a parametric Nullable{T} type to Add a parametric Nullable{T} type Aug 28, 2014
@johnmyleswhite
Member

Added Stefan's proposed conversion method.

@StefanKarpinski StefanKarpinski commented on the diff Aug 28, 2014
base/nullable.jl
+ value::T
+
+ Nullable() = new(true)
+ Nullable(value::T) = new(false, value)
+end
+
+immutable NullException <: Exception
+end
+
+Nullable{T}(::Type{T}) = Nullable{T}()
+
+Nullable{T}(value::T) = Nullable{T}(value)
+
+function convert{S, T}(::Type{Nullable{T}}, x::Nullable{S})
+ return isnull(x) ? Nullable{T}() : Nullable(convert(T, get(x)))
+end
@StefanKarpinski
StefanKarpinski Aug 28, 2014 Member

You really don't care for one-liners, do you?

@johnmyleswhite
johnmyleswhite Aug 28, 2014 Member

This doesn't fit on 80 characters, so I can't read it on my devserver. I'm not a fan of the one-liner syntax when it's split across multiple lines.

@StefanKarpinski
StefanKarpinski Aug 28, 2014 Member

Fair enough. I think that 80 lines chars is pretty short these days, but that's ok.

@johnmyleswhite
johnmyleswhite Aug 28, 2014 Member

The 80 char rule is alive and aggressively enforced at Facebook.

@ivarne
ivarne Aug 28, 2014 Contributor

80 char is great for having two columns on a 13 inch laptop. It is also great for viewing on a 7 inch tablet.

@StefanKarpinski
StefanKarpinski Aug 28, 2014 Member

52 char is great for having three columns on a 13 inch laptop.

@vtjnash
vtjnash Aug 30, 2014 Member

short lines are also necessary for when working on windows, since longer ones don't fit on the screen (console)

@vtjnash vtjnash and 2 others commented on an outdated diff Aug 30, 2014
base/nullable.jl
+ return isnull(x) ? Nullable{T}() : Nullable(convert(T, get(x)))
+end
+
+function show{T}(io::IO, x::Nullable{T})
+ if x.isnull
+ @printf(io, "Nullable(%s)", repr(T))
+ else
+ @printf(io, "Nullable(%s)", repr(x.value))
+ end
+end
+
+get(x::Nullable) = x.isnull ? throw(NullException()) : x.value
+
+get{S, T}(x::Nullable{S}, y::T) = x.isnull ? convert(S, y) : x.value
+
+unsafe_get(x::Nullable) = x.value
@vtjnash
vtjnash Aug 30, 2014 Member

should this be merged with unsafe_load?

@StefanKarpinski
StefanKarpinski Sep 2, 2014 Member

With a.b overloading, this could also just be x.value.

@johnmyleswhite
johnmyleswhite Sep 2, 2014 Member

Either of those suggestions seems fine with me. Merging this behavior into unsafe_load seems particularly good.

But do we all agree this function should exist?

@StefanKarpinski
StefanKarpinski Sep 2, 2014 Member

Not really. How about we leave it out for now? Ideally, people would just write this by checking for non-null and then doing the safe get but LLVM would eliminate the second check. I suspect this will work – LLVM is good at that kind of duplicate code elimination.

@johnmyleswhite
johnmyleswhite Sep 2, 2014 Member

Ok. Leaving it out in the first iteration seems better to me. I'd much prefer LLVM to handle removing a duplicate nullity check.

@vtjnash vtjnash commented on the diff Aug 30, 2014
base/nullable.jl
+
+Nullable{T}(value::T) = Nullable{T}(value)
+
+function convert{S, T}(::Type{Nullable{T}}, x::Nullable{S})
+ return isnull(x) ? Nullable{T}() : Nullable(convert(T, get(x)))
+end
+
+function show{T}(io::IO, x::Nullable{T})
+ if x.isnull
+ @printf(io, "Nullable(%s)", repr(T))
+ else
+ @printf(io, "Nullable(%s)", repr(x.value))
+ end
+end
+
+get(x::Nullable) = x.isnull ? throw(NullException()) : x.value
@vtjnash
vtjnash Aug 30, 2014 Member

I often like using the getindex syntax for this (x = Nullable(a); return x[])

@johnmyleswhite
johnmyleswhite Sep 19, 2014 Member

We could implement that, although it wouldn't handle the default case.

@joehuchette joehuchette referenced this pull request in JuliaOpt/JuMP.jl Sep 17, 2014
Closed

Why does `addelt!` include a 1e-50 perturbation #261

@johnmyleswhite
Member

Cycling back to this. Removed unsafe_get.

I believe the only remaining issue is the selection of constructors, particularly with regard to the ambiguities that @StefanKarpinski raised when writing Nullable(::DataType).

@johnmyleswhite
Member

It would seem the easiest solution to the ambiguities is to return to exposing Null and NotNull as separate functions, rather than merging them in Nullable.

@johnmyleswhite
Member

Fixed a typo in the docs.

@eschnett eschnett and 1 other commented on an outdated diff Sep 19, 2014
base/nullable.jl
+end
+
+immutable NullException <: Exception
+end
+
+Nullable{T}(::Type{T}) = Nullable{T}()
+
+Nullable{T}(value::T) = Nullable{T}(value)
+
+function convert{S, T}(::Type{Nullable{T}}, x::Nullable{S})
+ return isnull(x) ? Nullable{T}() : Nullable(convert(T, get(x)))
+end
+
+function show{T}(io::IO, x::Nullable{T})
+ if x.isnull
+ @printf(io, "Nullable(%s)", repr(T))
@eschnett
eschnett Sep 19, 2014 Contributor

May be better to output this as Nullable{T} (or Nullable{T}(), or Nullable()::T) instead of Nullable(T) to avoid confusion. Otherwise, it's not clear whether e.g. Nullable(Int) is null or not.

@johnmyleswhite
johnmyleswhite Sep 19, 2014 Member

That's a broader problem we should resolve. show should match the constructors we choose.

@eschnett
Contributor

On Thu, Sep 18, 2014 at 8:57 PM, John Myles White notifications@github.com
wrote:

It would seem the easiest solution to the ambiguities is to return to
exposing Null and NotNull as separate functions, rather than merging them
in Nullable.

If one wants to specify the type that should be constructed, then writing
Nullable{T}() and Nullable{T}(val) are fine. The case where the type should
be automatically inferred requires a wrapper, such as e.g. nullable(val).

-erik

Erik Schnetter schnetter@gmail.com
http://www.perimeterinstitute.ca/personal/eschnetter/

@johnmyleswhite
Member

My impression is that standard Julian style is to avoid writing out type parameters when calling constructors unless those type parameters are absolutely necessary. This might be a case where using type parameters explicitly is the best solution possible.

@JeffBezanson
Member

I really dislike that Nullable(x) might produce a null value or not depending on the type of x. I'd rather require Nullable{T}() to construct a null value with a certain type.

@johnmyleswhite
Member

And you're not interested in Null(T) vs NotNull(v::T)?

@JeffBezanson
Member

Null and NotNull are better than an unpredictable Nullable(x). Three names just seems like a lot when you can get by with one.

@johnmyleswhite
Member

Ok. Since I'm largely indifferent between Null/NotNull and explicit parameters to Nullable{T}, let's see what somebody else thinks.

@eschnett
Contributor

+1 for Nullable{T}() and Nullable(v::T).

@quinnj quinnj commented on the diff Sep 19, 2014
test/nullable.jl
+types = [
+ Bool,
+ Char,
+ Float16,
+ Float32,
+ Float64,
+ Int128,
+ Int16,
+ Int32,
+ Int64,
+ Int8,
+ Uint16,
+ Uint32,
+ Uint64,
+ Uint8,
+]
@quinnj
quinnj Sep 19, 2014 Member

No Uint128?

@johnmyleswhite
johnmyleswhite Sep 19, 2014 Member

I can add whatever you'd like that satisfies === and has zero/one.

@quinnj
quinnj Sep 19, 2014 Member

No worries, just didn't want ole' Uint128 to feel left out 😢

@quinnj
Member
quinnj commented Sep 19, 2014

I think it's going to be a point of confusion if Nullable(Int) returns a non-null Nullable of the Int datatype instead of a null Nullable of an Int value. Requiring Nullable{T}() is also problematic, mainly because explicitly stating type parameters seems to be avoided in most of Base (I think I only ever do it for certain Dict initializations).

With those points, I'm in favor of Null and NotNull, or perhaps just null and notnull, since they aren't types themselves.

What about nested nulls/non-nulls? Currently, you can do:

julia> NotNull(Null(Int))
NotNull(Null(Int64))

Does it really make sense to have a non-null with a null value? On the other hand, it seems like you should be able to do:

julia> Null(NotNull(1))
ERROR: `Null` has no method matching Null(::Nullable{Int64})

Perhaps we could add Null{T}(x::Nullable{T}) = isnull(x) ? x : Null(T)

@johnmyleswhite
Member

For simplicity, I think you should be able to nest things arbitrarily deeply.

I don't think you should be able to do Null(NotNull(1)) because that passes a value, not a type.

I'd rather use Null and NotNull than their lowercase equivalents to emphasize type construction.

@JeffBezanson
Member

@quinnj Don't be fooled by the fact that you can see that Int is a type in Nullable(Int). What does { Nullable(x) for x in { ... various values ... } } do? Would you want that to construct some null values and some non-null values, or simply wrap all the x's in Nullable no matter what they are?

@JeffBezanson
Member

I also think Nullable{T}() is perfectly analogous to Dict{T,S}(). They both basically make empty containers. They should match; if one is bad then the other is bad too, and we need a different convention for empty containers.

@johnmyleswhite
Member

I think that's a good argument for @eschnett's proposed solution.

@quinnj
Member
quinnj commented Sep 19, 2014

@JeffBezanson, note my proposal was Null{T}(x::Nullable{T}) = isnull(x) ? x : Null(T) with Null, not Nullable, meaning you would always get a null Nullable back when using Null, whether called on a null Nullable or non-null Nullable.

If we go with the Null and NotNull constructors, I don't see why Null{T}(x::T) = Nullable{T}() couldn't be had (in addition to the special case for Nullable above).

julia> NullableTypes.Null{T}(x::T) = Nullable{T}()
Null (generic function with 2 methods)

julia> Null(1)
Null(Int64)

julia> Null(Int)
Null(Int64)
@JeffBezanson
Member

You seem determined to introduce some f(x) whose behavior is subtly different based on whether x is a type. I simply don't see the advantage of this. Even if one considers it acceptable, I don't see how one can argue it is the simplest and least confusing option.

The danger here is that Nullable is extremely generic, parametrically polymorphic to the max: it makes equal sense for absolutely any value.

The function oftype of course has a similar sketchiness: oftype(1,1.0) and oftype(Int,1.0) both work. I don't love that either, but we can just barely get away with it because converting 1.0 to typeof(Int) doesn't make sense. However Null(x) easily makes sense for all x, so there is not much reason to sometimes take the type of x and other times not.

@quinnj
Member
quinnj commented Sep 19, 2014

No, that makes sense. Particularly the arguments for simplicity and the power of Nullable. I'd vote then to go with the Nullabe{T}() and Nullable(x::T) options. Looking forward to kicking the tires on this some more (for the ODBC and SQLite packages).

@kmsquire
Member

I also think Nullable{T}() is perfectly analogous to Dict{T,S}(). They
both basically make empty containers. They should match; if one is bad then
the other is bad too, and we need a different convention for empty
containers.

I had argued for this change before:
#4871 (comment)

I think it would be better to have a consistent convention for
creating typed containers (Arrays, Dicts, Sets, and the various containers
in DataStructures.jl). Currently, Dicts and Sets are special.

@JeffBezanson
Member

See also #3214. I dislike things like Container(T) more and more, since it's totally unclear which are type parameters and which are elements. The plan is for Array to remain the lone exception until #1470 is fixed.

@eschnett
Contributor

It's probably way too late in the discussion to bikeshed the name of the type... I don't like the name Nullable, as this implies that one can perform a certain action on the respective object. For example, Comparable would imply that == is defined, and Printable would indicate that the type can be output.

Nullable does not indicate such a property; an nval::Nullable{Int} is an immutable object, and there is no operation e.g. null(nval) that would modify nval. Also, the notion of null is tied to C and pointers, which is very different from the implementation here, which is more efficient.

Haskell calls this type Maybe (you have maybe an int, and maybe you have nothing) -- a cool name, but it takes a bit getting used to. Boost calls it Optional (you may have an int, or you may not) -- this is probably a good name that everybody immediately understands.

I like Optional. To check whether an optional value is present, one could call a function ispresent (instead of isnull).

@kmsquire
Member

+1 for consistency and Nullable{T}() then!

@JeffBezanson
Member

@eschnett I agree with everything you've said in this thread, including that it is too late to bikeshed the name :)

I'm actually not particularly attached to Nullable, and would be ok with Maybe or Optional or perhaps Opt if you're into the whole brevity thing. But as I said above Nullable is a well-established term of art that does not imply mutability. Interestingly, your examples Comparable and Printable also do not involve mutation. Nullable does in fact imply certain non-mutating methods, like isnull and get. Your argument actually supports the position that -able is not tied to mutation; it does not support your stated position.

@nalimilan
Contributor

@eschnett I think one of the points in favor of the Nullable term is that in SQL missing values are called NULL, and dealing with missing data is one of the big interests of this new type. Nullable is also called that way in C# and Java, though Option and Maybe seem to be equally popular, according to Wikipedia.

@eschnett
Contributor

The term "Nullable" indicates that there is some kind of operation that the object supports, namely "nulling" it. This does not really indicate that (a) there is a function isnull, or (b) there may not be a value present. That's what I meant when I spoke about modifying -- the term "nulling" sounds as if something could be modified, and that's not the case. I didn't mean to imply that the suffix "-able" indicates mutability, as you agree.

I guess we come from different programming language backgrounds. When I compare https://en.wikipedia.org/wiki/Nullable_type and https://en.wikipedia.org/wiki/Option_type, then I'd place Julia firmly in the latter category...

@JeffBezanson
Member

I read the Nullable type article, and nowhere does it mention an operation of "nulling" a value. The Option type article says "Outside of functional programming, these are known as nullable types." That seems to mean different kinds of programming have different names for the same thing, not that there are different kinds of option types (e.g. mutable vs immutable).

We agree that "X is nullable" does not imply that X supports some mutating operation. Why then would you say that "nullable" implies "nulling", which is a mutating operation? Maybe "nulling" means "constructing a similar value that is null". So I think the term is reasonable, making some allowance for the limitations of human language.

@johnmyleswhite
Member

FWIW, I think the argument about names is not likely to prove fruitful.

First off, the English suffix "-ble" does not precommit the earlier morpheme to any specific interpretation: compare livable, visible, defensible, potable, etc. Some of these involve transitive verbs, but some do not.

Second off, our type isn't identical to an Option or Maybe, since it's not a tagged union, but a distinct parametric type. This a somewhat minor point, but using a distinct name will help to keep the type theory folks from complaining about the use of terms that they perceive to have highly specialized meanings.

@JeffBezanson
Member

I agree the naming debate is not very fruitful, I was just starting to enjoy it :)

@JeffBezanson
Member

With the Nullable{T}() change I would like to merge this.

@johnmyleswhite
Member

Updated to use Nullable{T}(). Should be good to go now.

@johnmyleswhite
Member

And Travis gives us the green light.

@johnmyleswhite
Member

Bump.

@JeffBezanson JeffBezanson merged commit 7f47e6b into master Sep 20, 2014

1 check passed

continuous-integration/travis-ci The Travis CI build passed
Details
@IainNZ
Member
IainNZ commented Sep 20, 2014

🍰

@JeffBezanson JeffBezanson deleted the jmw/nullable branch Oct 25, 2014
@jkroso

I don't get it. Why would you define this to be an error?

Member

Because the test for being null already exists and is called isnull. Use that.

Contributor

That's the same case as the question whether adding two nullables should mean. There are several different possible answers (all self-consistent), and Julia decided to play it safe and not make a choice. Likewise, comparing nullables is currently not defined.

The basic question is whether comparing nullables should return Bool or Nullable{Bool}.

There's probably space for two different nullable types. Maybe we should introduce a type similar to Haskell's Maybe to Julia (with similar semantics), and make Nullable more comfortable to use.

Ok so would it cause problems if == was implemented in a similar way to isequal?

Member

Given that I wanted == not to be defined, it would certainly harm that goal. Eric's comment above really hits the nail on the head: there is no uniformly correct behavior for this function. Very different interpretations of what a Nullable ought to be are possible; we tried to provide the minimal core that all interpretations share. Once you define == to match isequal, you've removed our ability to support the other interpretations.

Oh @eschnett's answer hadn't shown up when I replied. I guess it's a good idea to experiment with monads a bit before defining Nullable fully.

Contributor

The monadic interpretation of "nullable" (i.e. Haskell's Maybe) is one way to define them; one other corresponds to nan in float-point arithmetic, and a third one to NA in data frames.

What monads give you is a definition of e.g. "nullable plus nullable" that is non-null if and only if all arguments are non-null, as well as an unambiguous way to collapse Nullable{Nullable{T}} to Nullable{T}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment