9 ms·
Option and Null in Dynamic Languages
- th3iedkid 12y agoJava 8 does more type-system work to help with the same unlike its previous versions. http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html http://www.oracle.com/technetwork/articles/java/java8-option...
- TheCoelacanth 12y agoSadly, until they get around to adding value types (maybe Java 9?) that just adds another layer that can be null.
- anon4 12y agoA null Optional sounds like a cross between Murphy's law and a Zen riddle. You achieve enlightenment, but wish you hadn't.
- typomatic 12y agoA 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 12y agoThe 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 12y agoAuthor 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.
- dragonwriter 12y ago> The Python solution using lists-as-options is actually even simpler: Good point. For some reason, I have this giant python blind spot for comprehensions where the sources aren't orthogonal -- I miss them in the first pass all the time; I normally notice it fairly quickly in code I'm not posting on a discussion forum... > Unfortunately, no dynamic language uses this idiom (or really any other non-exception option-like patterns) natively. Yeah, to use it in your own code in most popular dynamic languages, you have to wrap library code that works differently. The good thing is that most popular dynamic libraries have good (largely functionally-inspired) tools for working with lists and other iterables that the payoff is pretty good. (Of course, with duck typing in most popular dynamic languages, one thing you could do to be more explicit is to combine the Option-as-class and Option-as-list approach, and make Option a class that implemented the interface used by immutable collections.)
- chriswarbo 12y agoActually, 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).
- anon4 12y agoAs long as we're playing code golf, try this for python: def values_in(dict, keys): marker = object() return [x for x in (dict.get(k, marker) for k in keys) if x is not marker]
- dragonwriter 12y agoFor this case and similar cases where the API provides a "supply your own default value for the failure case" option, this kind of "unique one-time null marker" is actually a very good approach that addresses much of the problem with standard null values. Its not as composable as Option- or list-based approaches, though. (OTOH, you can use it to implement those approaches, and its cleaner for that than the way I did that elsewhere in the thread.)
- wfjackson 12y agoNull, 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.
- wvenable 12y agoCrashing is ok. The problem is that it usually doesn't crash in the right spot. A null is generated somewhere else and propagated around for a while and the crash happens later in a unrelated area. But it's even worse if it doesn't crash at all.
- taeric 12y agoThis 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?
- Dewie 12y ago> 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? Aren't you just moving the goal post? You can play the 'OK so you can eliminate that from the equation, but can you ...' indefinitely. Well, perhaps only until you get to the question of cosmic rays. > 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. The topic is a few special cases, like null, not every conceivable case. If things like null and undefined cause the majority of crashes, and they are simple to handle with a 'proper' language (and they are), then you already have gotten a lot of bang for your buck. For rapid prototyping: if handling null cases is too much, use a function that unwraps nullable values directly, and if they are null, crashes (and no, this is not the same as a null pointer exception). Then go back and clean up the program when you are not prototyping quite as rapidly anymore.
- lispm 12y agoCommon 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 12y agoI 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 12y agoThe 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.
- derefr 12y agoAnd in Erlang: 1> X = #{"a" => 1}. #{"a" => 1} 2> maps:find("a", X). {ok,1} 3> maps:find("x", X). error Constructing and destructuring tuples is cheap in the Erlang VM, so Erlang uses them where other languages would use types. Further, in idiomatic Erlang, you don’t bother with a branch for each case of the option type; you just do the following: {ok, Val} = maps:find("a", X). Effectively, that's a type assertion: if maps:find/1 responds with error instead of {ok, _}, that’s a process crash.
- tieTYT 12y ago> 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.
- rntz 12y agoYes, that is what I meant. And yeah, it is weird behavior. (I think of "d[k] = v" as "setting the key k to the value v", so that's why I said "setting a key". Perhaps "binding a key" would be clearer?)
- deleted 12y ago[deleted]
- russellsprouts 12y agoYes, 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.
- ataggart 12y agoCorrectness 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 12y agoFurthermore, 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.
- deleted 12y ago[deleted]