5 ms·
Maybe I am missing something. Here's how I might write something equivalent in Python: for f in GetFromCache, GetFromDatabase, GetFromAPI, Compute: try:
by atdt 5y ago
Maybe I am missing something. Here's how I might write something equivalent in Python:
for f in GetFromCache, GetFromDatabase, GetFromAPI, Compute:
try:
return f()
except Exception as ex:
log(ex)
return default_value
(You'd probably define a custom exception class for your application that represents the kinds of errors you can tolerate, so you don't end up swallowing programming errors, but you get the idea.)
- roenxi 5y agoMmm. You've given a code snippet while Zak had a single macro call. Let's make your code into a function with signature: def atdt_exception_suppression(*list_of_fns): ... What you've done is leverage the fact that functions are first class objects in Python (ie, can be passed as arguments) to achieve the same result as a macro. Which is cool, it works. Probably slightly better than a macro for this use case because we get a neat function boundary. But macros are a powerful option because it doesn't matter if functions are first class or not. If you are using a language where functions aren't first class objects, a macro system would let Zak implement anyway. So you are correctly identifying that Python can implement this in an alternate way as a function, but a macro system is sufficient to do this and a whole batch of other tricks even if first class functions aren't available as a feature.
- cnasc 5y agoYou’re running through a list of functions, the lisp example has a list of _function calls_ (which are not evaluated until necessary). Consider how your approach would have to change if each of those functions takes different parameters.
- lamontcg 5y agoIn ruby your lower level API should support methods with ! syntax that throw and without it that return nil instead: def get_from_cache get_from_cache! rescue NoRecord nil end Then you just do this to not only do it all but only do it once lazily on access: def record @record ||= get_from_cache || get_from_database || get_from_api || compute || default_value end Be slightly better if ruby supported a proper null coalescing operator like C# ?? and ??= or Perl // and //= but as long as you stay way from "false" as a valid value it works fine.
- Zak 5y ago> In ruby your lower level API should support... Assume, for purposes of the exercise that someone else wrote some of the methods you're calling and they do not behave this way. You could write your own wrappers, but that's a lot more code to write and maintain than a simple control structure that doesn't care what's inside it.
- lamontcg 5y agoSubmit a patch to get them implemented, or really just don't throw in cases that aren't actually exceptional and return nil instead.
- Zak 5y agoI think this makes my point perfectly. With macros, I can write a short macro regardless of how the upstream code behaves. Without macros, I can request a change to the upstream code and hope the maintainer will agree to make it behave how I want.
- lamontcg 5y agoYou can get macros out of rust or julia or other languages other than LISP as well. It is also arguable that overuse of macros leads to less readable code since you wind up inventing your own personal language the more you do it, which requires more of a learning curve to work on it. There's a large advantage to sticking with verbose idioms that are common and built out of a relatively few building blocks rather than building a meta-language which is terse. At the same time I'm not saying don't have macros, but they should really be reserved for more extensive heavy lifting, not a pile of tiny ones used all over the codebase to make it terse. A well written codebase should read more like Hemmingway at an 8th grade reading level using simple constructs from the base language which are put together, generally using programming design idioms which are common and terminology which everyone shares. There shouldn't be a lot of "what does this macro do and where it is it defined?" questions on every other line.
- lispm 5y agoThat would be the time, where I would consider to write a macro. The macro would capture similar logic, but would provide me no-overhead syntax. In Common Lisp: (defmacro return-first-working (&body body) "Expects a sequence of expressions as body. Returns the value of the first expression of body which returns without error." (when body `(handler-case ,(first body) (error () (return-first-working ,@(rest body)))))) (return-first-working (error "hi") (error "there") (error "!") "hello-world" (error "wtf?")) That way I can easily implement primitive control structures, without resorting to add to my code visibly complex things (like making expressions into functions). Above is a recursive macro, which takes its subexpressions and returns a simplified expression, reusing itself. That way code transformation itself is described as a recursive process. One can learn how to apply such transformation patterns to the code itself and then it gets relatively easy to write.
- Zak 5y agoI'm the one who missed something: each of my examples called its functions with no arguments. Your approach would, of course work when the functions are called with no arguments or the same arguments. (first-working (get-it-from-the-cache (or local-cache default-cache)) (get-it-from-the-database current-database-connection) (get-it-from-an-external-api (ask-user-for-api-credentials)) (compute-it-the-slow-way foo bar baz) default-value) Each expression can be arbitrary code where to do something similar in Python you'd need to compute a thunk in advance. There's also an advantage in clarity because an abstraction can be defined. Once you know what first-working is for, the purpose of this block of code is clear as soon as you read the first token. Using a pattern instead of an abstraction, it's necessary to read the whole loop to make sure it's really the pattern you think it is.