<div dir="ltr"><br><br><div class="gmail_quote">On Thu, Oct 2, 2008 at 1:23 AM, Richard O'Keefe <span dir="ltr"><<a href="mailto:ok@cs.otago.ac.nz">ok@cs.otago.ac.nz</a>></span> wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="Ih2E3d"><br>
On 2 Oct 2008, at 2:49 am, Edwin Fine wrote:<br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
This is a dissenting vote regarding macros.<br>
<br>
Macros *can* make maintenance harder,<br>
</blockquote>
<br></div>
You've just agreed; this is not dissent.<div class="Ih2E3d"></div></blockquote><div><br>Sorry, but I did not agree. You just read it that way. What I perhaps should have written is that you bluntly asserted that macros make maintenance harder. I am bluntly asserting that it is not necessarily so. That's dissent. Or maybe partial dissent, but there's dissent in there somewhere, I'm sure :)<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
just like gotos *can* create spaghetti code. Neither of them are intrinsically bad, merely easy to misuse.<br>
</blockquote>
<br></div>
Maintenance of large amounts of code has to be done with<br>
tools. Macros make it harder to produce accurate tools.<br>
The only freely available program I've come across that<br>
does a decent job of cross-referencing for *both* macros<br>
*and* the underlying symbols for C is CScout. (It isn't<br>
an Open Source program, but it is available in executable<br>
form for no money.) As far as I know there is no real<br>
equivalent for Erlang.<div class="Ih2E3d"></div></blockquote><div><br>I don't think this is a particularly compelling argument. I've been involved in maintenance of multi-million line systems and had much bigger fish to fry than macro-related difficulties with cross-reference tools. Maybe your experience has been different.<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Used with care and discipline, they both arguably have a place in good programming practice.<br>
</blockquote>
<br></div>
As a matter of fact, I still use M4 on Java code.<br>
(Remember, Java 1.5 generics do *not* accept primitive types<br>
as arguments. If you want to do that without the very heavy<br>
overhead of Java boxing and unboxing, M4 is the only game in town.)<div class="Ih2E3d"></div></blockquote><div><br>So macros are NOT that evil :)<br> <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="Ih2E3d"><br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Now I may get shot down in flames for saying this, but tail recursion in Erlang is effectively a restricted form of goto,<br>
</blockquote>
<br></div>
Perfectly true. There's even a famous paper<br>
"Lambda, the Ultimate Goto".<div class="Ih2E3d"></div></blockquote><div><br>So the evil GOTO is not always evil, only sometimes. So it is for macros, or any dirty trick we have to pull because we never thought of the right way to do it originally and now we have to retrofit a bolt-on solution.<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
and it's used a lot (not by choice over some other construct, though - the language design forces the usage).<br>
<br>
There are some things that macros can do that I have not found as easy (or possible) to do some other way, for example:<br>
<br>
-define(LOG_DBG(Msg, ArgList), iutil_log:log_debug(?MODULE, ?LINE, Msg, ArgList)).<br>
<br>
Example usage:<br>
<br>
?LOG_DBG("Received ~p from ~p~n", [Msg, Socket]).<br>
</blockquote>
<br></div>
This is a rather interesting one.<br>
What could replace it?<br>
<br>
-module(flog).<br>
-export([flog/2]).<br>
<br>
flog(Format, Arguments) -><br>
{Module, Line, _} = erlang:call_site(),<br>
iutil_log:log_debug(Module, Line, Format, Arguments).<br>
<br>
Some kind of primitive that extracted a return address and<br>
consulted a line number table. That could do it. In fact it<br>
could provide more information, such as {Function,Arity}. As<br>
a debugging tool, presumably it would not need to be fast.<div class="Ih2E3d"></div></blockquote><div><br>But it's not available *today*, and I need something that works *today* to give my customer.<br> <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="Ih2E3d"><br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
</blockquote>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
iutil_log:log_debug(?MODULE, ?LINE, "Received ~p from ~p~n", [Msg,<br>
Socket]).<br>
</blockquote>
<br></div>
But what if, instead of or in addition to ?MODULE and ?LINE,<br>
the system provided you with ?HERE, expanding to<br>
{Module, Line, {Function, Arity}}. Then<br>
<br>
iutil_log:flog(?HERE, "Received ~p from ~p~n", [Msg,Socket])<br>
<br>
doesn't seem _that_ horrible.<div class="Ih2E3d"></div></blockquote><div><br>Sorry to belabor the point, but what if it did? It doesn't. What if we all lived in peace and harmony and respected each other's rights?<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
<br>
If I find that debug logging is causing too much overhead, I can decide to conditionally compile it:<br>
<br>
-ifdef(DEBUG).<br>
-define(LOG_DBG(Msg, ArgList), iutil_log:log_debug(?MODULE, ?LINE, Msg, ArgList)).<br>
-else.<br>
-define(LOG_DBG(Msg, ArgList), ok).<br>
-endif.<br>
</blockquote>
<br></div>
Suppose we had top-level variables instead. So<br>
</blockquote><div><br>But we don't. I wish we did.<br> <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
Debug = false.<br>
<br>
-inline([flog/3]).<br>
<br>
flog({Module,Line,_}, Format, Arguments) when Debug -><br>
iutil:log_debug(Module, Line, Format, Arguments);<br>
flog(_, _, _) -><br>
ok.<br>
<br>
Now we are down to<br>
<br>
... flog(?HERE, "Received ~p from ~p~n", [Msg,Socket]) ...<br>
<br>
with the *same* efficiency as the macro, as easily enabled or<br>
disabled, and no preprocessor. The only thing we need that we<br>
don't have now (for we do have inlining) is ?HERE, which is no<br>
harder to provide than ?LINE.<br>
<br>
I've been experimentally rewriting some Erlang modules to see<br>
what top level variables would look like. Rather nice, in fact.<br>
Well, we don't have those now, and we'd need non-trivial compiler<br>
changes to get them. So let's do without.</blockquote><div><br>Exactly my point.<br> <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
<br>
-inline([debug/0, flog/3]).<br>
<br>
debug() -> false.<br>
<br>
flog(Where, Format, Arguments) -><br>
case debug()<br>
of true -><br>
{Module,Line,_} = Where,<br>
iutil:log_debug(Module, Line, Format, Arguments)<br>
; false -><br>
ok<br>
end.<br>
<br>
These functions we can write today.<div class="Ih2E3d"></div></blockquote><div><br>True - but I was working towards my ultimate point that having to recompile is a pain and wanted the ability to change run-time log levels. And, using the above code, won't you get copious compiler or Dialyzer warnings complaining that only one branch of the case will ever be reached? How would you suppress those? Or are you going to invent a suitable -pragma() for Erlang :) ?<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
</blockquote>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
A minor inconvenience of the above is that if it uses variables that are not otherwise used, you can get compile warnings when debug logging is disabled. This is easily fixed by using the underscore: _Unused.<br>
</blockquote>
<br></div>
And the version using an inlined function doesn't have the problem<br>
in the first place.<div class="Ih2E3d"></div></blockquote><div><br>True. Does the inlined function stay inlined even if you compile with [debug_info]? I truly don't know - in C++ this often disables inlining.<br> <br>
</div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
If I don't like having to recompile the code to enable and disable debug logging, but want to turn it on and off at run-time (and still have negligible overhead when debug logging is disabled), I can do this (and the source code using this does not change in any way, but of course must undergo a once-off recompilation):<br>
<br>
-define(<br>
LOG_DBG(Msg, ArgList),<br>
case iutil_log:ok_to_log(debug) of<br>
true -><br>
iutil_log:log_debug(?MODULE, ?LINE, Msg, ArgList);<br>
false -><br>
ok<br>
end<br>
).<br>
</blockquote>
<br></div>
Ah, the old don't-evaluate-the-arguments trick.<br>
I've run into code that only worked when you had<br>
assertions enabled, because it relied on the<br>
argument of assert() being evaluated...<div class="Ih2E3d"></div></blockquote><div><br>Ah, the old I've-seen-crap-code trick. Yes, absolutely true, and anyone worth their salt will never put important code into log statements that won't get evaluated.<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
</blockquote>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Checking if the Msg and ArgList should be evaluated before calling saves, at the cost of an efficient function call, potentially enormous amounts of unnecessary list creation and destruction (and garbage collection), not to mention any evaluation of the list elements that might be needed.<br>
</blockquote>
<br></div>
True. It also means that the debugging version and the non-debugging<br>
version of your program do not do the same thing.<br>
</blockquote><div><br>Only if I'm careless. Anyway, the whole point of having a debugging and non-debugging version is that they DON'T do the same thing - otherwise why have two versions? But I am splitting hairs here - I know you mean that from a functional standpoint, they won't do the same unless you take care. But that's true of anything to do with programming.<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
If the expressions involve only constants, variables, control<br>
structures, and calls to known pure functions, I would hope that<br>
</blockquote><div><br>And they absolutely should!<br> <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
-inline([flog/3]).<br>
flog(Where, Format, Arguments) when Debug -> %%% Huh??<br><div class="Ih2E3d"><br>
case iutil_log:ok_to_log(debug)<br>
of true -><br></div>
{Module, Line, _} = Where,<br>
iutil:log_debug(Module, Line, Format, Arguments)<br>
; false -><br>
ok<br>
end;<br>
flog(_, _, _) -><br>
ok.<br>
<br>
... flog(?HERE, "......", [.....]) ...<br>
<br>
would push the evaluation of the format and arguments into the one<br>
case branch that uses them. If it doesn't, we have far worse<br>
performance issues to worry about than this one.<br>
</blockquote><div><br>Where does Debug come from? Is it a (non-existent) top-level variable?<br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
If the expressions involve side effects and calls to possibly<br>
impure functions, then you had better make sure they are _always_<br>
evaluated, otherwise what you log won't be what happens when you<br>
are not logging.</blockquote><div><br>Totally agreed. But that's true of any conditional code, not just that to do with logging. You have to be careful. I suppose that with a macro that you don't understand might not always execute, you run a higher risk of screwing up. Ah, if only languages would give us what we need without macros....!<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
<br>
In this context, I find BitC's distinction between pure and impure<br>
functions _in the type system_ interesting.<div class="Ih2E3d"></div></blockquote><div><br>That would be very useful - does that mean the compiler automatically flags functions as pure or impure based on their side-effects?<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><div class="Ih2E3d"><br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
<br>
In this case I would argue that the macro makes the code *more* maintainable and easier to read, while keeping it efficient. Agreed, if the macro changes (and this class of macro seldom changes) I will need to recompile the dependent code, and this is definitely not good, but as Lord Farquhar said, "[Many of you may be killed, but] it is a sacrifice I am willing to make." ;-)<br>
</blockquote>
<br></div>
The problem is that ?LOG_DBG could do *anything*,<br>
and it isn't as easy as it should be to find the definition.</blockquote><div><br>That's also its strength (to be able to do ANYTHING and you don't have to change the code). To find the definition using find and grep isn't *that* hard, is it? I have searched the entire Erlang source code base for things of interest in under a minute with find/grep. Maybe it's slow using that 500MHz UltraSparc you are saddled with :(<br>
<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
(I was going to explain what etags does with macros, since<br>
etags on my box claims to support Erlang, but what<br>
cd stdlib/src; etags -o fred *.erl<br>
does is to crash in strncpy(). Since it fails to note the<br>
arity of functions, it's dubiously useful anyway.)</blockquote><div><br>The authors probably weren't careful with the side-effects of their debug log statements :)<br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
<br>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
</blockquote>
The curious thing is that people keep on trotting out the *same*<br>
example of why the preprocessor is useful. We *have* inlining.</blockquote><br><div>We keep trotting out the *same* example because there are no *existing* solutions that do what I want without using it!!<br><div> </div>
<blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;"><br>
If only we had ?HERE, </blockquote><br>But we don't. I have to work with the real world. If ?HERE was here I would use it!<br> <br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
then<div class="Ih2E3d"><br>
<br>
LOG_DBG("Received ~p from ~p~n", [Msg,Socket])<br>
<br></div>
would be<br>
<br>
flog(?HERE, "Received ~p from ~p~n", [Msg,Socket])<br>
<br>
which I for one don't regard as unduly burdensome. In<br>
fact the visible presence of ?HERE as an argument tells<br>
me that the location is being passed on, which is not so<br>
obvious in LOG_DBG.<br>
</blockquote><div><br>Maybe so, but it gives me the flexibility to remove the location being passed later if I find it is causing a problem, say with performance. <br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<br>
Actually, if we had ?HERE, things could get even better.<br>
If the compiler handled constant terms specially (using a<br>
single static copy instead of building a new copy on the<br>
heap), passing ?HERE could be as cheap as passing 42, and<br>
we could pass around one possibly detailed location term<br>
instead of separate module/line arguments. This would be<br>
nice to do for format strings too.<br>
</blockquote><div><br>Well, I for one would vote for a compiler that detected constant terms and kept them statically!!<br></div></div><br>The one important thing that I perhaps didn't understand from your discussion is how you proposed to change the logging level at run time, given the *existing* features of Erlang.<br>
<br>Understand that I gave the LOG_DBG macro as an example. As it happens, I have LOG_TRACE, LOG_INFO, LOG_WARN, and LOG_ERROR as well. I know that Erlang has trace facilities, but they are not retrospective. I need to go back in the logs and see what happened historically. I don't like the idea of sending all logging operations to a central manager process that decides to throw away the ones it doesn't want, because I don't want the sending process to be spewing out useless messages and using up CPU and GC time, and the manager process to be throwing things away. I have found historical logs to be an invaluable part of the fault-finding process, so I pepper my code liberally with all kinds of log statements - and I want to do that with the least runtime cost for the most debugging benefit.<br>
<br>Let me tell you what I have done, and this will probably give you nightmares because it's a terrible hack, but it works well. I hacked it like this because I didn't want to (a) pass log level variables in the parameter list of every single function I ever write (b) store the log level in the process dictionary where it has to be looked up thousands of times a second (c) even worse, store it in an ETS table. In the absence of top-level variables, I faked a top-level variable as follows.<br>
<br>I wrote a module that exports only one function, log_level(). This function is hard-coded to return (say) the atom 'info', e.g.<br><br>log_level() -> info.<br><br>If I want to change the logging level to debug at runtime, I simply rewrite the one line of code (plus module and export statements) in a string and recompile the code at runtime, then purge. The logging level then changes and I get debug, warning, error and info logs. (And I do understand that the historical aspect I find so important can be affected by changing the logging level, but I ensure that I log the most critical information at all times). My understanding is that a call to mod:func is very cheap, probably more so than any process dictionary or ETS lookups. So this way, for the price of a bit of skulduggery, I get tremendous flexibility at a low runtime cost. I am sure that the gurus will find some horrible flaw in this scheme, which I'd like to hear about so I can fix it, but for now it is working very well indeed.<br>
<br>I do look forward to the implementation an EEP for top-level variables, though, then I can throw this ugliness away.<br><br></div>