21 comments

[ 3.0 ms ] story [ 49.1 ms ] thread
Would I be correct in thinking this is a similar argument those put forward by GvR against the desirability of multi-line lambdas in Python? i.e. once you need multiple lines then you probably should be naming them.
Yes. There's no pride in jamming the most amount of code into one line unless you're entering a contest.
Heh, I remember being kind of scolded for using anything but "isolated functions" in MVC courses.
Surely it's the current one-line* restriction which forces "jamming the most amount of code into one line".

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...

I don't think GP meant one editor line, but one "line of code".
The naming part is, I don't see the connection with the multi-line part.

GvR just does not like functional programming very much, so Python has peculiar name spaces and second class lambdas.

This kind of thing is always a balancing act.

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...

> PS: Having only dabbled in Erlang, to me the real problem exposed by those examples is the lack of a combined lambda/case,

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).
I came away with a similar reaction, but there is no need to map separately first: https://news.ycombinator.com/item?id=13136056

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 agree with the sentiment but a better example should have been chosen. The first example given is very easy to read and not at all confusing while the last is much more verbose and hence harder to parse for non-experts.

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.

Why 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 name
> Which versions force your eyes to do less jumping around?

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.

Agree entirely. My eyes are jumping around most on his last, "preferred" version.
The labeled forms give my brain a chunk of code, with a name, to compartmentalize / internalize. In the nested forms, I have to either chunk it on the fly (every time I need to read it) or not chunk it and make my eyes do a bunch of work.

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.

for the list comprehension, why not

``` 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.

FWIW HN does not do markdown[0], let alone markdown extensions like triple-backtick code blocks.

[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

HN works fine with multiplication signs ("×"), it's asterisks that are problematic.
use 4 spaces in front of code lines instead of ```
meh. I've been doing functional programming for 30 years. Just because some Ruby people have trouble understanding it doesn't mean we should water everything down for "inclusivity."
There might be a good alternative. Sometimes it's that one function right in the case expression that prevents lifting the matching to the function head (which is considered good style in Erlang).

To 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).