TheJach.com

Jach's personal blog

(Largely containing a mind-dump to myselves: past, present, and future)
Current favorite quote: "Supposedly smart people are weirdly ignorant of Bayes' Rule." William B Vogt, 2010

I wish Python had macros, but it's okay

I love Python, I just wish it had macros so I didn't always have to use eval/exec and friends. I'm not even asking for Lisp macros! C macros would be enough. (Though I guess it's not too hard to wire Python up to use the C preprocessor or something similar.) Yet as I'll show, Python's nice enough that for most cases it can get by just fine. Look at this:


#define Q(x) ((#x)[0])
#define CH(N) if (ch == Q(N)) GPIO_WriteBit(GPIO7, GPIO_Pin_##N , GPIO_ReadBit(GPIO7, GPIO_Pin_##N ) == Bit_RESET ? Bit_SET : Bit_RESET)
CH(0);CH(1);CH(2);CH(3);CH(4);CH(5);CH(6);CH(7);


expands to (pretty printed):


if (ch == (("0")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_0 , GPIO_ReadBit(GPIO7, GPIO_Pin_0 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("1")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_1 , GPIO_ReadBit(GPIO7, GPIO_Pin_1 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("2")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_2 , GPIO_ReadBit(GPIO7, GPIO_Pin_2 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("3")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_3 , GPIO_ReadBit(GPIO7, GPIO_Pin_3 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("4")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_4 , GPIO_ReadBit(GPIO7, GPIO_Pin_4 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("5")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_5 , GPIO_ReadBit(GPIO7, GPIO_Pin_5 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("6")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_6 , GPIO_ReadBit(GPIO7, GPIO_Pin_6 ) == Bit_RESET ? Bit_SET : Bit_RESET);
if (ch == (("7")[0]))
GPIO_WriteBit(GPIO7, GPIO_Pin_7 , GPIO_ReadBit(GPIO7, GPIO_Pin_7 ) == Bit_RESET ? Bit_SET : Bit_RESET);


What this code is doing is letting the keyboard characters 0-7 toggle LED lights hooked up to a microcontroller under the specified GPIO pins. Look how much code that macro saved! In Lisp (Clojure), I might do (there's certainly a better way, I'm still learning):


(defmacro ch_trigger [N]
`(if (= (str ch) (str ~N ))
(GPIO_WriteBit GPIO7 ~(symbol (str 'GPIO_Pin_ N))
(if (= Bit_SET
(GPIO_ReadBit GPIO7 ~(symbol (str 'GPIO_Pin_ N))))
Bit_RESET
Bit_SET))))
(doseq [i (range 8)] (ch_trigger i))


Which isn't as short LOC wise but lets me loop, unlike the C version; imagine if I had 400 pins! This expands to, with (doseq [i (range 8)] (println (macroexpand-1 `(ch_trigger ~i)))):


(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 0)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_0 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_0)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 1)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_1 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_1)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 2)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_2 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_2)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 3)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_3 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_3)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 4)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_4 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_4)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 5)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_5 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_5)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 6)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_6 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_6)) user/Bit_RESET user/Bit_SET)))
(if (clojure.core/= (clojure.core/str user/ch) (clojure.core/str 7)) (user/GPIO_WriteBit user/GPIO7 GPIO_Pin_7 (if (clojure.core/= user/Bit_SET (user/GPIO_ReadBit user/GPIO7 GPIO_Pin_7)) user/Bit_RESET user/Bit_SET)))


Now Python, even though I have to use exec, I actually like the best since it's the clearest (to me):


for i in xrange(8):
N = str(i)
exec "if ch == '" + N + "': GPIO_WriteBit(GPIO7, GPIO_Pin_" + N + ', Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_' + N + ') == Bit_RESET else Bit_RESET)'


"expands" to (change exec to print):


if ch == '0': GPIO_WriteBit(GPIO7, GPIO_Pin_0, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_0) == Bit_RESET else Bit_RESET)
if ch == '1': GPIO_WriteBit(GPIO7, GPIO_Pin_1, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_1) == Bit_RESET else Bit_RESET)
if ch == '2': GPIO_WriteBit(GPIO7, GPIO_Pin_2, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_2) == Bit_RESET else Bit_RESET)
if ch == '3': GPIO_WriteBit(GPIO7, GPIO_Pin_3, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_3) == Bit_RESET else Bit_RESET)
if ch == '4': GPIO_WriteBit(GPIO7, GPIO_Pin_4, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_4) == Bit_RESET else Bit_RESET)
if ch == '5': GPIO_WriteBit(GPIO7, GPIO_Pin_5, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_5) == Bit_RESET else Bit_RESET)
if ch == '6': GPIO_WriteBit(GPIO7, GPIO_Pin_6, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_6) == Bit_RESET else Bit_RESET)
if ch == '7': GPIO_WriteBit(GPIO7, GPIO_Pin_7, Bit_SET if GPIO_ReadBit(GPIO7, GPIO_Pin_7) == Bit_RESET else Bit_RESET)


Again the most readable.

Should Python have official macros? Yes. Is it just fine without them? Quite. Even when you think Python should lose in expressiveness because it lacks a feature (like full lambdas, or macros), it still does pretty well and often wins.


Posted on 2011-09-26 by Jach

Tags: c, clojure, personal, programming, python

Permalink: https://www.thejach.com/view/id/206

Trackback URL: https://www.thejach.com/view/2011/9/i_wish_python_had_macros_but_its_okay

Back to the top

Jach March 21, 2012 10:37:23 AM I've since decided that it's not okay.
Back to the first comment

Comment using the form below

(Only if you want to be notified of further responses, never displayed.)

Your Comment:

LaTeX allowed in comments, use $$\$\$...\$\$$$ to wrap inline and $$[math]...[/math]$$ to wrap blocks.