Is there a way to make list comprehensions “strict”?

In renaming some map keys, I broke a bunch of code that uses list comprehensions. For example:

Errors = [E || #{error_code := E} <- Responses, E /= ?NO_ERROR]

(the actual example is more complicated, and has more nesting of maps and lists)

If I change error_code to (e.g.) status, then the resulting list is [], even if there were failed responses.

Is there a way to make list comprehensions “strict”, so that instead of skipping values that don’t match the pattern, Erlang raises an error (such as badmatch) instead?

Would this be valuable? Would it be worth writing an EEP (using <= instead of <- maybe)?

4 Likes

If i understand this example correctly this can be achieved in multiple ways, for example:

F = fun(X) when ?IS_ERROR(X)-> throw(some_error);
           (X)  ->  X; 
      end,   
[F(E) || E <- Responses]

% or with lists module 
lists:map(F, Responses)

%alternatively 

lists:foreach(F, Responses)

%not exception but boolean result

lists:any(fun is_error/1, Responses) 
1 Like

Having been bitten more than once by non-matching elements being silently missed, I would second such a proposal and would most probably use it a lot more than the current relaxed, error prone version.
Not wanting either having to define an extra function for such a basic need whose syntax could/should be straightforward, like the one mentioned. So in my opinion a dedicated operator would be very welcome.
More generally, stricter traits would I think benefit to Erlang. It would be a pity if, due to “flexible” datastructures (including lists but also maps), we ended with Erlang modules like too many Python ones that are based on dictionaries of dictionaries with loosely specified (in terms of naming, type, semantics, etc.) entries. In this regard an optional runtime control would be a welcome last-resort safety net.

3 Likes

Those are useful, but only for simple examples.

As stated, my actual problem is more complicated. The original has 3 nested generators. I’d need several nested calls to lists:flatmap, which gets messy very quickly, particularly because the original is capturing values from the nested generators to use in the final output.

2 Likes

Just my 2c.

I would argue that list comprehensions are pretty complicated as they are and that while the filtermap semantics might, or might not, have been a mistake in the first place, adding yet another way of getting things wrong would not improve matters.

I tend to use list comprehensions only when the format is simple enough to foresee the consequences and I use macros for keys in maps to get the spell check.

And while multiple generators can be really nice sometimes, I often refrain from using them because it often makes the code harder to understand.

If I’m doing something complicated it is often worth creating named functions and explicit recursion over the list rather than using comprehensions.

But then again, I might just be a grumpy old man :wink:

12 Likes

Am I being naïve in suggesting:

1> Responses = [#{status_code => 1}].
[#{status_code => 1}]
2> [maps:get(status_code, E) || E <- Responses, maps:get(status_code, E) /= 0].         
[1]
3> [maps:get(error_code, E) || E <- Responses, maps:get(error_code, E) /= 0]. 
** exception error: bad key: error_code
     in function  maps:get/2
        called as maps:get(error,#{status_code => 1})
        *** argument 1: not present in map

(Edit: Ugh, I realised after posting it’d need a 2nd call to maps:get/2. However, depending on your use case, it might not be that terribly inefficient).

1 Like

That, plus my original code is much more complicated than the example I gave. As well as using 3 nested generators, it also captures more than one thing from one of the maps.

Honestly, given Erlang’s lack of a pipeline operator (which, now I think about it, might not help that much in my specific case), the cleanest refactoring would be to a hand-written recursive function.

Hence the suggested strict matching.

1 Like

That might be the best option for when your future self comes to look at the treble-nested generator and their head explodes :wink:

7 Likes

No, I don’t think so. I fully agree on this FWIW.

5 Likes