Hacker News new | past | comments | ask | show | jobs | submit login

The problem is that ‘get’ possibly creates one nil result. Using the map as a function, even if nil is understood to be an error, now gives you two possible and very different errors from one expression. It’s a bad idea and generates added ambiguity and complexity, which further strays from the article’s philosophy.



There is no ambiguity to using map as a function. It raises an error if the map is nil, and returns a nil if the key is not found.

Using get is ambiguous, getting a nil result may mean that the key was not found or that there was no map in the first place.


Yeah, Common Lisp’s gethash is better than get in most other languages because of Common Lisp’s support for multiple return values.

    (gethash key hash-table)
Returns two values: the first one is the value found in the hash table, or nil if no value was found. The second one is nil (i.e. false) if no value was found and non-nil (maybe t? I forget what the standard says) if a value was found.


It's the style used by Go and certainly better than Java/Ruby/JS (which just return null/nil/undefined on a missing key, same as clojure).

My personal ranking would be along the lines of:

1. Option types, that only makes sense for statically typed languages but it's the most clear and helpful and all other styles can trivially be composed from that.

2. Erlang-style, that's less the API which is similar to (though less forgiving/error-prone than) CL/Go and more the language: find/2 returns `{ok, Value} | error`, so to actually get the value you must either assert that the retrieval succeeded by pattern-matching on `{ok, Value}` directly (faulting if that didn't work) or properly pattern-match on both cases, and it doesn't give the illusion of a value on failure. Erlang also provides (3) and (5) via get/2 and get/3.

3. Python-style, the default is to raise on missing keys which makes it unmissable that the value was not there, alternatively provides a variant of (5) which can fairly easily be used as a (4) via the get method (return a placeholder value which can't have existed in the map, check against it by identity).

4. Common Lisp & Go, both will implicitly ignore the presence flag by default (bind result to single value) and just return a default on miss in that case. Common Lisp has an edge because Go's statically typed so it could make better use of MRV, and CL has much better abilities to manipulate and reify MRVs (only thing you can do in Go is bind them).

5. Clojure, Ruby and (finally) Java >= 8: a missing key returns a valid default[0], but it's also possible to customise that default (oddly enough in Ruby the same method which lets you get a non-hash-global default also lets you opt into (3), but I don't think it's used much?). For Clojure and Ruby this lets you emulate a (4) the same way Python does.

6. Java < 8/JS: a missing key returns a valid default (null/undefined) and there's no way to differentiate "there was no value" and "this was a value" save by a second access (in/contains).

[0] as in the default they return could have been a valid value all along


I really like Common Lisp's solution because it doesn't force boilerplate on you. If you care whether or not the key was present, the second return value is there and, otherwise, you just continue on your way.

Also, since MRVs are a language construct, you can abstract over them to implement the equivalent of Haskell's >>= / maybe or to throw on a missing key. So, I'm not really sure there's a decisive advantage of Options vs. the MRV solution. In fact, if you change your perspective a bit, Options (and possibly sum types in general) are just ways of getting multiple return values in languages that only support single return values: i.e. you could think of the case of the sum type (i.e. "Some" or "None" being one of the return values and the wrapped value(s) being another.


> I really like Common Lisp's solution because it doesn't force boilerplate on you.

Checking what needs to be checked is not boilerplate.

> If you care whether or not the key was present, the second return value is there and, otherwise, you just continue on your way.

Which is the entire issue. By default, if you're not careful or don't check, you just get garbage without warning.

> Also, since MRVs are a language construct, you can abstract over them

No, MRV being a language construct does not allow that at all (you can't do any such thing in Go because MRVs are language structures rather than properly reified). You can abstract over MRVs in CL despite them being MRVs, because CL specifically provides a bunch of dedicated constructs for working with MRVs.

> So, I'm not really sure there's a decisive advantage of Options vs. the MRV solution.

That options force the developer to deal with the issue and do so by default.

> In fact, if you change your perspective a bit, Options (and possibly sum types in general) are just ways of getting multiple return values in languages that only support single return values

Utter nonsense, MRVs are products. The entire point of sum types is that they're not, their purpose is to provide alternatives in a type-safe manner. The way to get multiple return values in "languages that only support single return values" is tuples.

> you could think of the case of the sum type (i.e. "Some" or "None" being one of the return values and the wrapped value(s) being another.

You really could not, that's the entire point.


> No, MRV being a language construct does not allow that at all (you can't do any such thing in Go because MRVs are language structures rather than properly reified).

Oops, I meant "because MRVs are a first-class language construct in CL (if only because of macros), you can abstract over them ..."

Secondly, tagged unions are a very standard way of simulating sum types in languages that lack them. All I was saying is that MRVs can be used in a similar way. E.g., an option can be replaced by a pair of values, one of which indicates whether or not the value is present and the other indicates what the value was.

As far as the boilerplate issue goes, if you really need to make sure that a field is present, you should be using hash tables in CL, you use a class and when you try to access an unbound slot, it throws.

Finally, it's just wrong to say that Options are only useful in statically typed languages: any _strongly_ typed language can use them effectively, whether or not those types are checked at run time or compile time.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: