[erlang-questions] The Erlang Rationale
Tamas Nagy
lestat@REDACTED
Thu Oct 2 10:22:55 CEST 2008
Hi!
If you have problems with macro support for cross referencing take a
look at RefactorErl. Although it is a refactoring tool it provides a
quite extensive API to call, macro and record cross referencing.
I do not see any obstacle which would make it impossible for somebody
- with a bit of spare time - to create a tool on top of the data
provided by the system. Best of all it supports macros which cut into
the syntactical structure of the code.
For example:
...
-define(ADD(X,Y), + X + Y).
...
foo(X, Y) ->
3 ?ADD(X,Y).
...
I'm sure you can come up with uglier but more meaningful examples as
well.
Regards,
Tamas
On 2 Oct 2008, at 07:55, Edwin Fine wrote:
>
>
> On Thu, Oct 2, 2008 at 1:23 AM, Richard O'Keefe <ok@REDACTED>
> wrote:
>
> On 2 Oct 2008, at 2:49 am, Edwin Fine wrote:
>
> This is a dissenting vote regarding macros.
>
> Macros *can* make maintenance harder,
>
> You've just agreed; this is not dissent.
>
> 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 :)
>
>
>
> just like gotos *can* create spaghetti code. Neither of them are
> intrinsically bad, merely easy to misuse.
>
> Maintenance of large amounts of code has to be done with
> tools. Macros make it harder to produce accurate tools.
> The only freely available program I've come across that
> does a decent job of cross-referencing for *both* macros
> *and* the underlying symbols for C is CScout. (It isn't
> an Open Source program, but it is available in executable
> form for no money.) As far as I know there is no real
> equivalent for Erlang.
>
> 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.
>
>
>
> Used with care and discipline, they both arguably have a place in
> good programming practice.
>
> As a matter of fact, I still use M4 on Java code.
> (Remember, Java 1.5 generics do *not* accept primitive types
> as arguments. If you want to do that without the very heavy
> overhead of Java boxing and unboxing, M4 is the only game in town.)
>
> So macros are NOT that evil :)
>
>
>
> Now I may get shot down in flames for saying this, but tail
> recursion in Erlang is effectively a restricted form of goto,
>
> Perfectly true. There's even a famous paper
> "Lambda, the Ultimate Goto".
>
> 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.
>
>
>
> and it's used a lot (not by choice over some other construct, though
> - the language design forces the usage).
>
> There are some things that macros can do that I have not found as
> easy (or possible) to do some other way, for example:
>
> -define(LOG_DBG(Msg, ArgList), iutil_log:log_debug(?MODULE, ?LINE,
> Msg, ArgList)).
>
> Example usage:
>
> ?LOG_DBG("Received ~p from ~p~n", [Msg, Socket]).
>
> This is a rather interesting one.
> What could replace it?
>
> -module(flog).
> -export([flog/2]).
>
> flog(Format, Arguments) ->
> {Module, Line, _} = erlang:call_site(),
> iutil_log:log_debug(Module, Line, Format, Arguments).
>
> Some kind of primitive that extracted a return address and
> consulted a line number table. That could do it. In fact it
> could provide more information, such as {Function,Arity}. As
> a debugging tool, presumably it would not need to be fast.
>
> But it's not available *today*, and I need something that works
> *today* to give my customer.
>
>
>
>
> iutil_log:log_debug(?MODULE, ?LINE, "Received ~p from ~p~n", [Msg,
> Socket]).
>
> But what if, instead of or in addition to ?MODULE and ?LINE,
> the system provided you with ?HERE, expanding to
> {Module, Line, {Function, Arity}}. Then
>
> iutil_log:flog(?HERE, "Received ~p from ~p~n", [Msg,Socket])
>
> doesn't seem _that_ horrible.
>
> 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?
>
>
>
>
> If I find that debug logging is causing too much overhead, I can
> decide to conditionally compile it:
>
> -ifdef(DEBUG).
> -define(LOG_DBG(Msg, ArgList), iutil_log:log_debug(?MODULE, ?
> LINE, Msg, ArgList)).
> -else.
> -define(LOG_DBG(Msg, ArgList), ok).
> -endif.
>
> Suppose we had top-level variables instead. So
>
> But we don't. I wish we did.
>
>
> Debug = false.
>
> -inline([flog/3]).
>
> flog({Module,Line,_}, Format, Arguments) when Debug ->
> iutil:log_debug(Module, Line, Format, Arguments);
> flog(_, _, _) ->
> ok.
>
> Now we are down to
>
> ... flog(?HERE, "Received ~p from ~p~n", [Msg,Socket]) ...
>
> with the *same* efficiency as the macro, as easily enabled or
> disabled, and no preprocessor. The only thing we need that we
> don't have now (for we do have inlining) is ?HERE, which is no
> harder to provide than ?LINE.
>
> I've been experimentally rewriting some Erlang modules to see
> what top level variables would look like. Rather nice, in fact.
> Well, we don't have those now, and we'd need non-trivial compiler
> changes to get them. So let's do without.
>
> Exactly my point.
>
>
>
> -inline([debug/0, flog/3]).
>
> debug() -> false.
>
> flog(Where, Format, Arguments) ->
> case debug()
> of true ->
> {Module,Line,_} = Where,
> iutil:log_debug(Module, Line, Format, Arguments)
> ; false ->
> ok
> end.
>
> These functions we can write today.
>
> 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 :) ?
>
>
>
>
>
> 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.
>
> And the version using an inlined function doesn't have the problem
> in the first place.
>
> 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.
>
>
>
> 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):
>
> -define(
> LOG_DBG(Msg, ArgList),
> case iutil_log:ok_to_log(debug) of
> true ->
> iutil_log:log_debug(?MODULE, ?LINE, Msg, ArgList);
> false ->
> ok
> end
> ).
>
> Ah, the old don't-evaluate-the-arguments trick.
> I've run into code that only worked when you had
> assertions enabled, because it relied on the
> argument of assert() being evaluated...
>
> 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.
>
>
>
>
> 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.
>
> True. It also means that the debugging version and the non-debugging
> version of your program do not do the same thing.
>
> 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.
>
>
> If the expressions involve only constants, variables, control
> structures, and calls to known pure functions, I would hope that
>
> And they absolutely should!
>
>
> -inline([flog/3]).
> flog(Where, Format, Arguments) when Debug -> %%% Huh??
>
> case iutil_log:ok_to_log(debug)
> of true ->
> {Module, Line, _} = Where,
> iutil:log_debug(Module, Line, Format, Arguments)
> ; false ->
> ok
> end;
> flog(_, _, _) ->
> ok.
>
> ... flog(?HERE, "......", [.....]) ...
>
> would push the evaluation of the format and arguments into the one
> case branch that uses them. If it doesn't, we have far worse
> performance issues to worry about than this one.
>
> Where does Debug come from? Is it a (non-existent) top-level variable?
>
>
> If the expressions involve side effects and calls to possibly
> impure functions, then you had better make sure they are _always_
> evaluated, otherwise what you log won't be what happens when you
> are not logging.
>
> 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....!
>
>
>
> In this context, I find BitC's distinction between pure and impure
> functions _in the type system_ interesting.
>
> That would be very useful - does that mean the compiler
> automatically flags functions as pure or impure based on their side-
> effects?
>
>
>
>
> 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." ;-)
>
> The problem is that ?LOG_DBG could do *anything*,
> and it isn't as easy as it should be to find the definition.
>
> 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 :(
>
>
> (I was going to explain what etags does with macros, since
> etags on my box claims to support Erlang, but what
> cd stdlib/src; etags -o fred *.erl
> does is to crash in strncpy(). Since it fails to note the
> arity of functions, it's dubiously useful anyway.)
>
> The authors probably weren't careful with the side-effects of their
> debug log statements :)
>
>
>
>
> The curious thing is that people keep on trotting out the *same*
> example of why the preprocessor is useful. We *have* inlining.
>
> We keep trotting out the *same* example because there are no
> *existing* solutions that do what I want without using it!!
>
>
> If only we had ?HERE,
>
> But we don't. I have to work with the real world. If ?HERE was here
> I would use it!
>
> then
>
>
> LOG_DBG("Received ~p from ~p~n", [Msg,Socket])
>
> would be
>
> flog(?HERE, "Received ~p from ~p~n", [Msg,Socket])
>
> which I for one don't regard as unduly burdensome. In
> fact the visible presence of ?HERE as an argument tells
> me that the location is being passed on, which is not so
> obvious in LOG_DBG.
>
> 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.
>
>
> Actually, if we had ?HERE, things could get even better.
> If the compiler handled constant terms specially (using a
> single static copy instead of building a new copy on the
> heap), passing ?HERE could be as cheap as passing 42, and
> we could pass around one possibly detailed location term
> instead of separate module/line arguments. This would be
> nice to do for format strings too.
>
> Well, I for one would vote for a compiler that detected constant
> terms and kept them statically!!
>
> 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.
>
> 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.
>
> 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.
>
> I wrote a module that exports only one function, log_level(). This
> function is hard-coded to return (say) the atom 'info', e.g.
>
> log_level() -> info.
>
> 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.
>
> I do look forward to the implementation an EEP for top-level
> variables, though, then I can throw this ugliness away.
>
> _______________________________________________
> erlang-questions mailing list
> erlang-questions@REDACTED
> http://www.erlang.org/mailman/listinfo/erlang-questions
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20081002/1ced791b/attachment.htm>
More information about the erlang-questions
mailing list