Paul Rubin <http://[EMAIL PROTECTED]> writes:
> There is just not that much boilerplate in Python code, so there's > not so much need to hide it. Well, of course there is. There are always going to be patterns in the code you write that could be collapsed. Language has nothing to do with it; Lisp without macros would still be susceptible to boilerplate. Here's a concrete example: (let ((control-code (read-next-control-code connection))) (ecase control-code (+break+ (kill-connection connection) (throw :broken-by-client)) (+status+ (send-status-summary connection)) ((+noop+ +keep-alive+)))) ;; +X+ indicates a constant The standard ECASE macro encapsulates this pattern: Compare the variable control-code to +break+. If the two are EQL, then run the code provided in the +break+ clause. Likewise for +status+. In the last clause, the test-form is a list, so the generated code will compare control-code to either +noop+ or +keep-alive+. If it is EQL to either, it runs the body of that clause, which happens to be blank here. The E in ECASE stands for "error," so if control-code doesn't match any of these choices, the generated code will signal an error with the following text "CONTROL-CODE fell through ECASE expression; was not one of: +BREAK+, +STATUS+, +NOOP+, +KEEP-ALIVE+". All of that boilerplate is handled by the macro. In Python, I would need to do something like: control_code = connection.read_next_control_code() if control_code == +break+: connection.kill() throw blah else if control_code == +status+: connection.send_status_summary() else if control_code == +noop+ || control_code == +keep_alive+: else: error "CONTROL_CODE fell through conditional cascade; was not one of +BREAK+, +STATUS+, +NOOP+, +KEEP_ALIVE+" To change what control codes you want to check for, you need to add conditionals for them and keep the error text relevant. The reality is that a computer could be doing this for you, leaving your code simpler and more easily changed. Now someone will complain that the ECASE code means nothing until I understand ECASE. Yep. But once you understand ECASE, you can look at that code and, *at a glance*, see how control flows through it. In the equivalent Python code, I need to walk through each conditional and make sure they're all following the same pattern. If you're not convinced, extend the example to 12 different control codes. Note also that ECASE is just expanding to the COND conditional. There is nothing mind-bending (or even mind-twisty) going on inside of it. It's simply a way of expressing a common syntactic pattern in higher-level terms. To prove that macros are not the frightening beasts you guys are making them out to be: CL-USER 13 > (let ((*print-case* :downcase)) (pprint (macroexpand '(ecase control-code (+break+ (kill-connection connection) (throw :broken-by-client)) (+status+ (send-status-summary connection)) ((+noop+ +keep-alive+)))))) (let ((#:g17558 control-code)) (case #:g17558 (+break+ (kill-connection connection) (throw :broken-by-client)) (+status+ (send-status-summary connection)) ((+noop+ +keep-alive+)) (otherwise (conditions::ecase-error #:g17558 '(+break+ +status+ (+noop+ +keep-alive+)))))) If you treat the #:G17548 as just a weirdly-named variable, you can see that the code is just expanding into the standard CASE macro. I can in turn expand this CASE to: CL-USER 14 > (let ((*print-case* :downcase)) (pprint (macroexpand '(case #:g17558 (+break+ (kill-connection connection) (throw :broken-by-client)) (+status+ (send-status-summary connection)) ((+noop+ +keep-alive+)) (otherwise (conditions::ecase-error #:g17558 '(+break+ +status+ (+noop+ +keep-alive+)))))))) (let ((#:g17559 #:g17558)) (cond ((eql '+break+ #:g17559) (kill-connection connection) (throw :broken-by-client)) ((eql '+status+ #:g17559) (send-status-summary connection)) ((or (eql '+noop+ #:g17559) (eql '+keep-alive+ #:g17559)) nil) (t (conditions::ecase-error #:g17558 '(+break+ +status+ (+noop+ +keep-alive+)))))) COND is the Lisp conditional form. As you can see, ECASE does not blow your mind, but simply names and standardizes a common pattern of code. It expands into standard macros. And ECASE is so easy to write that most Lisp programmers have extended versions of it in their personal libraries. And most of these are named GENERIC-CASE or STRING-CASE, etc. and most expand into standard COND, CASE or ECASE macros. We are not going crazy and definining new langauges; we are simply extending Lisp to meet our needs, by creating macros that abstract common patterns. In many cases, the macros resemble standard, well-known Lisp macros even down to their names. (In the real world, I might use CLOS's eql-specifiers to define handlers for each kind of control code. But Python doesn't have anything analagous to that, so I'll be polite and pretend I have to use ECASE). -- http://mail.python.org/mailman/listinfo/python-list