I would like to be able to define a piece of code and later play it
back. This probably sounds very similar to block closure, but I would like the values used in the block to take the value at the point of creation of the block. Something like the code below, but would like the value printed to be 1 instead of 2. Did I miss a simple way of doing this? Thanks. === index := 1. block := [ Transcript show: index displayString ]. index := index + 1. block value. === HweeBoon |
Yar Hwee Boon wrote:
> index := 1. > block := [ Transcript show: index displayString ]. > index := index + 1. > block value. That's a deficiency in the current implementation of Blocks which, I believe, it is going to be fixed in Dolphin 6. In the meantime, the easiest way to get the effect you are after is probably to factor out the creation of the block into a separate method. Something like: ====== index := 1. block := self makeBlock: index. index := index + 1. block value. ====== where the definition of #makeBlock is: ====== makeBlock: anIndex ^ [Transcript display: anIndex]. ====== Obviously, if you want to do this kind of thing from Workspaces then you'll have to define some kind of helper class to hold #makeBlock: -- chris |
I wrote:
> That's a deficiency in the current implementation of Blocks which, I > believe, it is going to be fixed in Dolphin 6. Actually, now I think of it, this code *isn't* exhibiting the deficiency I was thinking of. Sorry. The workaround I posted still works, though. -- chris |
"Chris Uppal" <[hidden email]> wrote in message
news:40640cf5$0$65060$[hidden email]... > I wrote: > > > That's a deficiency in the current implementation of Blocks which, I > > believe, it is going to be fixed in Dolphin 6. > > Actually, now I think of it, this code *isn't* exhibiting the deficiency I was > thinking of. Sorry. > Yes, that's right. For the benefit of others what Chris is referring to (I assume) is that if one writes: > index := 1. > block := [ Transcript show: index displayString ]. > index := index + 1. > block value. Then regardless of whether one has Smalltalk-80 style blocks or proper closures, the value of index will be shared. This is because it is assigned to after the point where it is "captured" by the block. If however one had written: blocks := (1 to: 5) collect: [:each | [ Transcript display: each]]. blocks do: [:each | each value] separatedBy: [Transcript nextPutAll: ', ']. Transcript cr. Then that would result yield the following results: D5: 5, 5, 5, 5, 5 D6: 1, 2, 3, 4, 5 > The workaround I posted still works, though. The workaround of extracting the block creation to a separate method (and therefore a separate activation) will work in almost all circumstances that full closures are needed. It won't help with recursive blocks though. Regards Blair |
In reply to this post by Chris Uppal-3
"Chris Uppal" <[hidden email]> wrote in message news:<[hidden email]>...
> Yar Hwee Boon wrote: > > > index := 1. > > block := [ Transcript show: index displayString ]. > > index := index + 1. > > block value. > > That's a deficiency in the current implementation of Blocks which, I believe, > it is going to be fixed in Dolphin 6. > > In the meantime, the easiest way to get the effect you are after is probably to > factor out the creation of the block into a separate method. Something like: > > ====== > index := 1. > block := self makeBlock: index. > index := index + 1. > block value. > ====== > > where the definition of #makeBlock is: > > ====== > makeBlock: anIndex > ^ [Transcript display: anIndex]. > ====== > > Obviously, if you want to do this kind of thing from Workspaces then you'll > have to define some kind of helper class to hold #makeBlock: > > -- chris Thanks. But actually how does a block closure actually handle the variable(s) it is passed? I can't understand why your suggestion works (although it works of course). And actually I am not trying to do this in a workspace, rather, I'm trying to defer painting of text/lines onto a PrinterCanvas into the appropriate page, eg. when a table that I am printing spans across multiple pages, I check that it is out of bounds of the current page and I run the code below to adjust the coordinates and then store the block to be #value-ed late when I am at the correct page. ======== report atPage: 2 addPendingPrint: [ | adjustedOrigin | adjustedOrigin := position - 0@400. canvas formatText: each in: (Rectangle origin: adjustedOrigin extent: width@height) flags: ((columns at: index) justification)].]. ======== HweeBoon |
> Thanks. But actually how does a block closure actually handle the
> variable(s) it is passed? I can't understand why your suggestion works > (although it works of course). And actually I am not trying to do this > in a workspace, rather, I'm trying to defer painting of text/lines > onto a PrinterCanvas into the appropriate page, eg. when a table that > I am printing spans across multiple pages, I check that it is out of > bounds of the current page and I run the code below to adjust the > coordinates and then store the block to be #value-ed late when I am at > the correct page. Would a block with one or more arguments (the page number, etc.) work? Creating the block might look like this: aBlock := [ :pageNumber | | adjustedOrigin | adjustedOrigin := position - ( (0@400) * ( 1@pageNumber ) ). canvas formatText: each in: (Rectangle origin: adjustedOrigin extent: width@height) flags: ((columns at: index) justification)]. ]. and you would evaluate with aBlock value:2. to print page number 2 (or maybe three depending on zero or unit offset). Of course, you could always alter the block to get the page offset you want. You can also have more parameters: aBlock := [ :x :y :z | ]. aBlock value:1 value:3.0 value:100. Note that, because of the zero, (0@400) * ( 1@pageNumber ) is equivalent to (0@400)*pageNumber, but it is worth remembering that points add and multiply in useful ways, as do rectangles. Have a good one, Bill -- Wilhelm K. Schwab, Ph.D. [hidden email] |
In reply to this post by Yar Hwee Boon
Yar Hwee Boon wrote:
> Thanks. But actually how does a block closure actually handle the > variable(s) it is passed? I can't understand why your suggestion works > (although it works of course). Well, I don't know the details of how it's implemented, if that's what you are asking. But the essential points are that: - each time a method is called, it is given a new set of variables, - a block *shares* the variables of its containing method call, - those variables continue to exist for as long as the block does (even if the method has returned). So when you evaluate: block := self makeBlock: 22. that will call the method: makeBlock: anIndex ^ [Transcript display: anIndex]. Now, that invocation of the method has it's own variable 'anIndex' which is set to 22. The method creates a new block object that shares 'anIndex', and then when the method returns the block *still* has that variable. So if you send #value to block, it will print the value of that variable (22) on the transcript. Now, if you call the method a second time: anotherBlock := self makeBlock: 77. then this call to #makeBlock will have its own version of the variable 'anIndex' (in this case set to 77), and so the new block that it answers will print 77 on the transcript each time you send #value to it. It *may* help to imagine a very simple implementation -- this stuff isn't *really* implemented like this, but it is supposed to act as if it were. When you invoke a method, the system allocates a new object to hold the parameters and local variables of that method. Call the object a "context" (it could also be called a "stack frame" or "activation record"). Each method *execution* has its own context, and references to local variables and parameters are implemented by looking up slots in the context object. Normally contexts are created when a method is called, and are discarded and garbage-collected when the method returns. However all the blocks created as a method executes also have references to the context (so that they can share access to the variables), and so if a block survives past the end of the method (as in my example where I return the new block from the method) then the context object will also survive for as long as the block needs it. Then if I call the method a second time, a second context will be created and it too will survive for as long as the second block needs it. > [...] I'm trying to defer painting of text/lines > onto a PrinterCanvas into the appropriate page, eg. when a table that > I am printing spans across multiple pages, I check that it is out of > bounds of the current page and I run the code below to adjust the > coordinates and then store the block to be #value-ed late when I am at > the correct page. That's nice, elegant, idea. It's a shame that it doesn't quite work with standard Smalltalk semantics :-( > ======== > report atPage: 2 addPendingPrint: [ > > adjustedOrigin | > adjustedOrigin := position - 0@400. > canvas > formatText: each > in: (Rectangle origin: adjustedOrigin extent: width@height) > flags: ((columns at: index) justification)].]. > ======== The problem is that the block you create inline shares the "position", "canvas", etc, variables with the method call where is was created. So all the blocks created by that *single* method call will share the same variables. All you can do (without redesigning your code completely) is to ensure you use a fresh method call to create each block so that all the blocks have their "own" independent variables. *** Digression warning! **** This way of using blocks is so nice that it really is a shame that it doesn't work without messing around with helper methods. If method calls were really implemented in the simple way I sketched above, then it would be quite simple to "freeze" a block by having it take a copy of its context. That would ensure that it had it's own unshared copy of the variables and so it would be unaffected by any subsequent changes to their values. As an *experiment*, I tried adding this method to BlockClosure ============ freeze self outer: (self outer copy). ============ which attempts to "freeze" a block by making a copy of its context. I was surprised to find that it *does* appear to work! I don't know enough (not *nearly* enough) about Dolphin meta-programming to judge if it's really safe (it breaks the debugger, for a start), so I certainly wouldn't dare use it in real code. Blair, if you are reading, could you comment on the safety of the idea ? Please feel free to comment on the sanity too ;-) *** Digression end **** -- chris |
"Chris Uppal" <[hidden email]> wrote in message
news:[hidden email]... > Yar Hwee Boon wrote: > > [...] I'm trying to defer painting of text/lines > > onto a PrinterCanvas into the appropriate page, eg. when a table that > > I am printing spans across multiple pages, I check that it is out of > > bounds of the current page and I run the code below to adjust the > > coordinates and then store the block to be #value-ed late when I am at > > the correct page. > > That's nice, elegant, idea. It's a shame that it doesn't quite work with > standard Smalltalk semantics :-( > Do you mean that it doesn't work with standard Smalltalk-80 semantics, or that it doesn't work with full closure semantics? > > ======== > > report atPage: 2 addPendingPrint: [ > > > adjustedOrigin | > > adjustedOrigin := position - 0@400. > > canvas > > formatText: each > > in: (Rectangle origin: adjustedOrigin extent: width@height) > > flags: ((columns at: index) justification)].]. > > ======== > > The problem is that the block you create inline shares the "position", > "canvas", etc, variables with the method call where is was created. So > blocks created by that *single* method call will share the same variables. All > you can do (without redesigning your code completely) is to ensure you use a > fresh method call to create each block so that all the blocks have their "own" > independent variables. > > > *** Digression warning! **** > > This way of using blocks is so nice that it really is a shame that it doesn't > work without messing around with helper methods. If method calls were really > implemented in the simple way I sketched above, then it would be quite simple > to "freeze" a block by having it take a copy of its context. That would ensure > that it had it's own unshared copy of the variables and so it would be > unaffected by any subsequent changes to their values. > Well it would work with full closure semantics as in D6, as long as the captured variables are not written after they are captured by the block, and are not assigned to in the block. Where the variables are written-after-read in this way, then it is assumed that sharing is the desired behaviour, i.e. a := 1. block := [a]. a := 2. block value => 2 This would be the case in D5 or D6, the former sharing 'a' or any other temp closed over by the block because it is limited to working that way, the latter deliberately choosing to share the variable. Where the 'sharing' behaviour is not wanted in cases like this, then in D5 one has to have a separate method activation because, as you say, all variables are allocated per method activation. In D5 method activations will have a stackframe, and may have a heap allocated context object as well if they create any blocks. When a D5 method activation requires a context, all the temps are stored in the context regardless of whether they should really be shared (or shareable). Block activations in D5 do have a stackframe, but there are no temporary variables in the frame. Even the arguments get "popped" off the stack back into the home method context. In D6 all method activations still have a stack frame (the format of which is quite similar to D5s in fact). The may also have a "Context" object if needed to hold shared variables, but this is really little more than an array of slots. It doesn't hold any per-activation information, so its not like a Smalltalk-80 context. In D6 block activations are essentially the same as method activations, and so any variables local to the block, such as arguments or its own temporaries are allocated in the blocks own stack frame. Blocks might also have their own Context objects if they themselves enclose blocks which share their variables. In the general case it is possible to share variables from a chain of outer contexts, although this rarely occurs in practice. Rather than add further detail here, you can read the class comment of Dolphin 6's BlockClosure class at: http://object-arts.com/Lib/Update/Dolphin/5.1/BlockClosure.txt In D6 if one wants to "snapshot" the value of a shared variable that is changed after block creation, one can do something like this: a := 1. block := [:a1 | [a1]] value: a. a := 2. block value => 1 This makes it plain that one is deliberately freezing the value in the block. The basic rule is that any variable you don't want to share but do want to change after the block has been created needs to be frozen by passing it as an argument into another activation. In D5 this has to be done as a separate method, since only methods have proper activations, but in D6 it can be done inline as above. Frankly though it is not something I have ever needed to do as the standard rules for closing over variables are usually appropriate. > As an *experiment*, I tried adding this method to BlockClosure > > ============ > freeze > self outer: (self outer copy). > ============ > > which attempts to "freeze" a block by making a copy of its context. I was > surprised to find that it *does* appear to work! I don't know enough (not > *nearly* enough) about Dolphin meta-programming to judge if it's really > (it breaks the debugger, for a start), so I certainly wouldn't dare use it in > real code. > This is similar to the idea behind the Squeak (and I presume Smalltalk-80) #fixTemps message. It should be safe as long as you don't create a situation where the "frozen" block might attempt a ^-return after the original method has returned, which would almost certainly crash the system. Actually I would suggest modifying your implementation by adding a method to MethodContext that sets its frame instance variable to zero, then call this from BlockClosure>>freeze on the outer copy. As with #fixTemps, you now have two completely separate contexts so any variables you may have intended to share will no longer be sharing the same slot (though of course it is still possible to have "variables" shared between the contexts by by adding a further level of indirection). > Blair, if you are reading, could you comment on the safety of the idea ? > Please feel free to comment on the sanity too ;-) > Personally I'd rather add another method. There is no fixTemps in Dolphin because it is really a hack with hidden effects that are not immediately obvious to anyone with a basic understanding of Smalltalk semantics. For example you'll end up capturing more state than you need (a weakness of Smalltalk-80 blocks anyway of course). It really won't be needed with D6 anyway. If you want to experiment with it in the meantime, then it would be interesting to hear of your experiences. Regards Blair |
"Blair McGlashan" <[hidden email]> wrote in message news:<c49hf4$2fr70r$[hidden email]>...
snipped > > In D6 if one wants to "snapshot" the value of a shared variable that is > changed after block creation, one can do something like this: > > a := 1. > block := [:a1 | [a1]] value: a. > a := 2. > block value => 1 > > This makes it plain that one is deliberately freezing the value in the > block. The basic rule is that any variable you don't want to share but do > want to change after the block has been created needs to be frozen by > passing it as an argument into another activation. In D5 this has to be done > as a separate method, since only methods have proper activations, but in D6 > it can be done inline as above. Frankly though it is not something I have > ever needed to do as the standard rules for closing over variables are > usually appropriate. As for my original need (to defer painting of lines, text on the current page to another page on a PrinterCanvas), I have tried using the Command pattern, creating a subclass for each drawing method, such as #formatText:in:flags: and #text:at: and passing in the relevant parameters upon instantiation instead of using blocks. This will probably lead to a handful or 2 of subclasses, but seems to fit my needs so far.. Does anyone has any comment regarding this approach compared to using blocks as described by other Chris, etc earlier? snipped Hwee Boon |
Yar Hwee Boon wrote:
> As for my original need (to defer painting of lines, text on the > current page to another page on a PrinterCanvas), I have tried using > the Command pattern, creating a subclass for each drawing method, such > as #formatText:in:flags: and #text:at: and passing in the relevant > parameters upon instantiation instead of using blocks. This will > probably lead to a handful or 2 of subclasses, but seems to fit my > needs so far.. Does anyone has any comment regarding this approach > compared to using blocks as described by other Chris, etc earlier? It's a bit late to reply, but... FWIW, that seems like a sensible approach to me. The blocks approach is more elegant (if it worked ;-) but there's a lot to be said for keeping things simple. -- chris |
In reply to this post by Blair McGlashan
Blair McGlashan wrote:
> > > [...] I'm trying to defer painting of text/lines > > > onto a PrinterCanvas into the appropriate page, eg. when a table that > > > I am printing spans across multiple pages, I check that it is out of > > > bounds of the current page and I run the code below to adjust the > > > coordinates and then store the block to be #value-ed late when I am at > > > the correct page. > > > > That's nice, elegant, idea. It's a shame that it doesn't quite work > > with standard Smalltalk semantics :-( > > > > Do you mean that it doesn't work with standard Smalltalk-80 semantics, or > that it doesn't work with full closure semantics? The approach doesn't work with either semantics. It's looking for a (functional programming-style) closure over the current values of variables, rather than a closure over the variables (slots) themselves. > Rather than add further detail here, you can read the class comment of > Dolphin 6's BlockClosure class at: > > http://object-arts.com/Lib/Update/Dolphin/5.1/BlockClosure.txt Very interesting. Thanks. > In D6 if one wants to "snapshot" the value of a shared variable that is > changed after block creation, one can do something like this: > > a := 1. > block := [:a1 | [a1]] value: a. > a := 2. > block value => 1 Not wanting to be rude ;-) but that's *really* ugly.... I think the idea of freezing a block is a useful idiom to have available. Sure, there are other ways of getting the same effect -- breaking the variables out as parameters to a helper method in simple cases, or factoring the local variables into the instvars of a new object for more complicated cases -- but I think that there are classes of problems where either of those techniques would complicate, rather than clarify, the code. Compare the case of continuations. They aren't something you're going to need every day, but they are handy to have and can simplify the expression of some algorithms enormously (not that I've actually used continuations yet, but...). > > ============ > > freeze > > self outer: (self outer copy). > > ============ > > > > which attempts to "freeze" a block by making a copy of its context. I > > was surprised to find that it *does* appear to work! I don't know > > enough (not *nearly* enough) about Dolphin meta-programming to judge if > > it's really safe (it breaks the debugger, for a start), so I certainly > > wouldn't dare use it in real code. > > > > This is similar to the idea behind the Squeak (and I presume Smalltalk-80) > #fixTemps message. Ah! So that's what #fixTemps is for. I'd noticed it in the Squeak image, but (probably because "temps" doesn't mean "local variables" to me) hadn't clicked to what it was doing. A quick scan through the Squeak image suggests that it is rather over-used... > It should be safe as long as you don't create a > situation where the "frozen" block might attempt a ^-return after the > original method has returned, which would almost certainly crash the > system. Actually I would suggest modifying your implementation by adding > a method to MethodContext that sets its frame instance variable to zero, > then call this from BlockClosure>>freeze on the outer copy. Thanks for the guidance. I shall return to this later (currently, I'm too busy being intensely lazy ;-) > There is no fixTemps in Dolphin > because it is really a hack with hidden effects that are not immediately > obvious to anyone with a basic understanding of Smalltalk semantics. I wouldn't call it a hack. I agree that it's not something I'd want to see used much, but I think it's a useful, and semantically sound, albeit "advanced" technique. > It really won't be needed with D6 > anyway. If you want to experiment with it in the meantime, then it would > be interesting to hear of your experiences. I think it will be just as useful in D6. (That's to say, I don't think it's an appropriate "fix" for problems caused by Dolphin's current block behaviour -- I'd rather reserve it for places where it conveys real meaning.) I am, BTW, not arguing that you should add it to D6 (though I'd not be sorry if you did). It seems easy enough to do myself. -- chris |
"Chris Uppal" <[hidden email]> wrote in message
news:[hidden email]... > Blair McGlashan wrote: > ..... > > In D6 if one wants to "snapshot" the value of a shared variable that is > > changed after block creation, one can do something like this: > > > > a := 1. > > block := [:a1 | [a1]] value: a. > > a := 2. > > block value => 1 > > Not wanting to be rude ;-) but that's *really* ugly.... Perhaps, but one can understand that the block will definitely not share 'a' without having to know about what magic is performed by #fixTemps, or whatever better name one chooses for it - it certainly needs a name that reveals that any shared temporary variables accessed in the block will no longer be shared. Its also worth bearing in mind that the representation of the context you end up with is still the full monty needed for sharing variables, which is more general than you need. Anyway here is a better way (don't know why it didn't occur to me before): a := 1. fixedA := a. block := [fixedA]. a := 2. This won't work if you assign to fixedA in the block, but why would you want to do that anyway unless its value needed to be shared? Frankly, though, for the most part one tends to find that the lexical closure semantics do the right thing most of the time. >... > > This is similar to the idea behind the Squeak (and I presume Smalltalk-80) > > #fixTemps message. > > Ah! So that's what #fixTemps is for. I'd noticed it in the Squeak image, but > (probably because "temps" doesn't mean "local variables" to me) hadn't clicked > to what it was doing. A quick scan through the Squeak image suggests that it > is rather over-used... Put something like that in, and inevitably that will happen. Its a path of least resistance thing, and exactly the reason we have avoided putting a switch "statement" into Dolphin. >...[snip]... > > There is no fixTemps in Dolphin > > because it is really a hack with hidden effects that are not immediately > > obvious to anyone with a basic understanding of Smalltalk semantics. > > I wouldn't call it a hack. I agree that it's not something I'd want to see > used much, but I think it's a useful, and semantically sound, albeit "advanced" > technique. To my mind it's a hack to workaround the Smalltalk-80 block limitations. There might be an argument that the behaviour of being able to "unshare" specific variables is useful, but I would prefer that be achieved at block creation time, and syntactically. As I've mentioned, there is a way to do that by using additional temporaries to which one assigns the value of the temp that one wants to "fix". This is certainly more clunky than it could be, but it is a matter of opinion as to whether the requirement is esoteric enough that it shouldn't be any easier. > > It really won't be needed with D6 > > anyway. If you want to experiment with it in the meantime, then it would > > be interesting to hear of your experiences. > > I think it will be just as useful in D6. (That's to say, I don't think it's an > appropriate "fix" for problems caused by Dolphin's current block behaviour -- > I'd rather reserve it for places where it conveys real meaning.) Well we're in agreement about the second bit anyway :-) > > I am, BTW, not arguing that you should add it to D6 (though I'd not be sorry if > you did). It seems easy enough to do myself. I think it appropriate that advanced users add this themselves. Regards Blair |
Free forum by Nabble | Edit this page |