EEP 70: Non-filtering generators

Just a nit-pick: looks like the respective EEP has not been merged?

1 Like

ā€¦ and people accuse me of giving them bad ideasā€¦ :crazy_face:

1 Like

This just shows how different minds work - I honestly belive that is a nice and compact syntax, using it all the time :sweat_smile:

2 Likes

Indeedā€¦ I would call it a bug exploited as a feature :grimacing:

2 Likes

ā€¦ and people who deliberately use it that way should be flogged (just kidding).

Butā€¦ honestly? Like, for real? :flushed:

2 Likes

I could imagine an even more novel way of writing comprehensions. Something like this for example:

let
  %% Various types of generators (all strict)
  [Elem] <- ListExpression,
  <<Bin>> <- BinExpression,
  #{K := V} <- MapExpression,
  {T} <- TupleExpression, %% Long live EEP 12!

  %% Zip generators from EEP 73
  [A] <- As && [B] <- Bs,

  %% You can put here ordinary Erlang code
  X = A + B,
  case T of
    foo -> io:put_chars("hello world!\n");
    bar -> whatever()
  end,

  %% ?= is the only way to filter
  true ?= V > 0, % used as a traditional, boolean filter (without guard semantics!)
  {ok, Val} ?= Elem % but any pattern matching is allowed
in
  [Val] % you could also write <<Val>>, #{Key => Val} or {Val}
end % I guess it would be possible to parse the let expression without the end, but it's more uniform this way

Maybe with an additional rule that the comprehension must begin with a generator (so no more [X || if_this:is_true()]).

3 Likes

I just wanted to say that I find this proposition (i.e. letā€¦inā€¦end) super nice and friendly. Itā€™s my favorite style from the ones Iā€™ve seen in this thread so far.

2 Likes

Everyone to his own, I guess, that is probably the worst suggestion Iā€™ve seen in this thread :person_shrugging:

4 Likes

I really like this:

And it could also be applied to todayā€™s comprehensions if ?= is introduced: you could use ?= as a filter (therefore fixing the weird guard semantics we have today) and also soft matching (replacing the <- and <= operators). :+1:


I am not a biggest fan of the return type being specified in in, because I need to ā€œsearchā€ too deep to find if a let will return a list/map/binary. But you could also mix with my earlier proposal and have it at a glance:

[let X <- List in X * 2 end]

IMO, it reads like a nice way to say this list/map/binary is derived from something else. And it makes it immediately clear you have a comprehension, you donā€™t need to navigate inside the list to find a ||.

2 Likes

Constructions like this for example:

    AccS1 = [Acc || Acc /= void] ++ AccS,

and:

    COpts = [{src_dir, SrcDir} || SrcDir /= <<>>] ++
            ... 

and of course in tests:

    [ ct:log("There was a problem with the issue ~p", [Prob]) || Verbose ]

Yes, after the decision was taken, I had to leave to pick up my kid, so the PR to OTP was merged faster than me going home and merging the EEP.

EEP merged now.

I will also see what to do about old EEPs. In a way, we have EEPs that are there lying around and have been accepted to the EEP repo because they are valid and contain great ideas. I do not think it is fair to reject them when they have not been considered to be added to the language because no decision was taken at the time. Not sure if adding a new EEP category for those makes senseā€¦ I will think about it

1 Like

I personally donā€™t find that a problem. Itā€™s the same thing how I have to read through an entire function to see whatā€™s the return value going to be.

On the other hand, I wanted to keep both generators and the result of the comprehension similar in the sense that the type is determined by the kind of brackets used around the element you generate/yield. It also keeps the special syntax used within binary and map expressions inside a binary or map expression (and without further nesting inside the let ... in ... end). I think thatā€™s cool.

When I read let, my brain makes a context switch to Haskell, Elm or even Basic. In my perception, you use let to create local variables and/or functions. To use let as here specifically to represent comprehensions does not make sense to me. Then in instead of let use a keyword like itterate? And if you wanted to introduce something like a new expression such as let into core Erlang, why not just add it to the standard libraries?

2 Likes

That was partially my thought too, which is another reason why I suggested to at least move the let inside comprehension, so there are some differences to how lets are used almost everywhere else. As far as I know, languages like Haskell and Elm do not allow let inside data constructors (not even JavaScript does).

I would strongly suggest against let ... in ... (with or without [] around it), for the reasons @schnef gave, namely, that let is a keyword used to introduce/define/declare new variables (in all languages that have a let keyword? MLs/Haskell, Lisps/Schemes, JavaScript, Rust, ā€¦), which would thus be contrary to all other already well-established ā€œtraditionā€ for no apparent good reason, making Erlang an even more esoteric language than it already is.

I would also strongly suggest against using for ... of ... end (with or without [] around it) as it looks a lot like an imperative construct ā€“ itā€™s not clear to me that the last expression of the body is ā€œreturnedā€ ā€“ isnā€™t it just a for-each? ā€“ and I would bet anyone coming from other languages, even functional ones, would agree here (a survey to confirm this would be good, but I doubt itā€™d be feasible).

Instead I would suggest simply replacing || with of for the ā€œgood behaviorā€ (fixing the existing problems with list comprehensions), keeping || for the current behavior for backwards compatibility. The advanges I see: (1) doesnā€™t introduce new reserved keywords; (2) same number of characters; (3) easy to change between old and new behavior; (4) anyone who generally understands the list comprehension syntax will generally understand both.

[X || X <- List]
% vs
[X of X <- List]

If thereā€™s more inertia behind new syntax to the likes of let-in or for-of-end, that puts generated expression after generating expressions, I would recommend reading SRFI 42 for inspiration, as I think itā€™s a good syntax done well. Although, of course, it wouldnā€™t be possible import all the juicy parentheses into Erlangā€¦ :wink:

1 Like

Just to clarify: I donā€™t have strong feelings pro/contra let. I just picked that one in my makeshift example because itā€™s already a reserved word, plus I felt that this proposed use is actually about introducing variables (those that the generators generate). But I understand itā€™s not the typical use of let.

To be honest, your proposal of using [... of ...] is not bad, but not my favorite either for two reasons:

  • I find putting the elements you yield at the end instead of upfront is the more natural reading order.
  • I donā€™t see how ā€œofā€ would fit into this construct? I mean what would be the plain English sentence that describes what the comprehension does and contains a prominent ā€œofā€?

If you want to replace || you could just introduce any new operator (built up from symbols) instead of a keyword. Itā€™s easy to pick an operator that is currently invalid syntax: :-), @@, <|, |||, ~| or whatever.

But Iā€™d rather add a new reserved word: maybe expressions showed us that itā€™s possible and how could one approach the problem. Then we could have generate ... in ... end, from ... build ... end or something similar that most people could accept.

1 Like

Fair point. I think of it as ā€œsuch thatā€ or ā€œof the generator [expression]ā€.
[foo(X) of X <- List] would read ā€œcreate a list with foo(X) elements of the generator [expression] X <- Listā€.

Itā€™s not that clear-cut. I read || as ā€œsuch thatā€ (similar to plain | in various mathematical constructions) and itā€™s pretty natural to me.

My point being that itā€™s just what oneā€™s used to.

2 Likes

@bjorng do you think all currently relaxed generators in the OTP codebase should be replaced with strict ones (where possible, of course)? Is there any difference in performance by using one over the other?

1 Like

(Disclaimer: I still donā€™t like this :stuck_out_tongue_closed_eyes:)

I have since seen it used in a few places in the OTP codebase, for example here in zip or here in dets_utils. Another variation of the theme can be found here in dets (the generator part could be dropped I think).

So I wonderā€¦ what does the OTP team (@bjorng?) have to say about this style (if you want to call it such)? Is this an accepted or even encouraged way of doing things?

1 Like