Erlangers, Use Labels
zxq9.com
zxq9.com
Maybe I'm weird but the author's first examples are easier to read. Having labels is good when there is a non-trivial nested mess, but nothing comes for free. When the idiom is well-understood, being overly explicit ends up with more noise. In the first examples, I'm looking at 1 paragraph of code and understand what's going on. In the supposedly improved examples, labels force a mental dereferencing and a jump to different code paragraphs. Sometimes I don't want to scroll throughout the file (or lookup references) to understand something that's logically part of the same unit of processing.
Of course if the lamdas end up doing a lot of work then the overall structure gets lost and a label would be the right thing to do. But code is no exception to the rule "write for your audience". In this case, the audience is the compiler and humans which are versed in the idioms and style of that environment.
Code that comes later in a single code base will usually copy the patterns that are already established in that code base. (Assuming the starting patterns are decent, this is as it should be.)
If you add one more level of fun nesting or something like that, the labeled examples become even clearer. Since it's entirely likely that new code will copy the existing pattern, I prefer code shapes that will grow cleanly.
There's the argument that implementation details should be hidden, therefore separate named functions should be defined and their behaviour should be clear from the name. In my experience, those who go too far in this direction may not be used to first class functions; for example, I worked on a PHP project which dated way before the addition of closures, and even their JS had no lambdas: all functions, even trivial one-liners which were only used once, were given top-level, globally-scoped names, defined all together in a separate file.
There's the argument that each expression shouldn't do too much, hence pulling a lambda out into a (locally scoped) name.
There's the argument that variables which are only used once should be written inline to avoid the mental cost of indirection.
There's the argument that values should have the tightest possible scope.
There's the argument that common 'utility functions' should be pulled out to a separate place.
In situations like this, I usually favour doing the 'real work' in a locally scoped function, and writing the argument-plumbing inline. That lies somewhere in between the author's "labelled_lambda" (tightly scoped definitions, but conflates processing with argument juggling) and "isolated_functions" (separates processing from argument juggling, but has a wider scope).
PS: Having only dabbled in Erlang, to me the real problem exposed by those examples is the lack of a combined lambda/case, e.g. as (eventually) found in Haskell https://unknownparallel.wordpress.com/2012/07/09/the-long-an...
All lambda in Erlang is case:
do_whatever(Keys, SomeParameter) ->
Lookups = lists:map(fun external_lookup/1, Keys),
lists:foreach(fun
({ok, V}) -> do_side_effecty_thing(V, SomeParameter);
({error, R}) -> report_some_failure(R)
end, Lookups).Sometimes I do wish Erlang had something like F#'s active patterns because it'd make this sort of thing trivial to add to standard pattern expressions. Maybe someday I'll figure out how to add it myself.
I see that for serious Erlangers it would make a lot of sense but for people like me who only dabble in it the first is probably as easy or even easier to read than the rest. And the ones using list comprehension are the worst whether they use named functions or not.
do_whatever(Keys, SomeParameter) ->
lists:foreach(fun(Key) -> maybe_do_stuff(Key, SomeParameter) end, Keys).
//...
Still simple, wins a line, removes the ugly ExecIfFound nameTo do this you could try wrapping the list:foreach itself rather than worry about the inner function, leading to something which seems more concise and in one piece, leaving the more generic iteration to a well named function:
-spec do_whatever(Keys, SomeParameter) -> ok when
Keys :: [some_kind_of_key()],
SomeParameter :: term().
do_whatever(Keys, SomeParameter) ->
lookup_each(fun
({ok, Value}) -> do_side_effecty_thing(Value, SomeParameter);
({error, Reason}) -> report_some_failure(Reason)
end, Keys).
lookup_each(Fn, Keys) ->
lists:foreach(fun (Key) -> Fn(external_lookup(Key)) end, Keys).Allowing multiple lines, by definition, allows breaking up those lines.
* It's not actually one-line, it's one expression; Python's just picky about where you're allowed to break up an expression over multiple lines. For example see http://softwareengineering.stackexchange.com/questions/99243...
GvR just does not like functional programming very much, so Python has peculiar name spaces and second class lambdas.
``` do_whatever(Keys, SomeParameter) -> [case external_lookup(Key) of {ok, V} -> do_side_effecty_thing(V, SomeParameter); {error, R} -> report_some_failure(R) end || Key <- Keys], ok. ```
or
``` [begin some_func(Key), other_func(Key) end || Key <- Keys]. ```
if only the value of other_func() needs to be in the result list.
[0] it implements a very small subset of markdown badly: emphasis (with no escape so it tends to break when you try to use multiplication signs in your comments) and indented "code" blocks