
Option and Null in Dynamic Languages - rntz
http://www.rntz.net/post/2014-07-02-option-in-dynlangs.html
======
lispm
Common Lisp can return multiple values from functions. Thus it returns two
values from a hash-table lookup:

    
    
        CL-USER 45 > (gethash 'foo (make-hash-table))
        NIL
        NIL
    

The first value is the stored value in the hash table. If there is no key, the
first value is NIL. The second value indicates whether the key is present in
the hash table.

~~~
rntz
I list Common Lisp & its behavior in Appendix C at the end of the article.
Multiple-return-value is definitely better than just returning NIL, but I
still dislike it; it doesn't actively prevent you from confusing NIL with
absence, so "the obvious way to do it" can still be the wrong way. A good CL
programmer won't fall into this trap, but the article is in large part about
how some languages/interfaces _default_ you into good programming by requiring
you to distinguish success from failure, and others don't.

Another way of seeing the same thing is: Type-theoretically speaking, MRV is
using a product type (two values) to emulate a sum type (one value of two
possible forms); why not just use a sum type?

~~~
tel
The optimistic type theoretician says that since dependent products are sums
then it's perfectly fine to use a product to emulate a sum... except since the
dependence in the projections isn't reflected in the type system it's totally
up to the programmer to ensure that the providence is maintained.

------
ataggart
Correctness can often arise naturally when the implementation reflects the
function's intent by composing "correct" functions. E.g.,

    
    
      (defn values-in [m ks]
        (vals (select-keys m ks)))

~~~
dkersten
Furthermore, I've found that absent keys degrading to _nil_ is actually a very
useful feature that makes a lot of code really composable and simple. I can't
think of any Clojure code I've written recently where a value of _nil_
actually should be distinguished from an absent value - I just write my code
so that semantically they mean the same thing, because this makes the code
much easier to compose and much easier to think about.

------
typomatic
A more sane* way to implement the single lookup behavior is:

    
    
      def values_in(keys, d):
          ret = {}
          for key in keys:
              try:
                  ret[key] = d[key]
              except KeyError:
                  pass
          return ret
    

which works much more quickly in cases where keys is nearly all of d.keys, and
doesn't force you to reinvent monads in Python, which is arguably unsuited for
them.

* Sanity values may vary for people who haven't gotten into the habit of using Python exceptions for little things.

~~~
dragonwriter
The article is looking at returning a list, while your option returns a dict,
but that's an easy fix. And even for an Option-like approach, the article
tries way too hard: Option can be viewed as a constrained cardinality list,
and regular lists work just fine to implement them.

    
    
      from itertools import chain
      
      def getOpt(d, k):
          return [d[key]] if key in d else []
    
      def values_in(keys, d):
          return list(chain(*[ getOpt(d,key) 
                               for key in keys ]))
    

Or Ruby:

    
    
      def values_in(keys, d)
        keys.flat_map {|k| d.has_key?(k) ? [d[k]] : []}
      end

~~~
rntz
Author of the article here. The Python solution using lists-as-options is
actually even simpler:

    
    
        def values_in(keys, d):
            return [x for key in keys for x in getOpt(d,key)]
    

Using lists as options is a neat trick I hadn't thought of when writing the
article. Unfortunately, no dynamic language uses this idiom (or really any
other non-exception option-like patterns) natively.

~~~
chriswarbo
Actually, at least in PHP, I find myself using arrays and other iterables as
pseudo-Options quite a lot; eg. when checking the existence of a DB value,
xpath element, etc.

    
    
        // Using if/then/else
        function foo($x) {
          $results = look_up_something($x);
          if (count($results) > 0) {
            $val = $results[0];
            return do_something($val);
          }
        }
    
        // Same as above, but with a foreach loop
        function foo($x) {
          foreach (look_up_something($x) as $val) {
            return do_something($val);
          }
        }
    

Of course, the nice thing about Options (other than being able to distinguish
between "None", "Some None", "Some (Some None)", etc.) is the availability of
combinators to lift and compose non-Option functions with optional functions.
Sometimes we can map and fold ("reduce"), but sometimes we can't (eg. PHP's
"array_map" doesn't work on iterable objects, which many APIs use in lieu of
arrays).

------
tieTYT
> In Lua, it's impossible to have a dictionary that maps a key to nil: setting
> a key to nil removes it from the dictionary!

Wait is he really trying to say, "Setting a VALUE to nil removes the entry
from the dictionary!"? If so, that sounds terrible.

I can imagine barking up the wrong tree wondering why my map is missing fields
when the real problem is an object I passed into it is unexpectedly nil.

~~~
russellsprouts
Yes, that's true. It does have drawbacks, but overall I think it is better
than say, JavaScript. In JavaScript, a key can be not present in an object, be
undefined, be null, be set to some other value, or be present in the
prototype. The difference between these can cause very subtle bugs.

    
    
            //Let's define some object
            var obj = {a: true, b: true, c: true, d: true, hasOwnProperty: true}
    
            //Now let's make them falsy in different ways
            obj.a = undefined; //set to undefined
            obj.b = null; //set to null
            obj.c = false; //set to false
            delete obj.d; //delete key
            delete obj.hasOwnProperty //delete key, but present in prototype
    
            console.log(obj.a) //--> undefined
            console.log(obj.b) //--> null
            console.log(obj.c) //--> false
            console.log(obj.d) //--> undefined
            console.log(obj.hasOwnProperty) //--> [Function]
    
            console.log('a' in obj) //--> true
            console.log('b' in obj) //--> true
            console.log('c' in obj) //--> true
            console.log('d' in obj) //--> false
            console.log('hasOwnProperty' in obj) //--> true
    
            console.log(obj.a == null) //--> true
            console.log(obj.b == null) //--> true
            console.log(obj.c == null) //--> false
            console.log(obj.d == null) //--> true
            console.log(obj.hasOwnProperty == null) //--> false 
    
            for(var key in obj) {
              console.log(key) //--> prints a, b, c
            }
    

In Lua, the only data structure is a hashtable, which maps any non-nil value
to any other non-nil value. It can also be set in the objects __index
metamethod/table

    
    
            local tab = {a = true, b = true, c = true}
            
            -- by default there are none, but we can defined a fallback metatable similar to a prototype method like js's obj.prototype.hasOwnProperty
    
            setmetatable(tab, {__index={c='meta c', d='meta d'}})
    
            -- if it is important that a key is explicitly set to false, do so, otherwise simply remove it
            tab.a = false --set to false
            tab.b = nil --set to nil
            tab.c = nil --set to nil, present in fallback table
            tab.d = false --set to false, present in fallback table
    
            print(tab.a) --false
            print(tab.b) --nil
            print(tab.c) --meta c
            print(tab.d) --false
    

The rules for Lua are simple: Look for the key in the table, if it's not
there, check the fallback defined in the metatable, if not, then return nil.

This is especially important in Lua, because any value can be a key, you would
have to be sure to delete the key otherwise it could not be garbage collected.
local tab1 = {1,2,3,4,5} local tab2 = {} tab2[tab1] = true for k,v in
pairs(tab2) do tab[k] = nil end

If this worked like JavaScript, then tab2[tab1] would return nil, but tab1
could not be garbage collected until tab2 is. Because JavaScript objects can
only have strings for keys, this is less of an issue.

------
th3iedkid
Java 8 does more type-system work to help with the same unlike its previous
versions.

[http://www.oracle.com/technetwork/articles/java/java8-option...](http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html)

~~~
TheCoelacanth
Sadly, until they get around to adding value types (maybe Java 9?) that just
adds another layer that can be null.

~~~
anon4
A null Optional sounds like a cross between Murphy's law and a Zen riddle. You
achieve enlightenment, but wish you hadn't.

------
wfjackson
Null, empty string, empty value, Undefined etc. are the biggest causes of
crashes,edge case bugs and a big time sink programming and data handling. Wish
languages had better support to handle straightforward cases instead of just
crashing.

~~~
taeric
This is close to saying that lack of oxygen is the number one killer in
drownings. That is, when you are in that situation there is a lot going wrong.

So, sure, you got rid of null from the language. Does your program handle the
case where it was handed a negative number correctly? All positive values? All
unicode?

Consider, in the Ariane Rocket incident, they were using a dependently typed
language. Didn't magically prevent errors in assembly.

So, fine, we'll make our programs where they handle all cases... Rapid
prototyping probably just flew out the window. As did worrying about actual
honest to goodness use cases.

A great quote by Knuth I saw once was where he acknowledged that tex would
choke on mile wide documents. But who cares?

~~~
tel
As far as I know Ada doesn't have dependent types.

~~~
taeric
Apologies, I had thought it was. (This goes to everyone that corrected me on
this.)

I can only guess that I got confused over reading about Agda at some point.
Again, apologies.

