collect:thenDo: woes

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
10 messages Options
Reply | Threaded
Open this post in threaded view
|

collect:thenDo: woes

Stéphane Rollandin
Richard O'Keefe is making good points against the Squeak implementation
of method #collect:thenDo: in the Pharo-users list:

https://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/2019-September/044204.html

I vote for the removal of this confusing method.

Stef

Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Tobias Pape
Hi

> On 11.09.2019, at 09:42, Stéphane Rollandin <[hidden email]> wrote:
>
> Richard O'Keefe is making good points against the Squeak implementation of method #collect:thenDo: in the Pharo-users list:
>
> https://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/2019-September/044204.html

I read it. I see his point but I am attempted to disagree.

Here's my reasoning:

#collect:thenXXX: for me implies that the block of the collect-part has to abide the same constraints as a "pure" collect.

That is, if you do
        'abc' collect: [:x | x asMorph]
and this fails, then
        'abc' collect: [:x | x asMorph] thenDo: [:m | m openInHand].
MUST also fail.

Collect is bound to the kind of collection it collects over.
We have #collect:as: for kind/species-changing collects.
So to support Richard's case, one could imagine

  |o|
  o := OrderedCollection new.
  #[3 1 4 1 5 9]
    collect: [:byte | byte asFloat] as: OrderedCollection
    thenDo: [:float | o addLast: float].
  Transcript print: o; cr.

(which makes the thenDo:-part superfluous, but I understand why the original version is that way)
or for the other example:

   #[3 1 4 1 5 9]
     collect: [:byte | byte odd ifTrue: [byte printString]
                                ifFalse: [byte]]
     as: Array
     thenSelect: [:each | each isNumber]



>
> I vote for the removal of this confusing method.

[TL;DR below]


The problem the #enumerationPart:thenOtherEnumerationPart: messages try to solve is our (otherwise helpfully) slim syntax.
Because
        (((foo collect: [:x|x barf]) select: [:y|y isKnorz]) inject: Kiffle into: [:clomb :blui | clomb baz: blui]) sum
is hard to write, error-prone and not "scriptable"[1].

Marcel Weiher added syntax for this in his Objective-SmallTalk [sic] which would rather read like

        foo collect: [:x|x barf |> select: [:y|y isKnorz] |> inject: Kiffle into: [:clomb :blui | clomb baz: blui] |> sum

or with breaks:
        foo
                collect: [:x|x barf |>
                select: [:y|y isKnorz] |>
                inject: Kiffle into: [:clomb :blui | clomb baz: blui] |>
                sum

This makes it all very data-flow-y, which can be really great to understand what's going on with one's collections.

Personally, I am not too keen on the '|>' symbolism itself, because |> is just another binary operator and would break current syntax.

However, this whole thing is close enough to cascades we already got:

        forc
                gnarf: [kelp];
                klimb;
                strosn: [:f :zz | zz klept: f];
                badummdz.

And _If_ we had something like it, I'd prefer it similarly to this.
Maybe ;; (while somewhat stupid, its practical in its own right…), or use ! (no syntactical meaning yet, aside the changeset separation, but looks strange).
The ` is free but used in RefactoringBrowser's code matching and compiler error messages, so no good candidate either.

Other syntactic variant, but even more out-of-place would be colon-doubling to signal "end of keywords":


        foo
                collect:: [:x|x barf]
                select:: [:y|y isKnorz]
                inject: Kiffle into:: [:clomb :blui | clomb baz: blui])
                sum

which is just one stop before Self's syntax, which abuses capitalization (watch for the Into):

        foo
                collect: [:x|x barf]
                select: [:y|y isKnorz]
                inject: Kiffle Into: [:clomb :blui | clomb baz: blui])
                sum

Btw: With higher order messages, it would also look quite good:


        (foo
                collect barf
                select isKnorz
                "inject: Kiffle into: [:clomb :blui | clomb baz: blui]" "<-- not sure here, tho"
                inject Kiffle
                        into baz: blui)
                sum

TL;DR: the ..:then..: messages have an important role in readability, so should we abandon them, we need something else, maybe even richer syntax.

Best regards
        -Tobias



[1]: With that, I mean, you can't forward-progam here. Think you star with some collection and inspect it. Then you go back to your do it,
#collect: on it and inspect again. But then, to do the same with another, eg, #select:, first you have to put parenthesis around it, to then go to the end again and add your code. Repeat.








Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Christoph Thiede

Wow, those syntactical ideas sound really interesting. However, I think is very important to keep the Smalltalk syntax as minimal as possible, as imho this is one big advantage over other common languages like C*. Smalltalk stands out by its use of self-explaining messages instead of non-intuitive keywords. So what would you think about the following?


foo asZöglfrex
    collect: [:x|x barf];
    select: [:y|y isKnorz];
    inject: Kiffle into: [:clomb :blui | clomb baz: blui]);
    sum

(Or even override #yourself instead of using #receiver.)

The relevant implementation of Zöglfrex would be quite easy:

Object subclass: #Zöglfrex
    instanceVariableNames: 'receiver'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Kernel-Objects'

Zöglfrex >> doesNotUnderstand: aMessage
    ^ receiver := aMessage sendTo: receiver


Best,

Christoph


Von: Squeak-dev <[hidden email]> im Auftrag von Tobias Pape <[hidden email]>
Gesendet: Mittwoch, 11. September 2019 10:52:39
An: The general-purpose Squeak developers list
Betreff: Re: [squeak-dev] collect:thenDo: woes
 
Hi

> On 11.09.2019, at 09:42, Stéphane Rollandin <[hidden email]> wrote:
>
> Richard O'Keefe is making good points against the Squeak implementation of method #collect:thenDo: in the Pharo-users list:
>
> https://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/2019-September/044204.html

I read it. I see his point but I am attempted to disagree.

Here's my reasoning:

#collect:thenXXX: for me implies that the block of the collect-part has to abide the same constraints as a "pure" collect.

That is, if you do
        'abc' collect: [:x | x asMorph]
and this fails, then
        'abc' collect: [:x | x asMorph] thenDo: [:m | m openInHand].
MUST also fail.

Collect is bound to the kind of collection it collects over.
We have #collect:as: for kind/species-changing collects.
So to support Richard's case, one could imagine

  |o|
  o := OrderedCollection new.
  #[3 1 4 1 5 9]
    collect: [:byte | byte asFloat] as: OrderedCollection
    thenDo: [:float | o addLast: float].
  Transcript print: o; cr.

(which makes the thenDo:-part superfluous, but I understand why the original version is that way)
or for the other example:

   #[3 1 4 1 5 9]
     collect: [:byte | byte odd ifTrue: [byte printString]
                                ifFalse: [byte]]
     as: Array
     thenSelect: [:each | each isNumber]



>
> I vote for the removal of this confusing method.

[TL;DR below]


The problem the #enumerationPart:thenOtherEnumerationPart: messages try to solve is our (otherwise helpfully) slim syntax.
Because
        (((foo collect: [:x|x barf]) select: [:y|y isKnorz]) inject: Kiffle into: [:clomb :blui | clomb baz: blui]) sum
is hard to write, error-prone and not "scriptable"[1].

Marcel Weiher added syntax for this in his Objective-SmallTalk [sic] which would rather read like

        foo collect: [:x|x barf |> select: [:y|y isKnorz] |> inject: Kiffle into: [:clomb :blui | clomb baz: blui] |> sum

or with breaks:
        foo
                collect: [:x|x barf |>
                select: [:y|y isKnorz] |>
                inject: Kiffle into: [:clomb :blui | clomb baz: blui] |>
                sum

This makes it all very data-flow-y, which can be really great to understand what's going on with one's collections.

Personally, I am not too keen on the '|>' symbolism itself, because |> is just another binary operator and would break current syntax.

However, this whole thing is close enough to cascades we already got:

        forc
                gnarf: [kelp];
                klimb;
                strosn: [:f :zz | zz klept: f];
                badummdz.

And _If_ we had something like it, I'd prefer it similarly to this.
Maybe ;; (while somewhat stupid, its practical in its own right…), or use ! (no syntactical meaning yet, aside the changeset separation, but looks strange).
The ` is free but used in RefactoringBrowser's code matching and compiler error messages, so no good candidate either.

Other syntactic variant, but even more out-of-place would be colon-doubling to signal "end of keywords":


        foo
                collect:: [:x|x barf]
                select:: [:y|y isKnorz]
                inject: Kiffle into:: [:clomb :blui | clomb baz: blui])
                sum

which is just one stop before Self's syntax, which abuses capitalization (watch for the Into):

        foo
                collect: [:x|x barf]
                select: [:y|y isKnorz]
                inject: Kiffle Into: [:clomb :blui | clomb baz: blui])
                sum

Btw: With higher order messages, it would also look quite good:


        (foo
                collect barf
                select isKnorz
                "inject: Kiffle into: [:clomb :blui | clomb baz: blui]" "<-- not sure here, tho"
                inject Kiffle
                        into baz: blui)
                sum

TL;DR: the ..:then..: messages have an important role in readability, so should we abandon them, we need something else, maybe even richer syntax.

Best regards
        -Tobias



[1]: With that, I mean, you can't forward-progam here. Think you star with some collection and inspect it. Then you go back to your do it,
#collect: on it and inspect again. But then, to do the same with another, eg, #select:, first you have to put parenthesis around it, to then go to the end again and add your code. Repeat.










Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Stéphane Rollandin
Guys I don't know where you went, but the rationale for getting rid of
#collect:thenDo: is simply that it is syntactic sugar only (just hiding
two parenthesis), and that it makes it at the same time pretty much
useless and very much misleading depending on the receiver, as per
Richard's discussion.

I am not proposing implementing any new thing, just removing that method.


Stef

Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

marcel.taeumel
Hi, all.


Let me try to extract the arguments for and against Collection >> #collect:thenDo: (and similar).

Pro
- works with most kinds of Collection
- in the source code, one less pair of parentheses needed
- same semantics as (... collect: [...]) do: [:ea | ...] ... (because it reads almost the same)
- in the source code, the word "then" supports readability (or reading flow) a bit

Contra
- the current implementation creates an extra collection (-> easy to change)
- no inherent support for #collect:as: to configure the target/intermediate kind (-> maybe irrelevant?)
- having all combinations would blow up the interface: do/collect/select/reject/inject ... collect:thenInject:into:thenReject:thenDo: ... (-> big issue)

I think that changing the implementation of #collect:thenDo: to not behave like the "expanded version" -- (... collect: [...]) do: [:ea | ...]  -- could be quite confusing. Take the following example:

#(1 2 3 4) asSet
   collect: [:num | num  even]
   thenDo: [:isEven | Transcript show: isEven].

I think that this snippet should output "falsetrue" (or "truefalse") but not "falsetruefalsetrue" (or the other combinations). Otherwise it would be sth. like #do:afterCollectEach: ... brrrrr ... Same for a SortedCollection where the sort order depends on (or changes with) the values *after* the collect operation.

After reading Richard's discussion, I do still not understand, when (and how) #collect:thenDo: could be misleading. At least not the way Squeak is implementing it at the moment. It is just syntactic sugar for collect;do. We have plenty of that kind of sugar. :-)

Best,
Marcel

Am 11.09.2019 15:14:26 schrieb Stéphane Rollandin <[hidden email]>:

Guys I don't know where you went, but the rationale for getting rid of
#collect:thenDo: is simply that it is syntactic sugar only (just hiding
two parenthesis), and that it makes it at the same time pretty much
useless and very much misleading depending on the receiver, as per
Richard's discussion.

I am not proposing implementing any new thing, just removing that method.


Stef



Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Levente Uzonyi
In reply to this post by Stéphane Rollandin
On Wed, 11 Sep 2019, Stéphane Rollandin wrote:

> Richard O'Keefe is making good points against the Squeak implementation
> of method #collect:thenDo: in the Pharo-users list:

I disagree.

His idea is that #collect:thenDo: ought to work differently, calling
Squeak's implementation "pointless", because it is what its name suggests:
#collect: followed by #do:. But there's a small twist: the collected
values are returned.

Then he brings up the red herring: examples where #collect: does something
a naive user would not expect, and where #collect:as: should be used. Then
blames it all on #collect:thenDo:.

Next, he shows how good those examples work in Pharo, with the new
implementation. Even though he forgets to point out that the new
implementation has different semantics (when your blocks have side
effects) and different return value (because it doesn't create an
intermediate collection to return), which can break your code[1].

In the summary, he writes that "[the Squeak definition] does not improve
readability. In fact, it REDUCES readability". I think having a fewer
parentheses can help with legibility.
He also writes "[the Squeak definition] is useless with most collection
classes.". I somewhat agree with this. I wouldn't use it in a method, but
it sometimes comes handy in a workspace script (just like #in:).
Finally, "[the Squeak definition] does nothing to improve performance
but just adds overhead.". I suppose no one wanted to improve performance
with that method.

>
> https://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/2019-September/044204.html
>
> I vote for the removal of this confusing method.

I'm not against deprecating it, because every now and then it brings up a
similar discussion, but then all other methods of the same kind (*:then*:)
must go as well from the collections.


Levente
[1] http://forum.world.st/Bad-reimplementation-of-Collection-gt-gt-collect-thenDo-in-Pharo-3-0-td4760774.html

>
> Stef
>
>

Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Chris Muller-3
In reply to this post by Christoph Thiede

Wow, those syntactical ideas sound really interesting. However, I think is very important to keep the Smalltalk syntax as minimal as possible,

I'm glad to hear you say that, Christoph, because I feel the Collection class>>#new:filledWith: proposal falls into the same category as this -- too many issues.

As a development community, I think we should consider moving some of these "syntactic sugar" method ideas into a new "Collections-Extensions" package which could be safely unloaded (e.g., no core code would depend on it).

as imho this is one big advantage over other common languages like C*. Smalltalk stands out by its use of self-explaining messages instead of non-intuitive keywords. So what would you think about the following?


foo asZöglfrex
    collect: [:x|x barf];
    select: [:y|y isKnorz];
    inject: Kiffle into: [:clomb :blui | clomb baz: blui]);
    sum

I don't understand this aversion to parentheses.  They're the universal symbol of precedence, not only universally readable, but thanks to Squeak's built-in "expression editing" capabilities, enable a more powerful way to edit code than the sugary syntax that typically leave you "editing text" instead of expressions. 

I generally don't care for added syntactic sugar that serves only to reduce parentheses.  It's not worth the API explosion or costs in portability.

+1 to deprecate them or move them to an unloadable Collections-Extensions.

Best,
  Chris

 

(Or even override #yourself instead of using #receiver.)

The relevant implementation of Zöglfrex would be quite easy:

Object subclass: #Zöglfrex
    instanceVariableNames: 'receiver'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Kernel-Objects'

Zöglfrex >> doesNotUnderstand: aMessage
    ^ receiver := aMessage sendTo: receiver


Best,

Christoph


Von: Squeak-dev <[hidden email]> im Auftrag von Tobias Pape <[hidden email]>
Gesendet: Mittwoch, 11. September 2019 10:52:39
An: The general-purpose Squeak developers list
Betreff: Re: [squeak-dev] collect:thenDo: woes
 
Hi

> On 11.09.2019, at 09:42, Stéphane Rollandin <[hidden email]> wrote:
>
> Richard O'Keefe is making good points against the Squeak implementation of method #collect:thenDo: in the Pharo-users list:
>
> https://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/2019-September/044204.html

I read it. I see his point but I am attempted to disagree.

Here's my reasoning:

#collect:thenXXX: for me implies that the block of the collect-part has to abide the same constraints as a "pure" collect.

That is, if you do
        'abc' collect: [:x | x asMorph]
and this fails, then
        'abc' collect: [:x | x asMorph] thenDo: [:m | m openInHand].
MUST also fail.

Collect is bound to the kind of collection it collects over.
We have #collect:as: for kind/species-changing collects.
So to support Richard's case, one could imagine

  |o|
  o := OrderedCollection new.
  #[3 1 4 1 5 9]
    collect: [:byte | byte asFloat] as: OrderedCollection
    thenDo: [:float | o addLast: float].
  Transcript print: o; cr.

(which makes the thenDo:-part superfluous, but I understand why the original version is that way)
or for the other example:

   #[3 1 4 1 5 9]
     collect: [:byte | byte odd ifTrue: [byte printString]
                                ifFalse: [byte]]
     as: Array
     thenSelect: [:each | each isNumber]



>
> I vote for the removal of this confusing method.

[TL;DR below]


The problem the #enumerationPart:thenOtherEnumerationPart: messages try to solve is our (otherwise helpfully) slim syntax.
Because
        (((foo collect: [:x|x barf]) select: [:y|y isKnorz]) inject: Kiffle into: [:clomb :blui | clomb baz: blui]) sum
is hard to write, error-prone and not "scriptable"[1].

Marcel Weiher added syntax for this in his Objective-SmallTalk [sic] which would rather read like

        foo collect: [:x|x barf |> select: [:y|y isKnorz] |> inject: Kiffle into: [:clomb :blui | clomb baz: blui] |> sum

or with breaks:
        foo
                collect: [:x|x barf |>
                select: [:y|y isKnorz] |>
                inject: Kiffle into: [:clomb :blui | clomb baz: blui] |>
                sum

This makes it all very data-flow-y, which can be really great to understand what's going on with one's collections.

Personally, I am not too keen on the '|>' symbolism itself, because |> is just another binary operator and would break current syntax.

However, this whole thing is close enough to cascades we already got:

        forc
                gnarf: [kelp];
                klimb;
                strosn: [:f :zz | zz klept: f];
                badummdz.

And _If_ we had something like it, I'd prefer it similarly to this.
Maybe ;; (while somewhat stupid, its practical in its own right…), or use ! (no syntactical meaning yet, aside the changeset separation, but looks strange).
The ` is free but used in RefactoringBrowser's code matching and compiler error messages, so no good candidate either.

Other syntactic variant, but even more out-of-place would be colon-doubling to signal "end of keywords":


        foo
                collect:: [:x|x barf]
                select:: [:y|y isKnorz]
                inject: Kiffle into:: [:clomb :blui | clomb baz: blui])
                sum

which is just one stop before Self's syntax, which abuses capitalization (watch for the Into):

        foo
                collect: [:x|x barf]
                select: [:y|y isKnorz]
                inject: Kiffle Into: [:clomb :blui | clomb baz: blui])
                sum

Btw: With higher order messages, it would also look quite good:


        (foo
                collect barf
                select isKnorz
                "inject: Kiffle into: [:clomb :blui | clomb baz: blui]" "<-- not sure here, tho"
                inject Kiffle
                        into baz: blui)
                sum

TL;DR: the ..:then..: messages have an important role in readability, so should we abandon them, we need something else, maybe even richer syntax.

Best regards
        -Tobias



[1]: With that, I mean, you can't forward-progam here. Think you star with some collection and inspect it. Then you go back to your do it,
#collect: on it and inspect again. But then, to do the same with another, eg, #select:, first you have to put parenthesis around it, to then go to the end again and add your code. Repeat.











Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Christoph Thiede

I'm glad to hear you say that, Christoph, because I feel the Collection class>>#new:filledWith: proposal falls into the same category as this -- too many issues.


Hm, but this does not change any syntactical aspects? There were no reactions on Collections-mt.852 so far - I would be curious what the issues are!

I don't understand this aversion to parentheses.

Parentheses are not bad at all, but when your code becomes really lispy, I think the parentheses concept has reached its limits. Consider SQL or LINQ which do not indent each line to another level.
I do not find it easy to read this expression:

((((foo
    collect: [:x|x barf])
    select: [:y|y isKnorz])
    inject: Kiffle into: [:clomb :blui | clomb baz: blui])
    reduce: [:z :w | z plong: w])
    sum

Yes, you can use indentations, but the readability is still suboptimal:

((((foo collect: [:x|x barf])
    select: [:y|y isKnorz])
        inject: Kiffle into: [:clomb :blui | clomb baz: blui])
            reduce: [:z :w | z plong: w])
                sum

Imho brackets are the one concept and pipelines/queries are another one. If you would like to concatenate really many expressions, brackets turn out not to be "sustainable" any longer, as each additional expression costs a new pair of brackets and a new indentation. So the parenthesis costs two dimensions of space where pipelines only need one, what makes it really hard to insert another dimension into the bracket monster:

((((foo collect: [:x|x barf])
    select: [:y|y isKnorz])
        inject: Kiffle into: [:clomb :blui |
            (clomb baz: [:deng | deng ploof: blui in: Roz])
                with: blui collect: [:zi :rad | zi pol: rad]])
                    reduce: [:z :w | z plong: w])
                        sum

(Where is the inner expression??)
In contrast:

foo asZöglfrex
    collect: [:x|x barf];
    select: [:y|y isKnorz];
    inject: Kiffle into: [:clomb :blui |
        clomb asZöglfrex
            baz: [:deng | deng ploof: blui in: Roz];
            with: blui collect: [:zi :rad | zi pol: rad]];
    reduce: [:z :w | z plong: w];
    sum

Best,
Christoph


Von: Squeak-dev <[hidden email]> im Auftrag von Chris Muller <[hidden email]>
Gesendet: Donnerstag, 12. September 2019 00:38:46
An: The general-purpose Squeak developers list
Betreff: Re: [squeak-dev] collect:thenDo: woes
 

Wow, those syntactical ideas sound really interesting. However, I think is very important to keep the Smalltalk syntax as minimal as possible,

I'm glad to hear you say that, Christoph, because I feel the Collection class>>#new:filledWith: proposal falls into the same category as this -- too many issues.

As a development community, I think we should consider moving some of these "syntactic sugar" method ideas into a new "Collections-Extensions" package which could be safely unloaded (e.g., no core code would depend on it).

as imho this is one big advantage over other common languages like C*. Smalltalk stands out by its use of self-explaining messages instead of non-intuitive keywords. So what would you think about the following?


foo asZöglfrex
    collect: [:x|x barf];
    select: [:y|y isKnorz];
    inject: Kiffle into: [:clomb :blui | clomb baz: blui]);
    sum

I don't understand this aversion to parentheses.  They're the universal symbol of precedence, not only universally readable, but thanks to Squeak's built-in "expression editing" capabilities, enable a more powerful way to edit code than the sugary syntax that typically leave you "editing text" instead of expressions. 

I generally don't care for added syntactic sugar that serves only to reduce parentheses.  It's not worth the API explosion or costs in portability.

+1 to deprecate them or move them to an unloadable Collections-Extensions.

Best,
  Chris

 

(Or even override #yourself instead of using #receiver.)

The relevant implementation of Zöglfrex would be quite easy:

Object subclass: #Zöglfrex
    instanceVariableNames: 'receiver'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'Kernel-Objects'

Zöglfrex >> doesNotUnderstand: aMessage
    ^ receiver := aMessage sendTo: receiver


Best,

Christoph


Von: Squeak-dev <[hidden email]> im Auftrag von Tobias Pape <[hidden email]>
Gesendet: Mittwoch, 11. September 2019 10:52:39
An: The general-purpose Squeak developers list
Betreff: Re: [squeak-dev] collect:thenDo: woes
 
Hi

> On 11.09.2019, at 09:42, Stéphane Rollandin <[hidden email]> wrote:
>
> Richard O'Keefe is making good points against the Squeak implementation of method #collect:thenDo: in the Pharo-users list:
>
> https://lists.pharo.org/pipermail/pharo-users_lists.pharo.org/2019-September/044204.html

I read it. I see his point but I am attempted to disagree.

Here's my reasoning:

#collect:thenXXX: for me implies that the block of the collect-part has to abide the same constraints as a "pure" collect.

That is, if you do
        'abc' collect: [:x | x asMorph]
and this fails, then
        'abc' collect: [:x | x asMorph] thenDo: [:m | m openInHand].
MUST also fail.

Collect is bound to the kind of collection it collects over.
We have #collect:as: for kind/species-changing collects.
So to support Richard's case, one could imagine

  |o|
  o := OrderedCollection new.
  #[3 1 4 1 5 9]
    collect: [:byte | byte asFloat] as: OrderedCollection
    thenDo: [:float | o addLast: float].
  Transcript print: o; cr.

(which makes the thenDo:-part superfluous, but I understand why the original version is that way)
or for the other example:

   #[3 1 4 1 5 9]
     collect: [:byte | byte odd ifTrue: [byte printString]
                                ifFalse: [byte]]
     as: Array
     thenSelect: [:each | each isNumber]



>
> I vote for the removal of this confusing method.

[TL;DR below]


The problem the #enumerationPart:thenOtherEnumerationPart: messages try to solve is our (otherwise helpfully) slim syntax.
Because
        (((foo collect: [:x|x barf]) select: [:y|y isKnorz]) inject: Kiffle into: [:clomb :blui | clomb baz: blui]) sum
is hard to write, error-prone and not "scriptable"[1].

Marcel Weiher added syntax for this in his Objective-SmallTalk [sic] which would rather read like

        foo collect: [:x|x barf |> select: [:y|y isKnorz] |> inject: Kiffle into: [:clomb :blui | clomb baz: blui] |> sum

or with breaks:
        foo
                collect: [:x|x barf |>
                select: [:y|y isKnorz] |>
                inject: Kiffle into: [:clomb :blui | clomb baz: blui] |>
                sum

This makes it all very data-flow-y, which can be really great to understand what's going on with one's collections.

Personally, I am not too keen on the '|>' symbolism itself, because |> is just another binary operator and would break current syntax.

However, this whole thing is close enough to cascades we already got:

        forc
                gnarf: [kelp];
                klimb;
                strosn: [:f :zz | zz klept: f];
                badummdz.

And _If_ we had something like it, I'd prefer it similarly to this.
Maybe ;; (while somewhat stupid, its practical in its own right…), or use ! (no syntactical meaning yet, aside the changeset separation, but looks strange).
The ` is free but used in RefactoringBrowser's code matching and compiler error messages, so no good candidate either.

Other syntactic variant, but even more out-of-place would be colon-doubling to signal "end of keywords":


        foo
                collect:: [:x|x barf]
                select:: [:y|y isKnorz]
                inject: Kiffle into:: [:clomb :blui | clomb baz: blui])
                sum

which is just one stop before Self's syntax, which abuses capitalization (watch for the Into):

        foo
                collect: [:x|x barf]
                select: [:y|y isKnorz]
                inject: Kiffle Into: [:clomb :blui | clomb baz: blui])
                sum

Btw: With higher order messages, it would also look quite good:


        (foo
                collect barf
                select isKnorz
                "inject: Kiffle into: [:clomb :blui | clomb baz: blui]" "<-- not sure here, tho"
                inject Kiffle
                        into baz: blui)
                sum

TL;DR: the ..:then..: messages have an important role in readability, so should we abandon them, we need something else, maybe even richer syntax.

Best regards
        -Tobias



[1]: With that, I mean, you can't forward-progam here. Think you star with some collection and inspect it. Then you go back to your do it,
#collect: on it and inspect again. But then, to do the same with another, eg, #select:, first you have to put parenthesis around it, to then go to the end again and add your code. Repeat.











Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Chris Muller-4
Hi Christoph,

Parentheses are not bad at all, but when your code becomes really lispy, I think the parentheses concept has reached its limits.

What I think has actually reached its limits is passive reading of textual code, instead of active reading of tile-based code like Scratch.  By active, I mean using the interface to delineate the nodes in the hierarchy of expressions, as you read and digest the method.

As it turns out, there is a particular combination of settings in Squeak (I really need to document them as a set...) that enables fairly rich expression-editing in the standard Squeak editor.  It involves a couple of trade-offs with the conventions of other text editors, but the payoff is huge.  In this environment the parentheses actually assist the active reading of the code.  Everyone knows about selecting expressions by clicking just inside the parentheses.  Another is Command+[space].  Still another assist is that the first 8 or so levels of parentheses can each be set to a unique color.  One last trick is to place the text cursor in front of a particular closing bracket, to know its corresponding opening, simply press that closing bracket key to highlight its corresponding opening bracket.  Just an alternative way to delineate the expression.

Consider SQL or LINQ which do not indent each line to another level.
I do not find it easy to read this expression:

((((foo
    collect: [:x|x barf])
    select: [:y|y isKnorz])
    inject: Kiffle into: [:clomb :blui | clomb baz: blui])
    reduce: [:z :w | z plong: w])
    sum

Yes, you can use indentations, but the readability is still suboptimal:

((((foo collect: [:x|x barf])
    select: [:y|y isKnorz])
        inject: Kiffle into: [:clomb :blui | clomb baz: blui])
            reduce: [:z :w | z plong: w])
                sum

Imho brackets are the one concept and pipelines/queries are another one.
 
 
If you would like to concatenate really many expressions, brackets turn out not to be "sustainable" any longer, as each additional expression costs a new pair of brackets and a new indentation. So the parenthesis costs two dimensions of space where pipelines only need one, what makes it really hard to insert another dimension into the bracket monster:

((((foo collect: [:x|x barf])
    select: [:y|y isKnorz])
        inject: Kiffle into: [:clomb :blui |
            (clomb baz: [:deng | deng ploof: blui in: Roz])
                with: blui collect: [:zi :rad | zi pol: rad]])
                    reduce: [:z :w | z plong: w])
                        sum

(Where is the inner expression??)
In contrast:

foo asZöglfrex
    collect: [:x|x barf];
    select: [:y|y isKnorz];
    inject: Kiffle into: [:clomb :blui |
        clomb asZöglfrex
            baz: [:deng | deng ploof: blui in: Roz];
            with: blui collect: [:zi :rad | zi pol: rad]];
    reduce: [:z :w | z plong: w];
    sum

Active reading improves the bandwidth of my understanding of code better than passive reading of the code as a stream of formatted text -- even when that stream of code is sweetened in some way to try to hide the precedence.  I think a hierarchy of expressions is easier to see and understand than a sequence of outputs piped to inputs.  The former mirrors the structure of the language.  The latter can only be fully understood by reading the entire sequence.

Best,
  Chris
 


Reply | Threaded
Open this post in threaded view
|

Re: collect:thenDo: woes

Christoph Thiede

Hi Chris,


thanks for the <cmd>Space tip, this is great! (Btw, I would find it quite useful to press this combination again and again to select each time the parent expression up to the root -- similar to shift + blue button on morphs.)


I see your argument of hierarchic expressions, and I would not use something like zöglfrex in common Smalltalk code. However, I would find it irritating to read long SQL queries in a triangle layout, so finally I think it remains a domain dependent question. :)


Best,

Christoph


Von: Chris Muller <[hidden email]>
Gesendet: Donnerstag, 12. September 2019 05:15:49
An: Thiede, Christoph
Cc: The general-purpose Squeak developers list
Betreff: Re: [squeak-dev] collect:thenDo: woes
 
Hi Christoph,

Parentheses are not bad at all, but when your code becomes really lispy, I think the parentheses concept has reached its limits.

What I think has actually reached its limits is passive reading of textual code, instead of active reading of tile-based code like Scratch.  By active, I mean using the interface to delineate the nodes in the hierarchy of expressions, as you read and digest the method.

As it turns out, there is a particular combination of settings in Squeak (I really need to document them as a set...) that enables fairly rich expression-editing in the standard Squeak editor.  It involves a couple of trade-offs with the conventions of other text editors, but the payoff is huge.  In this environment the parentheses actually assist the active reading of the code.  Everyone knows about selecting expressions by clicking just inside the parentheses.  Another is Command+[space].  Still another assist is that the first 8 or so levels of parentheses can each be set to a unique color.  One last trick is to place the text cursor in front of a particular closing bracket, to know its corresponding opening, simply press that closing bracket key to highlight its corresponding opening bracket.  Just an alternative way to delineate the expression.

Consider SQL or LINQ which do not indent each line to another level.
I do not find it easy to read this expression:

((((foo
    collect: [:x|x barf])
    select: [:y|y isKnorz])
    inject: Kiffle into: [:clomb :blui | clomb baz: blui])
    reduce: [:z :w | z plong: w])
    sum

Yes, you can use indentations, but the readability is still suboptimal:

((((foo collect: [:x|x barf])
    select: [:y|y isKnorz])
        inject: Kiffle into: [:clomb :blui | clomb baz: blui])
            reduce: [:z :w | z plong: w])
                sum

Imho brackets are the one concept and pipelines/queries are another one.
 
 
If you would like to concatenate really many expressions, brackets turn out not to be "sustainable" any longer, as each additional expression costs a new pair of brackets and a new indentation. So the parenthesis costs two dimensions of space where pipelines only need one, what makes it really hard to insert another dimension into the bracket monster:

((((foo collect: [:x|x barf])
    select: [:y|y isKnorz])
        inject: Kiffle into: [:clomb :blui |
            (clomb baz: [:deng | deng ploof: blui in: Roz])
                with: blui collect: [:zi :rad | zi pol: rad]])
                    reduce: [:z :w | z plong: w])
                        sum

(Where is the inner expression??)
In contrast:

foo asZöglfrex
    collect: [:x|x barf];
    select: [:y|y isKnorz];
    inject: Kiffle into: [:clomb :blui |
        clomb asZöglfrex
            baz: [:deng | deng ploof: blui in: Roz];
            with: blui collect: [:zi :rad | zi pol: rad]];
    reduce: [:z :w | z plong: w];
    sum

Active reading improves the bandwidth of my understanding of code better than passive reading of the code as a stream of formatted text -- even when that stream of code is sweetened in some way to try to hide the precedence.  I think a hierarchy of expressions is easier to see and understand than a sequence of outputs piped to inputs.  The former mirrors the structure of the language.  The latter can only be fully understood by reading the entire sequence.

Best,
  Chris