hyrule icon indicating copy to clipboard operation
hyrule copied to clipboard

Add (get ...) with default value

Open dakrone opened this issue 9 years ago • 10 comments

Coming from Clojure, I was expecting this to work:

(get {"foo": "bar"} "baz" "eggplant") ;; => "eggplant" in Clojure, KeyError in Hy

However, instead I get KeyErrors. I have come to find out that (get ...) is closer to Clojure's (get-in ...).

It would be great if there was a "get-with-default" equivalent, otherwise I have to fall back to Python's implementation of get:

(.get {"foo": "bar"} "baz" "eggplant") ;; => "eggplant"

dakrone avatar Aug 20 '15 22:08 dakrone

My suggestion on IRC is to move get as get-in and implement get with default value as third parameter.

Foxboron avatar Aug 20 '15 22:08 Foxboron

I like this suggestion. PR coming up in a bit.

algernon avatar Aug 27 '15 12:08 algernon

There's a slight problem here: Hy's (get) works on everything that supports the python subscript stuff, be that arrays, dicts, and whatnot. Trying to subscript a key or index that doesn't exist, raise different exceptions. So to make (get) truly generic, I'd have to catch all exceptions. That does not sound like something we'd want.

algernon avatar Aug 27 '15 12:08 algernon

So, unless someone knows a way how to do this in a sane way, I'd say (.get) ain't that bad.

algernon avatar Aug 27 '15 12:08 algernon

Based on the problem explained above, I'm closing this, because there's no way to properly implement it.

algernon avatar Sep 22 '15 09:09 algernon

A bit late to the party, but I too would like to see this feature in the standard library.

Hy's (get) works on everything that supports the python subscript stuff, be that arrays, dicts, and whatnot.

So you're implying that if a user expects (get) to work with "whatnot", then they will also expect get-with-default to work with it also?

Trying to subscript a key or index that doesn't exist, raise different exceptions.

If we rely on exceptions, then yes, we have a problem with "whatnot", since we don't know what could it raise.

So to make (get) truly generic, I'd have to catch all exceptions.

Well, there are other options.

Instead of relying on exceptions, we could check keys of a dict or length of a list. There's still a problem with "whatever", but at least we won't need to catch all exceptions.

Instead of making a truly generic get-with-default, maybe it will be enough to only support dict- and list-like collections? Then, if a container raises another exception, we will not catch it. Or catch it and raise another exception, stating that container is not supported. Or, if we don't rely on exceptions, but can't check the length or the keys of a container, also raise an "unsupported" exception.

Instead of putting this feature inside the (get), maybe it will better to create something separate like (get-default)? That will not break the existing API and the user will be aware that this function may behave differently.

MelomanCool avatar Apr 13 '18 09:04 MelomanCool

I would also like to point out that Clojure's get, which OP mentioned, doesn't rely on exceptions, but checks map's keys and length of an array or a string. And for unsupported/null collections it simply returns the default value.

MelomanCool avatar Apr 13 '18 10:04 MelomanCool

Okay, I think those could be reasonable compromises.

Kodiologist avatar Apr 13 '18 14:04 Kodiologist

That will not break the existing API

I'm not too worried about that at the moment. We've been breaking API every release, and have more breaking changes planned.

That said, Hy's get is not the same as Clojure's get-in, which puts the keys list in brackets and also takes a default afterwards. Rather, it's the special form that corresponds to Python's [] (subscript) operator. Like + (and many other Python binary operators) it's been adjusted to be variable-arity to better work in a Lisp. Note that Python's operator module calls this getitem/setitem/delitem. So perhaps item would be a better name for it.

Clojure's get, which OP mentioned, doesn't rely on exceptions, but checks map's keys and length of an array or a string.

Exactly how would you compile that? Catching the exception might actually be easier. Both IndexError and KeyError are instances of LookupError, so we could catch them both. But I'm worried we might catch an exception we shouldn't.

gilch avatar Apr 14 '18 00:04 gilch

Thinking about this some more, we could probably write a macro to do this, and avoid catching extra exceptions with gensyms.

(defmacro/g! get [coll key &optional default]
  `(do
     ;; eval coll & key outside try
     (setv ~g!coll ~coll
           ~g!key ~key)
     (try
       (. ~g!coll[~g!key])
       (except [LookupError]
         ~default))))

But this doesn't really seem better than a simple function. The only advantage is that it doesn't have to evaluate the default expression.

def get(coll, key, default=None):
    try:
        return coll[key]
    except LookupError:
        return default

But by then why not use .get?

gilch avatar Apr 14 '18 04:04 gilch