-- Leo Tolstoy, Anna Karenina
Part 6, Chapter 25
Problem Statement
What does it mean for something to be undefined? This question sounds like a kōan:
If you meet the Buddha, kill him.
-- Linji Yixuan
Great Master Ba was seriously ill. The chief priest of the temple asked him,
"Master, how are you feeling these days?"
Great Master said,
"Sun-face Buddha, Moon-face Buddha"
-- The Blue Cliff Record
Case 3: Master Ba Is Ill
(Note, the "Sun-face Buddha" is said to have a life of 1800 years, the "Moon-face Buddha" lives 24 hours)
Functional Point-of-view
In programming having a function return undefined is a bit like saying, "I did not think that this would happen, but here we are." In college I was both a Mathematics and Computer Science major, I remember the first time I came across undefined it was in Linear Algebra with Dr. Beachy, I was stuck on a homework problem, flipping ahead in the textbook I notice a an example that was basically the same problem that I was stuck on. I talked to Dr. Beachy about the problem and how I planned on solving it, he said that could be a great way to solve the problem but that the property I was going to use was not defined to work with the problem I had. This was the first time in my life that I came across undefined as output of applying something.
In programming terms, undefined is what you would get if you have functional input contract which is broken. I believe I hear an example would be nice, so here it is.
DEFINE foo = FUNCTION(x)
RETURN 8 / x
when x does not equal 0
If we call foo with 1 we would get 8. Likewise if we call foo with 2 we would get 4. What should happen if we call foo with 0? Should we even be allowed to call foo with 0?
If we are using a language or framework that supports programming-by-contract then the answer to what should happen if we call foo with 0 is easy, the call would fail. What would happen if we call foo in a language that does not support programming-by-contract?
If we are not using a language of framework that supports programming-by-contract we have two options:
- set up guards in the function which filter out unsupported input (returning null, IllegalArgumentException, or whatever makes sense in the language we are using)
- have the language return some type of unknown value
Option 1 requires us to think about what to allow and not allow ahead of time.
Option 2 is interesting and is the point of this post.
JavaScript and undefined
JavaScript is very interesting since it includes undefined as part of its language.
> undefined
undefined
I know so what, any call without a return value in the Node.js REPL will return undefined. Yes, but what is that actually telling us?
> 2 + 2;
4
> console.log('2 + 2 = ' + (2 + 2));
2 + 2 = 4
undefined
We see that the call to console.log sends the string to the console be has the return value of undefined, interesting. Let's play around if return values from functions and see if we can find anything of interest.
> (function(){return 2;})();
2
> (function(){return 'Hello World';})();
'Hello World'
> (function(){return 2 + 2;})();
4
> (function(x){return x;})(2);
2
Nothing interesting yet, how about ...
> (function(){})();
undefined
Now that is a bit interesting. What happen there? We do not have a return value so JavaScript has given us one of undefined. Let's play with this a little more.
> (function(x){if (x % 3 === 0) return "Fizz"})(9);
'Fizz'
> (function(x){if (x % 3 === 0) return "Fizz"})(1);
undefined
> (function(x){if (x % 3 === 0) return "Fizz"})("Mike Harris");
undefined
(I did not retype that but instead use the up arrow, don't judge me.)
Hmmm, we see that if we have undefined pathways through are code we have the value of undefined returned. Looking back at our foo example from before, JavaScript would give us the following results.
> var foo = function(x){return 8 / x;};
undefined
> foo(1);
8
> foo(2);
4
> foo(0);
Infinity
> foo();
NaN
> foo('Mr. Jack');
NaN
> foo('');
Infinity
> foo(Infinity);
0
> foo(NaN);
NaN
> foo([]);
Infinity
> foo((function(){}));
NaN
We see that JavaScript returns what is mathematically an acceptable answer for the input of 0, but we get some very interesting output for values that are treated the same way as 0 in JavaScript (like the empty string and empty array). The value for the input of Infinity are interesting. Some people would say 8/infinity = 0 is correct or close enough other would say 8/infinity = 0 is just plain wrong.
One last thing to check, does undefined equal undefined in JavaScript?
> undefined === undefined
true
> ((function(){})()) === undefined
true
> ((function(){})()) === ((function(){})())
true
> ((function(){})()) === ((function(){})('something'))
true
> ((function(){2})()) === ((function(){})())
true
> ((function(){})()) === void 0
true
> ((function(x){if (x === 2) return x;})(3)) === ((function(){})())
true
It seems that JavaScript considers undefined to be equal to undefined.
F# and undefined
F# is a strongly typed language, let's see what happens with our foo example from before.
> let foo x = 8 / x;;
foo 1;; // 8
foo 2;; // 4
foo 0;;
val foo : x:int -> int
> val it : int = 8
> val it : int = 4
> System.DivideByZeroException: [Arg_DivideByZero]
Arguments:
Debugging resource strings are unavailable. Often the key and arguments provide sufficient information to diagnose the problem. See http://go.microsoft.com/fwlink/?linkid=106663&Version=5.1.20913.00&File=mscorlib.dll&Key=Arg_DivideByZero
at .$FSI_0004.main@()
at main@dm()
Stopped due to error
Nice we get a DivideByZeroExpection for calling foo with 0.
How about our conditional return statement lambda that only handles numbers divisible by 3?
> (fun x -> if x % 3 = 0 then "Fizz") 2;;
(fun x -> if x % 3 = 0 then "Fizz") 3;
(fun x -> if x % 3 = 0 then "Fizz") 2;;
----------------------------^^^^^^
stdin(13,29): error FS0001: This expression was expected to have type
unit
but here has type
string
>
(fun x -> if x % 3 = 0 then "Fizz") 3;
----------------------------^^^^^^
stdin(14,29): error FS0001: This expression was expected to have type
unit
but here has type
string
>
That did not even compile right and makes sense based on what the MSDN documentation says about conditional expressions in F#. "The types of the values produced in each branch must match. If there is no explicit else branch, its type is unit."
How about pattern matching?
> (fun x -> match x with _ when x % 3 = 0 -> "Fizz") 2;;
(fun x -> match x with _ when x % 3 = 0 -> "Fizz") 3;;
(fun x -> match x with _ when x % 3 = 0 -> "Fizz") 2;;
-----------------^
stdin(16,18): warning FS0025: Incomplete pattern matches on this expression.
Microsoft.FSharp.Core.MatchFailureException: The match cases were incomplete
at .$FSI_0011.main@()
at main@dm()
Stopped due to error
>
(fun x -> match x with _ when x % 3 = 0 -> "Fizz") 3;;
-----------------^
stdin(17,18): warning FS0025: Incomplete pattern matches on this expression.
val it : string = "Fizz"
> >
Well that did not work either, it seems that the pattern matching must cover all paths.
It seems that an undefined value is not possible in F# unless you make one up.
Pseudo-Conclusion
With undefined what we are saying is here there be dragons.