Confusing issue with blocks

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

Confusing issue with blocks

J J-6
Hi all,

I am having a strange problem with anonymous blocks.  I have been trying to
impliment a Reccurrence rule engine.  After trying different implimentations
I didn't like, I found that the best solution involved an infinite list.  So
I set out to impliment lazy evaluation in smalltalk.  What I have now works
like:

((LazyElement enumerateFrom: 2006 with: [:e| e + 2]) collectAndFlatten: [:y|
   (LazyElement fromArray: #(1 8 12)) collectAndFlatten: [:m|
                (LazyElement fromArray: #(1 15 27)) collect: [:d|
          Date year: y month: m day: d
                        ]
        ]
]) take: 500

And this part works perfectly.  I have gone up to 2k dates and I noticed no
delay at all (though I haven't timed it).  I then changed the List creation
parts to be methods, which is what my final solution will need.  If I change
the month function to return different months depending on the year and it
works perfectly.  But if I have a year for which no months apply and return
an empty list, I get "error: Attempt to  evaluate a block that is already
evaluated."

I have done a parralell implimentation in Ocaml (because it also requires me
to manually build the Lazy List operations) and everything worked as
expected, so I am really baffelled on this one.  Nothing should be happening
in the empty list case that doesn't already happen in the case of 1 or more
in the list.

I read on the list that the solution to this problem is to copy or clone the
block.  I have put a clone on every place I use a block and it didn't help.  
Nor did copy.

I have enclosed the file out of the Lazy list classes (it's not a lot of
code), I would appreciate any help anyone could give me.

Thanks,
Jason

_________________________________________________________________
View Athlete’s Collections with Live Search
http://sportmaps.live.com/index.html?source=hmemailtaglinenov06&FORM=MGAC01



LazyElement.st (5K) Download Attachment
LazyNil.st (2K) Download Attachment
LazyTest.st (1K) Download Attachment
LazyValue.st (1K) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Confusing issue with blocks

Boris.Gaertner

From: "J J" <[hidden email]> wrote:


> Hi all,
>
> I am having a strange problem with anonymous blocks.
>
> I read on the list that the solution to this problem is to copy or clone
the
> block.
Yes, that is a good advice.

> I have enclosed the file out of the Lazy list classes (it's not a lot of
> code), I would appreciate any help anyone could give me.
With that code it is not too difficult to understand what you are
doing.

The problem with our good old Squeak is that blocks cannot be used
recursively. To see that problem. evaluate:

  | fac |

  fac := [:int | int = 1 ifTrue: [1]
              ifFalse: [int* (fac value: int - 1)]
      ].
  fac value: 2

You will get exactly the error message that you got with your
code.

When you do this:
  | fac |

  fac := [:int | int = 1 ifTrue: [1]
              ifFalse: [int* (fac clone value: int - 1)]
      ].
  fac clone value: 5

you will get the expected result.


In your code, you should modify method
LazyElement>> inject:into:

inject: aValue into: aBinaryBlock
 "NOTE: This method does not behave as a normal
  inject into.  It actually behaves exactly like fold_right"

 ^ aBinaryBlock clone   " <-  clone here "
       value: head
       value: (LazyValue delay: [ tail force inject: aValue into:
aBinaryBlock])

That should help.


A slightly different approach introduces an instance method
that creates a block instance:

flattenBlock

   ^[:p :e| p append: e ]

and modifies methods flatten and inject:into:

flatten

 ^ self inject: LazyNil new into: [self flattenBlock]


inject: aValue into: aBinaryBlockGenerator
 "NOTE: This method does not behave as a normal inject into.  It actually
behaves exactly like fold_right"

 ^ aBinaryBlockGenerator value "  this value calls a block that creates
                             a new block instance "
     value: head
      value: (LazyValue delay: [ tail force inject: aValue into:
aBinaryBlockGenerator])

> Thanks,
> Jason

I know that this is tricky stuff, but I also hope that my remarks
are to some extent helpful.

Greetings,
Boris


Reply | Threaded
Open this post in threaded view
|

Re: Confusing issue with blocks

J J-6
>From: "Boris Gaertner" <[hidden email]>
>Reply-To: The general-purpose Squeak developers
>list<[hidden email]>
>To: "The general-purpose Squeak developers
>list"<[hidden email]>
>Subject: Re: Confusing issue with blocks
>Date: Tue, 12 Dec 2006 21:37:41 +0100
>
>The problem with our good old Squeak is that blocks cannot be used
>recursively. To see that problem. evaluate:
>

But this is what is confusing to me.  The way I'm using it is recursively
and it is working.  Except in this one case.  Let me try to illistrate what
I mean.  In the following [] means the nil list, which in my implimentation
is LazyNil.

In the call that works, the code does the following for (years = 2006 ...,
months = 1,8, days = 1,15,28):

2006 :: 1 :: 1
2006 :: 1 :: 15
2006 :: 1 :: 28
2006 :: 1 :: []            "List terminated, unwinds one level and starts
over on next month"
2006 :: 8 :: 1
2006 :: 8 :: 15
2006 :: 8 :: 28
2006 :: 8 :: []
2006 :: []                  "Month list terminated, unwinds back to year"
2007 :: 1 :: 1
etc.

And this works.  But in the case that the month list is empty for 2007 I get
the error:

2006 :: 1 :: 1             "as before"
2006 :: 1 :: 15
2006 :: 1 :: 28
2006 :: 1 :: []            "List terminated, unwinds one level and starts
over on next month"
2006 :: 8 :: 1
2006 :: 8 :: 15
2006 :: 8 :: 28
2006 :: 8 :: []
2006 :: []                  "Month list terminated, unwinds back to year"
2007 :: []                  "No months for this year"
2008 :: 1 :: 1             "Should happen but does not"

This is what I don't get.  The two cases seem to me to be virtually
identical.  It is as if squeak needs a list of at least one element to get
constructed so some internal counter can get reset or something.  In Ocaml
my implementation is almost word for word the same (obviously I use Ocaml
idioms in ocaml but the behavior is exactly the same) and this part works.  
And in fact in the stack trace I see that Smalltalk does indeed calculate
the next date (2008,1,1 in this example), it just throws the exception.  
Very strange.

>In your code, you should modify method
>LazyElement>> inject:into:
>
>inject: aValue into: aBinaryBlock
>  "NOTE: This method does not behave as a normal
>   inject into.  It actually behaves exactly like fold_right"
>
>  ^ aBinaryBlock clone   " <-  clone here "
>        value: head
>        value: (LazyValue delay: [ tail force inject: aValue into:
>aBinaryBlock])
>
>That should help.

Actually I had the clone in the place you suggest and inside the delay.  
Every place you see a block used in that code has had a clone and a copy
statement at some point, but nothing has fixed it so far.  I took them out
to make sure the meaning of the code was clear as well as make the bug
easily reproducable.  If you run the method "showWorking" it produces 500
dates without incident, despite being a completely recursive algorythm.  If
you run "showNotWorking" you get the failure behavior.

>A slightly different approach introduces an instance method
>that creates a block instance:
>
>flattenBlock
>
>    ^[:p :e| p append: e ]
>
>and modifies methods flatten and inject:into:
>
>flatten
>
>  ^ self inject: LazyNil new into: [self flattenBlock]
>
>
>inject: aValue into: aBinaryBlockGenerator
>  "NOTE: This method does not behave as a normal inject into.  It actually
>behaves exactly like fold_right"
>
>  ^ aBinaryBlockGenerator value "  this value calls a block that creates
>                              a new block instance "
>      value: head
>       value: (LazyValue delay: [ tail force inject: aValue into:
>aBinaryBlockGenerator])
>
> > Thanks,
> > Jason
>
>I know that this is tricky stuff, but I also hope that my remarks
>are to some extent helpful.
>

Of course.  Thank you very much for your help and response.  But as I have
described this, I believe I figured out what the issue is:

In my implementation a LazyList is either a LazyNil (meaning empty list) or
a two element object that the first object is a real value and the second is
basically a function that returns the next sequence recursively (i.e. the
next sequence can again be a LazyNil or a function).

The consequence of this is that you can have as much complicated nested
lists, method calls etc. as you want but only 1 value will actually be
calculated.  But each time a value is asked for *1* must be returned.  So in
the case where each list has a value, the algorithm must only call each
function once to get that value, all the recursive calls are delayed.  But
in the case that months returns nothing, the algorithm can't go into a
delay, it has to continue searching until it comes up with something.  This
is creating the case where the same block actually ends up being used twice
in the same call stack.

So that leads to the question, why didn't cloning everything fix it? :(  
What would I need to do to get squeak to instantiate closures instead of
blockContexts?

I wish that one of the following 3 things would happen:
- Squeak would switch completely to proper closures
- Squeak would, during compile phase, determine if the block should be a
closure or block context (e.g. does the block contain ^?)
- Squeak would provide some simple way to say "please instantiate these
specific blocks as closures" (maybe this one exists?)

Thanks,
Jason

_________________________________________________________________
View Athlete’s Collections with Live Search
http://sportmaps.live.com/index.html?source=hmemailtaglinenov06&FORM=MGAC01


Reply | Threaded
Open this post in threaded view
|

Re: Re: Confusing issue with blocks

Lukas Renggli
> - Squeak would provide some simple way to say "please instantiate these
> specific blocks as closures" (maybe this one exists?)

Sure, use an image with the new compiler (such as
http://www.iam.unibe.ch/~denker/temp/NewCompiler.zip). Then either
change the global settings or implement a method like

LazyWhatsoever class>>compilerClass
        ^ ClosureCompiler

in your classes where you want closures.

Lukas

--
Lukas Renggli
http://www.lukas-renggli.ch

Reply | Threaded
Open this post in threaded view
|

Re: Re: Confusing issue with blocks

J J-6
Thanks Lukas.  I will keep that in mind.  It turns out that one call to
clone fixed it.

I changed:

        ^ aBinaryBlock value: head value: (LazyValue delay: [ tail force inject:
aValue into: aBinaryBlock ])

to:

        ^ aBinaryBlock clone value: head value: (LazyValue delay: [ tail force
inject: aValue into: aBinaryBlock ])

I had already tried doing:

        cloneBlock := aBinaryBlock clone.

        ^ cloneBlock value: head value: (LazyValue delay: [ tail force inject:
aValue into: cloneBlock ])

But it didn't work.  But anyway, it's working now.  Thanks for the help all.

>From: "Lukas Renggli" <[hidden email]>
>Reply-To: The general-purpose Squeak developers
>list<[hidden email]>
>To: "The general-purpose Squeak developers
>list"<[hidden email]>
>Subject: Re: Re: Confusing issue with blocks
>Date: Wed, 13 Dec 2006 08:45:15 +0100
>
>>- Squeak would provide some simple way to say "please instantiate these
>>specific blocks as closures" (maybe this one exists?)
>
>Sure, use an image with the new compiler (such as
>http://www.iam.unibe.ch/~denker/temp/NewCompiler.zip). Then either
>change the global settings or implement a method like
>
>LazyWhatsoever class>>compilerClass
> ^ ClosureCompiler
>
>in your classes where you want closures.
>
>Lukas
>
>--
>Lukas Renggli
>http://www.lukas-renggli.ch
>

_________________________________________________________________
WIN up to $10,000 in cash or prizes – enter the Microsoft Office Live
Sweepstakes http://clk.atdmt.com/MRT/go/aub0050001581mrt/direct/01/