pharo-users@lists.pharo.org

Any question about pharo is welcome

View all threads

Re: [vwnc] Block evaluation with n+1 arguments

RS
Richard Sargent
Mon, Apr 10, 2023 4:24 PM

Excellent write up, Stephane!

I will add that over the years, there have been many times (countless!) when developing/debugging involved a complex block and that turned out to be significant nuisance.

I can elaborate on the details, but once a block gets passed around, revising it on the fly breaks debug and continue.

Having a block comprise a single message send allows one to revise that method as and when needed.

From: vwnc-request@lists.cs.illinois.edu vwnc-request@lists.cs.illinois.edu On Behalf Of stephane.ducasse@free.fr
Sent: April 10, 2023 07:03
To: Any question about pharo is welcome pharo-users@lists.pharo.org
Cc: vwnc@lists.cs.illinois.edu
Subject: Re: [vwnc] [Pharo-users] Block evaluation with n+1 arguments

BTW to me when a block needs too many arguments it feels like that an object has to be born :)

With an object I can just sent or not a given extra argument.

Now I do not know enough your specific context but what I learned is that complex blocks are difficult to follow, manipulate…

so I keep block as simple as possible and else I create little objects.

This is a little lectures from a super cool forthcoming mooc

            https://rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf <https://urldefense.com/v3/__https:/rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf__;!!DZ3fjg!7bRnSLpPY1OiW_EoNtvSDBVMfYN5owvLQjTs_FdNbynPLMw7hmdZCNifkawlO9Hz-NbVkGnnh37u3XIaEJEAHjFBHHppJxSeBgY$> 

On 6 Apr 2023, at 15:28, Steffen Märcker <merkste@web.de mailto:merkste@web.de > wrote:

Hi!

I want to evaluate a block an argument 'arg1' and additional n arguments
given in an array 'args'. The following code does the trick:

block valueWithArguments: (Array with: arg1) , args.

Is there a way to do this without the overhead of creating a new Array?
(How) Can I add additional #value:value:[...] methods to BlockClosure that
evaluate the block with n arguments directly without falling back to
#valueWithArguments: ? If yes, what's the maximum?

Cheers!
Steffen

Excellent write up, Stephane! I will add that over the years, there have been many times (countless!) when developing/debugging involved a complex block and that turned out to be significant nuisance. I can elaborate on the details, but once a block gets passed around, revising it on the fly breaks debug and continue. Having a block comprise a single message send allows one to revise that method as and when needed. From: vwnc-request@lists.cs.illinois.edu <vwnc-request@lists.cs.illinois.edu> On Behalf Of stephane.ducasse@free.fr Sent: April 10, 2023 07:03 To: Any question about pharo is welcome <pharo-users@lists.pharo.org> Cc: vwnc@lists.cs.illinois.edu Subject: Re: [vwnc] [Pharo-users] Block evaluation with n+1 arguments BTW to me when a block needs too many arguments it feels like that an object has to be born :) With an object I can just sent or not a given extra argument. Now I do not know enough your specific context but what I learned is that complex blocks are difficult to follow, manipulate… so I keep block as simple as possible and else I create little objects. This is a little lectures from a super cool forthcoming mooc https://rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf <https://urldefense.com/v3/__https:/rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf__;!!DZ3fjg!7bRnSLpPY1OiW_EoNtvSDBVMfYN5owvLQjTs_FdNbynPLMw7hmdZCNifkawlO9Hz-NbVkGnnh37u3XIaEJEAHjFBHHppJxSeBgY$> On 6 Apr 2023, at 15:28, Steffen Märcker <merkste@web.de <mailto:merkste@web.de> > wrote: Hi! I want to evaluate a block an argument 'arg1' and additional n arguments given in an array 'args'. The following code does the trick: block valueWithArguments: (Array with: arg1) , args. Is there a way to do this without the overhead of creating a new Array? (How) Can I add additional #value:value:[...] methods to BlockClosure that evaluate the block with n arguments directly without falling back to #valueWithArguments: ? If yes, what's the maximum? Cheers! Steffen
RO
Richard O'Keefe
Tue, Apr 11, 2023 4:52 AM

From the slides:
"What is the difference between a block and a simple object understanding
#value?"
The slides give the wrong answer.  The right answer is that

  • a block can see the variables of the blocks, method, and object
    containing it
    which allows ENCAPSULATION of long term and short term state
  • an object cannot see those variables, forcing long term and short term
    state
    to be dispersed to other objects, harming encapsulation.

The slides also say
" imagine passing a block around and want to accumulate information
◦  you can't!"
which is of course not really so.  Consider
|b s|
b := [:state :argument | state nextPut: argument].
s := Summary new.
#(3 1 4 1 5 9) do: (b bindFirst: s).
s printOn: Transcript. Transcript cr.
That's actual working code and the output is
Summary(count: 6 min: 1 max: 9 mean: 3.833333333333333 sd:
2.994439290863428 skew: 1.044066600910494)
Far from "you can't", it's dead simple.
My library includes
#bindFirst:[bindSecond:[bindThird:]] #bindSecond:[bindThird:] #bindThird:
for binding any subset of the first three arguments of a block.
So far that's been enough.

The slides further say
"Adding behavior (i.e., offering another message) is impossible"
which is sort of true but misleading: you cannot add additional behaviour
to an individual block, but you CAN add additional behaviour to the class
it is an instance of.  Which is how come I have #value:valueWithArguments:
#bindFirst: and its relatives, and a host of other combinators.  It really
really
helps that I have a separate class for each block arity.  For example, I
have
about 20 non-ANSI methods on TriadicBlock but about 50 on DyadicBlock.
The additional 30 on DyadicBlock would make no sense for TriadicBlock,
and smushing everything up into a single Block class would not work well.

That whole set of slides reads like a hymn of praise to Java nested classes,
which can do things blocks can't but on the other hand can't do things that
blocks can.

The issue we are discussing in this thread, of course, is not blocks as
such.
It isn't even blocks with many arguments.  In
aBlock value: firstArgument valueWithArguments: restArguments
we have been given no reason to suppose that restArguments is long.

I still have a couple of stickers that Brian Marick gave me, reading

AN EXAMPLE WOULD BE GOOD ABOUT NOW.

I'd still like to know what the original poster wanted to DO with

value: firstArgument value: restArguments
^self valueWithArguments: {firstArgument} , restArguments

On Tue, 11 Apr 2023 at 04:25, Richard Sargent rsargent@5x5.on.ca wrote:

Excellent write up, Stephane!

I will add that over the years, there have been many times (countless!)
when developing/debugging involved a complex block and that turned out to
be significant nuisance.

I can elaborate on the details, but once a block gets passed around,
revising it on the fly breaks debug and continue.

Having a block comprise a single message send allows one to revise that
method as and when needed.

From: vwnc-request@lists.cs.illinois.edu <
vwnc-request@lists.cs.illinois.edu> *On Behalf Of *
stephane.ducasse@free.fr
Sent: April 10, 2023 07:03
To: Any question about pharo is welcome pharo-users@lists.pharo.org
Cc: vwnc@lists.cs.illinois.edu
Subject: Re: [vwnc] [Pharo-users] Block evaluation with n+1 arguments

BTW to me when a block needs too many arguments it feels like that an
object has to be born :)

With an object I can just sent or not a given extra argument.

Now I do not know enough your specific context but what I learned is that
complex blocks are difficult to follow, manipulate…

so I keep block as simple as possible and else I create little objects.

This is a little lectures from a super cool forthcoming mooc

https://rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf
https://urldefense.com/v3/__https:/rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf__;!!DZ3fjg!7bRnSLpPY1OiW_EoNtvSDBVMfYN5owvLQjTs_FdNbynPLMw7hmdZCNifkawlO9Hz-NbVkGnnh37u3XIaEJEAHjFBHHppJxSeBgY$

On 6 Apr 2023, at 15:28, Steffen Märcker merkste@web.de wrote:

Hi!

I want to evaluate a block an argument 'arg1' and additional n arguments
given in an array 'args'. The following code does the trick:

block valueWithArguments: (Array with: arg1) , args.

Is there a way to do this without the overhead of creating a new Array?
(How) Can I add additional #value:value:[...] methods to BlockClosure that
evaluate the block with n arguments directly without falling back to
#valueWithArguments: ? If yes, what's the maximum?

Cheers!
Steffen

From the slides: "What is the difference between a block and a simple object understanding #value?" The slides give the wrong answer. The right answer is that - a block can see the variables of the blocks, method, and object containing it which allows ENCAPSULATION of long term and short term state - an object cannot see those variables, forcing long term and short term state to be dispersed to other objects, harming encapsulation. The slides also say " imagine passing a block around and want to accumulate information ◦ you can't!" which is of course not really so. Consider |b s| b := [:state :argument | state nextPut: argument]. s := Summary new. #(3 1 4 1 5 9) do: (b bindFirst: s). s printOn: Transcript. Transcript cr. That's actual working code and the output is Summary(count: 6 min: 1 max: 9 mean: 3.833333333333333 sd: 2.994439290863428 skew: 1.044066600910494) Far from "you can't", it's dead simple. My library includes #bindFirst:[bindSecond:[bindThird:]] #bindSecond:[bindThird:] #bindThird: for binding any subset of the first three arguments of a block. So far that's been enough. The slides further say "Adding behavior (i.e., offering another message) is impossible" which is sort of true but misleading: you cannot add additional behaviour to *an individual block*, but you CAN add additional behaviour to the class it is an instance of. Which is how come I have #value:valueWithArguments: #bindFirst: and its relatives, and a host of other combinators. It really really helps that I have a separate class for each block arity. For example, I have about 20 non-ANSI methods on TriadicBlock but about 50 on DyadicBlock. The additional 30 on DyadicBlock would make no sense for TriadicBlock, and smushing everything up into a single Block class would not work well. That whole set of slides reads like a hymn of praise to Java nested classes, which can do things blocks can't but on the other hand can't do things that blocks can. The issue we are discussing in this thread, of course, is not blocks as such. It isn't even blocks with many arguments. In aBlock value: firstArgument valueWithArguments: restArguments we have been given no reason to suppose that restArguments is long. I still have a couple of stickers that Brian Marick gave me, reading AN EXAMPLE WOULD BE GOOD ABOUT NOW. I'd still like to know what the original poster wanted to DO with value: firstArgument value: restArguments ^self valueWithArguments: {firstArgument} , restArguments On Tue, 11 Apr 2023 at 04:25, Richard Sargent <rsargent@5x5.on.ca> wrote: > Excellent write up, Stephane! > > I will add that over the years, there have been many times (countless!) > when developing/debugging involved a complex block and that turned out to > be significant nuisance. > > I can elaborate on the details, but once a block gets passed around, > revising it on the fly breaks debug and continue. > > Having a block comprise a single message send allows one to revise that > method as and when needed. > > > > > > *From:* vwnc-request@lists.cs.illinois.edu < > vwnc-request@lists.cs.illinois.edu> *On Behalf Of * > stephane.ducasse@free.fr > *Sent:* April 10, 2023 07:03 > *To:* Any question about pharo is welcome <pharo-users@lists.pharo.org> > *Cc:* vwnc@lists.cs.illinois.edu > *Subject:* Re: [vwnc] [Pharo-users] Block evaluation with n+1 arguments > > > > BTW to me when a block needs too many arguments it feels like that an > object has to be born :) > > With an object I can just sent or not a given extra argument. > > > > Now I do not know enough your specific context but what I learned is that > complex blocks are difficult to follow, manipulate… > > so I keep block as simple as possible and else I create little objects. > > > > > > This is a little lectures from a super cool forthcoming mooc > > > > > https://rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf > <https://urldefense.com/v3/__https:/rmod-files.lille.inria.fr/DesignCoffeeClub/ForLearningLab/7-Lang-04-BlocksVsObjects.pdf__;!!DZ3fjg!7bRnSLpPY1OiW_EoNtvSDBVMfYN5owvLQjTs_FdNbynPLMw7hmdZCNifkawlO9Hz-NbVkGnnh37u3XIaEJEAHjFBHHppJxSeBgY$> > > > > > > > > On 6 Apr 2023, at 15:28, Steffen Märcker <merkste@web.de> wrote: > > > > Hi! > > I want to evaluate a block an argument 'arg1' and additional n arguments > given in an array 'args'. The following code does the trick: > > block valueWithArguments: (Array with: arg1) , args. > > Is there a way to do this without the overhead of creating a new Array? > (How) Can I add additional #value:value:[...] methods to BlockClosure that > evaluate the block with n arguments directly without falling back to > #valueWithArguments: ? If yes, what's the maximum? > > Cheers! > Steffen > > >
SM
Steffen Märcker
Tue, Apr 11, 2023 4:43 PM

Hi!

First, thanks for your engaging answers Richard, Stephane and the others!

The objective is to avoid unnecessary object creation in a tight loop that
interfaces between a value source and a block that processes the values.

  • The source object returns multiple values as a tuple (for good reasons).

  • The block processes theses values but needs another argument (at the
    first place).
    We do not know the number of values at compile time but know that they
    match the arity of the block. Something like this (though more involved in
    practice):

    (1 to: 1000) do: [:i | | args |
    args := source compute: i.
    block valueWithArguments: {i} , args ]

Since prepending the tuple with the first argument and then sending
#valueWithArguments: creates an intermediate Array, I wonder whether we can
avoid (some of) that overhead in the loop without changing this structure.
Note, "{i}, args" is only for illustration and creates an additional third
array as Steve already pointed out.

To sum up the discussion so far:

  • If possible, change the structure, e.g., processing the tuple directly.
  • Fast primitives exist for the special cases of 1, 2 and 3 arguments only.
  • Code for > 3 arguments would have to use #valueWithArguments: after all.

Did I miss something?

Kind regards,
Steffen

Hi! First, thanks for your engaging answers Richard, Stephane and the others! The objective is to avoid unnecessary object creation in a tight loop that interfaces between a value source and a block that processes the values. - The source object returns multiple values as a tuple (for good reasons). - The block processes theses values but needs another argument (at the first place). We do not know the number of values at compile time but know that they match the arity of the block. Something like this (though more involved in practice): (1 to: 1000) do: [:i | | args | args := source compute: i. block valueWithArguments: {i} , args ] Since prepending the tuple with the first argument and then sending #valueWithArguments: creates an intermediate Array, I wonder whether we can avoid (some of) that overhead in the loop without changing this structure. Note, "{i}, args" is only for illustration and creates an additional third array as Steve already pointed out. To sum up the discussion so far: - If possible, change the structure, e.g., processing the tuple directly. - Fast primitives exist for the special cases of 1, 2 and 3 arguments only. - Code for > 3 arguments would have to use #valueWithArguments: after all. Did I miss something? Kind regards, Steffen
RS
Richard Sargent
Tue, Apr 11, 2023 7:13 PM

Steffen,

I think the trouble you are seeing comes from trying to use a block in a way that is inappropriate.

A Block provides an anonymous function, but why use a Block that makes it so much more complicated?
Perhaps, you should reconsider an approach like:
(1 to: 1000) do: [:i | | data |
data:= source compute: i.
MyProcessor handleSourceData: data index: i ]

That allows you to factor out the block code into clean and intention revealing code, and eliminates the need to compose the arguments into a form compatible with Block invocation.

-----Original Message-----
From: Steffen Märcker merkste@web.de
Sent: April 11, 2023 09:44
To: Any question about pharo is welcome pharo-users@lists.pharo.org
Cc: vwnc@lists.cs.illinois.edu
Subject: [Pharo-users] Re: [vwnc] Block evaluation with n+1 arguments

Hi!

First, thanks for your engaging answers Richard, Stephane and the others!

The objective is to avoid unnecessary object creation in a tight loop that interfaces between a value source and a block that processes the values.

  • The source object returns multiple values as a tuple (for good reasons).

  • The block processes theses values but needs another argument (at the first place).
    We do not know the number of values at compile time but know that they match the arity of the block. Something like this (though more involved in
    practice):

    (1 to: 1000) do: [:i | | args |
    args := source compute: i.
    block valueWithArguments: {i} , args ]

Since prepending the tuple with the first argument and then sending
#valueWithArguments: creates an intermediate Array, I wonder whether we can avoid (some of) that overhead in the loop without changing this structure.
Note, "{i}, args" is only for illustration and creates an additional third array as Steve already pointed out.

To sum up the discussion so far:

  • If possible, change the structure, e.g., processing the tuple directly.
  • Fast primitives exist for the special cases of 1, 2 and 3 arguments only.
  • Code for > 3 arguments would have to use #valueWithArguments: after all.

Did I miss something?

Kind regards,
Steffen

Steffen, I think the trouble you are seeing comes from trying to use a block in a way that is inappropriate. A Block provides an anonymous function, but why use a Block that makes it so much more complicated? Perhaps, you should reconsider an approach like: (1 to: 1000) do: [:i | | data | data:= source compute: i. MyProcessor handleSourceData: data index: i ] That allows you to factor out the block code into clean and intention revealing code, and eliminates the need to compose the arguments into a form compatible with Block invocation. -----Original Message----- From: Steffen Märcker <merkste@web.de> Sent: April 11, 2023 09:44 To: Any question about pharo is welcome <pharo-users@lists.pharo.org> Cc: vwnc@lists.cs.illinois.edu Subject: [Pharo-users] Re: [vwnc] Block evaluation with n+1 arguments Hi! First, thanks for your engaging answers Richard, Stephane and the others! The objective is to avoid unnecessary object creation in a tight loop that interfaces between a value source and a block that processes the values. - The source object returns multiple values as a tuple (for good reasons). - The block processes theses values but needs another argument (at the first place). We do not know the number of values at compile time but know that they match the arity of the block. Something like this (though more involved in practice): (1 to: 1000) do: [:i | | args | args := source compute: i. block valueWithArguments: {i} , args ] Since prepending the tuple with the first argument and then sending #valueWithArguments: creates an intermediate Array, I wonder whether we can avoid (some of) that overhead in the loop without changing this structure. Note, "{i}, args" is only for illustration and creates an additional third array as Steve already pointed out. To sum up the discussion so far: - If possible, change the structure, e.g., processing the tuple directly. - Fast primitives exist for the special cases of 1, 2 and 3 arguments only. - Code for > 3 arguments would have to use #valueWithArguments: after all. Did I miss something? Kind regards, Steffen
RO
Richard O'Keefe
Wed, Apr 12, 2023 9:39 AM

You say
<quote>

  • The source object returns multiple values as a tuple (for good reasons).
  • The block processes theses values but needs another argument (at the
    first place).
</quote>

On the first point, we have to take your word for it.
It's not clear why you could not pass an n+1-element array to
the source method and have it fill in elements after the first
rather than having it allocate a new array.  If you are concerned
about object allocation in a tight loop this would be a good
place to start.

argArray := Array new: block argumentCount.
...
source compute: i into: argArray.
argArray at: 1 put: i.
block valueWithArguments: argArray.

If for some reason it is utterly impossible to modify
'source compute: i' in this way, we can still use the
technique of allocating an array once for the whole loop.

argArray := Array new: block argumentCount.
...
argArray at: 1 put: i;
replaceFrom: 2 to: argArray size with: (source compute: i).
block valueWithArguments: argArray.

This seems like the smallest possible change to your code.
You still have the overhead of copying from one array to another
-- which is why I prefer modifying #compute: -- but you do not
have the overhead of allocating an array per iteration.

On the second point, right there you have the assumption that is
limiting your vision.

You are viewing the problem as "pass an extra first argument to
the block" when you should frame it as "ensure that the block
knows the value of i SOMEHOW".  Presumably these blocks are
generated by code written by you.

So let's start with
Someclass
methods for: 'generating blocks'
blockFor: aSituation
^[:x0 :x1 ... :xn | ....]

block := Someclass blockFor: theSourceSituation.
(1 to: 1000) do: [:i | | args |
  args := source compute: i.
  block valueWithArguments: {i} , args

So now we change it to

Someclass
methods for: 'generating blocks'
blockFor: aSituation sharing: stateObject
^[:x1 ... :xn | |x0|
x0 := stateObject contents.
.....]

ref := Ref with: 0.
block := Someclass blockFor: theSourceSituation sharing: ref.
1 to: 1000 do: [:i | |args|
  ref contents: i.
  args := source compute: i.
  block valueWithArguments: args].

Ref is an actual class in my library modelled on the Pop-2 and SML
types of the same name.  It's not important.  What is important is
that information can be supplied to a block through a shared object
as well as through a parameter.

I am a little bit twitchy about the 'coincidence' of the size of
#compute:'s result and the argument count of the block.  Why not
pass the block to #compute: so that there never is any array in
the first place?

compute: index
... ^{e1. ... en} ...

=>
compute: index thenDo: aBlock
... ^aBlock value: e1 ... value: en ...

ref := Ref with: 0.
block := Someclass blockFor: theSourceSituation sharing: ref.
1 to: 1000 do: [:i | |args|
  ref contents: i.
  source compute: i thenDo: block].

Now there are even fewer arrays being allocated and no use of
#valueWithArguments: in any guise.

The problem is NOT, as some commentators apparently think, that
you are using a block.  The problem is that having thought of
one way to wire things up -- not an unreasonable way, in fact --
you concentrated on making that way faster instead of looking
for other ways to do it.

We have several idioms here:
Reuse Object (convert an allocation per iteration to an allocation
per loop by reinitialising a object instead of allocating a new one)
Communicate Through Shared Microstate (communicate information between
a method and a block or object through a 'microstate' object created
by the method and passed when the block or object is created)
Multiple Values by Callback (instead of 'returning' multiple values in
a data structure, pass a block to receive those values as parameters).

On Wed, 12 Apr 2023 at 04:44, Steffen Märcker merkste@web.de wrote:

Hi!

First, thanks for your engaging answers Richard, Stephane and the others!

The objective is to avoid unnecessary object creation in a tight loop that
interfaces between a value source and a block that processes the values.

  • The source object returns multiple values as a tuple (for good reasons).

  • The block processes theses values but needs another argument (at the
    first place).
    We do not know the number of values at compile time but know that they
    match the arity of the block. Something like this (though more involved in
    practice):

      (1 to: 1000) do: [:i | | args |
              args := source compute: i.
              block valueWithArguments: {i} , args ]
    

Since prepending the tuple with the first argument and then sending
#valueWithArguments: creates an intermediate Array, I wonder whether we can
avoid (some of) that overhead in the loop without changing this structure.
Note, "{i}, args" is only for illustration and creates an additional third
array as Steve already pointed out.

To sum up the discussion so far:

  • If possible, change the structure, e.g., processing the tuple directly.
  • Fast primitives exist for the special cases of 1, 2 and 3 arguments only.
  • Code for > 3 arguments would have to use #valueWithArguments: after all.

Did I miss something?

Kind regards,
Steffen

You say <quote> - The source object returns multiple values as a tuple (for good reasons). - The block processes theses values but needs another argument (at the first place). </quote> On the first point, we have to take your word for it. It's not clear why you could not pass an n+1-element array to the source method and have it fill in elements after the first rather than having it allocate a new array. If you are concerned about object allocation in a tight loop this would be a good place to start. argArray := Array new: block argumentCount. ... source compute: i into: argArray. argArray at: 1 put: i. block valueWithArguments: argArray. If for some reason it is utterly impossible to modify 'source compute: i' in this way, we can *still* use the technique of allocating an array once for the whole loop. argArray := Array new: block argumentCount. ... argArray at: 1 put: i; replaceFrom: 2 to: argArray size with: (source compute: i). block valueWithArguments: argArray. This seems like the smallest possible change to your code. You still have the overhead of copying from one array to another -- which is why I prefer modifying #compute: -- but you do not have the overhead of allocating an array per iteration. On the second point, right there you have the assumption that is limiting your vision. You are viewing the problem as "pass an extra first argument to the block" when you *should* frame it as "ensure that the block knows the value of i SOMEHOW". Presumably these blocks are generated by code written by you. So let's start with Someclass methods for: 'generating blocks' blockFor: aSituation ^[:x0 :x1 ... :xn | ....] block := Someclass blockFor: theSourceSituation. (1 to: 1000) do: [:i | | args | args := source compute: i. block valueWithArguments: {i} , args So now we change it to Someclass methods for: 'generating blocks' blockFor: aSituation sharing: stateObject ^[:x1 ... :xn | |x0| x0 := stateObject contents. .....] ref := Ref with: 0. block := Someclass blockFor: theSourceSituation sharing: ref. 1 to: 1000 do: [:i | |args| ref contents: i. args := source compute: i. block valueWithArguments: args]. Ref is an actual class in my library modelled on the Pop-2 and SML types of the same name. It's not important. What *is* important is that information can be supplied to a block through a shared object as well as through a parameter. I am a little bit twitchy about the 'coincidence' of the size of #compute:'s result and the argument count of the block. Why not pass the block to #compute: so that there never is any array in the first place? compute: index ... ^{e1. ... en} ... => compute: index thenDo: aBlock ... ^aBlock value: e1 ... value: en ... ref := Ref with: 0. block := Someclass blockFor: theSourceSituation sharing: ref. 1 to: 1000 do: [:i | |args| ref contents: i. source compute: i thenDo: block]. Now there are even fewer arrays being allocated and no use of #valueWithArguments: in any guise. The problem is NOT, as some commentators apparently think, that you are using a block. The problem is that having thought of one way to wire things up -- not an unreasonable way, in fact -- you concentrated on making *that* way faster instead of looking for other ways to do it. We have several idioms here: Reuse Object (convert an allocation per iteration to an allocation per loop by reinitialising a object instead of allocating a new one) Communicate Through Shared Microstate (communicate information between a method and a block or object through a 'microstate' object created by the method and passed when the block or object is created) Multiple Values by Callback (instead of 'returning' multiple values in a data structure, pass a block to receive those values as parameters). On Wed, 12 Apr 2023 at 04:44, Steffen Märcker <merkste@web.de> wrote: > Hi! > > First, thanks for your engaging answers Richard, Stephane and the others! > > The objective is to avoid unnecessary object creation in a tight loop that > interfaces between a value source and a block that processes the values. > - The source object returns multiple values as a tuple (for good reasons). > - The block processes theses values but needs another argument (at the > first place). > We do not know the number of values at compile time but know that they > match the arity of the block. Something like this (though more involved in > practice): > > (1 to: 1000) do: [:i | | args | > args := source compute: i. > block valueWithArguments: {i} , args ] > > Since prepending the tuple with the first argument and then sending > #valueWithArguments: creates an intermediate Array, I wonder whether we can > avoid (some of) that overhead in the loop without changing this structure. > Note, "{i}, args" is only for illustration and creates an additional third > array as Steve already pointed out. > > To sum up the discussion so far: > - If possible, change the structure, e.g., processing the tuple directly. > - Fast primitives exist for the special cases of 1, 2 and 3 arguments only. > - Code for > 3 arguments would have to use #valueWithArguments: after all. > > Did I miss something? > > Kind regards, > Steffen >
SM
Steffen Märcker
Wed, Apr 12, 2023 7:11 PM

Dear Richard,

thanks for elaborating on your ideas. As I am still figuring out what works
best, I'll give them a try. Especially the approach to let the source deal
with passing the arguments to the block - though this requires more
changes.

The problem is NOT, as some commentators apparently think, that
you are using a block.

Indeed. A block provided (by the user) is actual the natural way in my
case.

The problem is that having thought of
one way to wire things up -- not an unreasonable way, in fact --
you concentrated on making that way faster instead of looking
for other ways to do it.

You're right. I first wanted to see how far I can get with this "direct"
approach before trying other techniques. :-D

All the best!
Steffen

Dear Richard, thanks for elaborating on your ideas. As I am still figuring out what works best, I'll give them a try. Especially the approach to let the source deal with passing the arguments to the block - though this requires more changes. The problem is NOT, as some commentators apparently think, that you are using a block. Indeed. A block provided (by the user) is actual the natural way in my case. The problem is that having thought of one way to wire things up -- not an unreasonable way, in fact -- you concentrated on making *that* way faster instead of looking for other ways to do it. You're right. I first wanted to see how far I can get with this "direct" approach before trying other techniques. :-D All the best! Steffen