11

While creating a client for a web API in C#, I ran into a problem regarding null as a value where it would represent two different things:

  • nothing, e.g. a foo may or may not have a bar
  • unknown: by default the API response only includes a subset of properties, you have to indicate which additional properties you want. So unknown means that the property was not requested from the API.

After some searching I found out about the Maybe (or Option) type, how it's used in functional languages and how it "solves" null dereferencing problems by forcing the user to think about the possible absence of a value. However, all of the resources I encountered talked about replacing null with Maybe. I did find some mentions of three-valued logic, but I don't fully understand it and most of the times its mentioning was in the context of "it's a bad thing".

I'm now wondering if it makes sense to have both the concept of null and Maybe, to represent unknown and nothing respectively. Is this the three-valued logic I read about, or does it have another name? Or is the intended way to nest a Maybe in a Maybe?

Stijn
  • 213
  • 1
  • 8

4 Answers4

14

The null value as a default present everywhere is just a really broken idea, so forget about that.

You should always have exactly the concept that best describes your actual data. If you need a type which indicates "unkown", "nothing", and "value", you should have precisely that. But if it does not fit your actual needs then you should not do it. It is a good idea to see what other people use and what they have proposed, but you do not have to follow them blindly.

People who design standard libraries try to guess common usage patterns, and they usually do it well, but if there's a specific thing you need, you should define it yourself. For instance, in Haskell, you could define:

data UnknownNothing a =
     Unknown
   | Nothing
   | Value a

It may even be the case that you should be using something more descriptive that is easier to remember:

data UserInput a =
     Unknown
   | NotGiven
   | Answer a

You could define 10 such types to be used for different scenarios. The drawback is that you won't have available pre-existing library functions (such as those of Maybe), but this usually turns out to be a detail. It's not that hard to add your own functionality.

Gilles 'SO- stop being evil'
  • 44,159
  • 8
  • 120
  • 184
Andrej Bauer
  • 31,657
  • 1
  • 75
  • 121
4

As far as I know, the value null in C# is a possible value for some variables, depending on its type (am I right?). For example, instances of some class. For the rest of the types (like int, bool, etc) you can add this exception value by declaring variables with int? or bool? instead (this is exactly what the Maybe constructor does, as I will describe next).

The Maybe type constructor from functional programming adds this new exception value for a given datatype. So if Int is the type of integers or Game is the type of the state of a game, then Maybe Int has all the integers plus a null (sometimes called nothing) value. The same for Maybe Game. Here, there are no types that come with the null value. You add it when you need it.

IMO, this last approach is the better one for any programming language.

Euge
  • 630
  • 1
  • 4
  • 8
3

If you can define type unions, like X or Y, and if you have a type named Null which represents only the null value (and nothing else), then for any type T, T or Null is actually not so different from Maybe T. The only problem with null is when the language treats a type T as T or Null implicitly, making null a valid value for every type (except primitive types in languages that have them). This is what happens for example in Java where a function which takes a String also accepts null.

If you treat types as set of values and or as union of sets, then (T or Null) or Null represents the set of values T ∪ {null} ∪ {null}, which is the same as T or Null. There are compilers which perform this kind of analyses (SBCL). As said in comments, you cannot easily distinguish between Maybe T and Maybe Maybe T when you view types as domains (on the other hand, that view is quite useful for dynamically typed programs). But you could also keep types as algebraic expressions, where (T or Null) or Null represents multiple levels of Maybes.

coredump
  • 133
  • 4
1

You need to find out what you wish to express, and then find a way how to express it.

First, you have values in your problem domain (like numbers, strings, customer records, booleans, etc). Second, you have some additional, generic values on top of your problem domain values: For example "nothing" (the known absence of a value), "unknown" (no knowledge about presence or absence of a value), not mentioned was "something" (there is definitely a value, but we don't know which one). Maybe you can think of other situations.

If you wanted to be able to represent all the values plus these three additional values, you would create an enum with cases "nothing", "unknown", "something", and "value" and go from there.

In some languages some cases are simpler. For example, in Swift you have for every type T the type "optional T" which has as its possible values nil plus all the values of T. This lets you handle many situations easily, and is perfect if you have "nothing" and "value". You could use it if you have "unknown" and "value". You could not use it if you need to handle "nothing", "unknown" and "value". Other languages may use "null" or "maybe", but that's just another word.

In JSON, you have dictionaries with key/value pairs. Here, a key may just be missing from a dictionary, or it may have a value of null. So by carefully describing how things are stored, you can represent generic values plus two additional values.

gnasher729
  • 32,238
  • 36
  • 56