Re: Change in semantics for some control flow operations

Jeremy Tregunna <[email protected]> Fri, 09 Dec 2011 18:21:58 -0600
Newsgroups gmane.comp.lang.io
Message-ID <[email protected]>
Reposting code with the 3 arg values.

Note, that "foreachNormal" is meant to replace List foreach to even the playing field. Implement all methods in Io. I'm not proposing we change all the versions written in C to be written in Io, I'm suggesting that we implement them in C still, but with the semantics described in the Io versions below. I hope that's more clear.

List foreachNormal := method(
  idxName := call argAt(0) name
  valName := call argAt(1) name
  body := call argAt(2)

  for(i, 1, size,
    call sender setSlot(idxName, i)
    call sender setSlot(valName, at(i))
    call sender doMessage(body)
  )
)

List foreachContext := method(
  idxName := call argAt(0) name
  valName := call argAt(1) name
  body := call argAt(2)
  ctx := call sender clone

  for(i, 1, size,
    ctx setSlot(idxName, i)
    ctx setSlot(valName, at(i))
    body doInContext(ctx)
  )
)

List foreachBlock := method(
  idxName := call argAt(0) name
  valName := call argAt(1) name
  body := call argAt(2)
  blk := block setArgumentNames(list(idxName, valName)) setMessage(body) setScope(call sender)
  for(i, 1, size, blk call(i, at(i)))
)

Range;

lst := 1 to(10000) asList
Date secondsToRun(
  lst foreachNormal(i, x, x)
  if(getSlot("x"), "x leaked!" println)
) println
Date secondsToRun(
  lst foreachContext(i, x, x)
  if(getSlot("x"), "x leaked!" println)
) println
Date secondsToRun(
  lst foreachBlock(i, x, x)
  if(getSlot("x"), "x leaked!" println)
) println


On 2011-12-09, at 5:16 PM, Jeremy Tregunna wrote:

> If I implemented those methods in C to compare, then yes we should. Since I reproduced foreach, I think that was a fair comparison. I didn't feel like having to submit a patch so people could test on their own having to recompile their VM to get a fair comparison (C vs C vs C); so instead, I gave 3 simple Io implementations that everyone could just paste into their REPL :)
> 
> 
> On 2011-12-09, at 5:02 PM, Steve Dekorte wrote:
> 
>>  
>> 
>> 
>> To be fair, we should compare it to the C implementation of foreach, right?
>>  
>> I get:
>> 
>> 0.000341 for Date secondsToRun(lst foreach(1000000)) println
>> 0.019947 for Date secondsToRun(lst foreachNormal(1000000)) println
>> 0.018511 for Date secondsToRun(lst foreachContext(1000000)) println
>> 0.054686 for Date secondsToRun(lst foreachBlock(1000000)) println
>> 
>> So ~60x slower. Though if we were going to do this, we'd implement the scoped version of foreach in C too.
>> It would be interesting to see the difference. I'm guessing 10x for a no op loop but maybe much less if it's looping on a calll to an Io defined method.
>> 
>> I generally like the idea. What do you think about implementing it on control flow using blocks instead?
>> 
>> On 2011-12-09 Fri, at 02:46 PM, Jeremy Tregunna wrote:
>>> 
>>> A quick test, code below, results first:
>>> 
>>> Normal foreach (the way we implement it now): 0.045998, 0.042344, 0.04463
>>> With a single throwaway context: 0.041687, 0.044041, 0.042541
>>> With a block: 0.129006, 0.134746, 0.133294
>>> 
>>> The above results are based on three runs, of the code below. As you can tell, it's a very trivial example (single arg form, didn't implement the other two and three arg forms for the sake of time):
>>> 
>>> List foreachNormal := method(
>>>   body := call argAt(0)
>>>   for(i, 1, size, call sender doMessage(body))
>>> )
>>> 
>>> List foreachContext := method(
>>>   body := call argAt(0)
>>>   ctx := call sender clone
>>>   for(i, 1, size, body doInContext(ctx))
>>> )
>>> 
>>> List foreachBlock := method(
>>>   body := call argAt (0)
>>>   blk := block setMessage(body) setScope(call sender)
>>>   for(i, 1, size, blk call)
>>> )
>>> 
>>> Range;
>>> 
>>> lst := 1 to(10000) asList
>>> Date secondsToRun(lst foreachNormal(1000000)) println
>>> Date secondsToRun(lst foreachContext(1000000)) println
>>> Date secondsToRun(lst foreachBlock(1000000)) println
>>> 
>>> 
>>> On 2011-12-09, at 4:30 PM, Steve Dekorte wrote:
>>> 
>>>> 
>>>> 
>>>> Can you do a test to determine what the performance costs are?
>>>> 
>>>> On 2011-12-08 Thu, at 06:43 AM, Jeremy Tregunna wrote:
>>>>> I want to propose a change in direction for the standard library foreach(), select(), and friends methods. I'll supply code, don't worry about that.
>>>>> 
>>>>> Right now, how we go about doing things is once we're iterating, we set values in the calling context directly. This has obvious problems worse than shadowing, i.e., overwriting values we want and persisting after the foreach() has finished. Now at present, anyone hit by this problem will have renamed their identifiers when they got bad results, but this is rather confusing. Additionally, why should we leak these values?
>>>>> 
>>>>> I'm proposing one of two solutions I think are a better fit:
>>>>> 
>>>>> 1. Set up a block with the argument name(s) we want to set, the body we want to evaluate, and the proper scope to capture (the sender). Call that inside our main loop.
>>>>> 2. Create a separate isolated context outside the loop, set our value(s) in it inside the loop, a sk our body to doInContext with that context and the sender. This has a slight advantage performance wise in that we only create one context, as opposed to creating N locals with the block worst case (or recycling N locals depending on our environment).
>>>>> 
>>>>> Now, this does have the negative use in that it breaks operational semantics of these methods at present if a user depends on this behaviour, but I'm ok with that; I'd consider that behaviour broken anyway.
>>>>> 
>>>>> I think this should be done to all the standard library objects that support these types of constructs. Take some of the surprise out of using them.
>>>>> 
>>>>> Regards,
>>>>> 
>>>>> Jeremy Tregunna
>>>> 
>>>> 
>>>> 
>>> 
>>> Regards,
>>> 
>>> Jeremy Tregunna
>>> < /div>
>>> 
>>> 
>>> 
>>> 
>> 
>> 
> 
> Regards,
> 
> Jeremy Tregunna
> 
> 
> 
> 
> 

Regards,

Jeremy Tregunna