hyrule
hyrule copied to clipboard
Add (get ...) with default value
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"
My suggestion on IRC is to move get
as get-in
and implement get
with default value as third parameter.
I like this suggestion. PR coming up in a bit.
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.
So, unless someone knows a way how to do this in a sane way, I'd say (.get)
ain't that bad.
Based on the problem explained above, I'm closing this, because there's no way to properly implement it.
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.
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.
Okay, I think those could be reasonable compromises.
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.
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
?