So I created a class that enforces every method (message) that is sent to it's class instance.
i.e that code:
|a|
a := Animal new.
a makeSound: 'bark'
should lead to a call to "doesNotUnderstand" (even though it exists in the class) and it should check wehther the post and pre conditions are there, i'll explain:
If a method looks like this:
makeSound: aNoise
"%self assert: [canMakeNoise = true]%"
"#self assert: [numOfLegs >= 0]#"
numOfLegs := -1
it means that asside from that main method, there is also a method called: PREmakeSound which is implementation is:
self assert: [canMakeNoise = true]
and onther method called POSTmakeSiund which is implemented as follows:
self assert: [numOfLegs >= 0]
---My question is - because of the fact that every method call is calling doesNotUnderstand, whenever I want to actually activate the method (after I cheked whatever i needed) how can I activate it as is?
hope my problem is clear...
Maybe using method wrappers would work better than using #doesNotUnderstand:?
Create a class PrePostMethod with a compiledMethod instance variable. You can then install instances of PrePostMethod in the method dictionary of a class instead of instances of CompiledMethod.
When the VM looks-up a message and gets this PrePostMethod instance instead of a CompiledMethod, it doesn't know what to do with it. Consequently, it will send "run: aSelector with: arguments in: receiver" to that PrePostMethod object. This is where you can perform custom action like checking pre-post condition.
For example:
PrePostMethod>>run: aSelector with: arguments in: receiver
| result |
self checkPrecondition: receiver
result := compiledMethod run: aSelector with: arguments in: receiver
self checkPostCondition: receiver.
^ result
As Sean suggests, an alternative solution is to change the way these methods are compiled.
You could transform the AST of the method before compilation, or change the compilation process itself. For example, with the AST transformation approach you can transform:
makeSound: aNoise
"%self assert: [ self canMakeNoise]%"
"#self assert: [ self numOfLegs >= 0]#"
numOfLegs := -1
into:
makeSound: aNoise
self assert: [
"The preconditions goes here"
self canMakeNoise ]
^ [ "Original method body + self at the end if there is no return"
numOfLegs := -1.
self ] ensure: [
"The postcondition goes here"
self assert: [ self numOfLegs >= 0 ] ]
One the one hand, these solutions would be more tedious to implement but one the other hand, they are more performant.
HTH
Could you explain more about why you're using doesNotUnderstand? My first thought would be injecting extra byte codes during compilation...
Although, the way to send a message from doesNotUnderstand would be something like:
self perform: newSelector withArguments: aMessage arguments.
The problem is that if this message is that it's easy to get into an infinite loop this way.
Related
There's an unwrap method, but the way it seems I'm supposed to use
it isn't the way it should be used. It seems like it should either be a
standalone routine or a method in a different class. What am I missing?
It appears that it doesn't care what its invocant is as long as it
gets the right Routine::WrapHandle thingy as an argument. In this
example, I wrap a subroutine and get back a WrapHandle:
sub add-two-numbers ( $n, $m ) { $n + $m }
sub do-some-stuff ( $n, $m, $o ) {
add-two-numbers( $n max $m, $m max $o );
}
put do-some-stuff( 5, 10, 15 );
# now I want to look into do-some-stuff to see what it's
# passing
my $wraphandle = &add-two-numbers.wrap: {
say "Arguments were (#_[])";
callwith( |#_ );
}
put do-some-stuff( 5, 10, 15 );
Then, I can create a different and unrelated routine and call unwrap
on that:
my &routine = sub { 1 };
&routine.unwrap( $wraphandle );
put do-some-stuff( 5, 10, 15 );
The invocant to unwrap seems superfluous. Indeed, I can call it as a
class method and it still works:
Routine.unwrap( $wraphandle );
But, it seems this should either be a routine (since the invocant
doesn't matter):
unwrap( $wraphandle ); # doesn't exist
Or a method on Routine::WrapHandle since that's the source of the
behavior:
$wraphandle.unwrap; # but, this method doesn't exist in that class
So, what am I missing about this method?
At a guess, the interface was designed one way (with the routine keeping the information and being able to remove 'passive' handles), but implemented another (with the handle already keeping all required information so it can unwrap itself).
As to the notion that unwrap should perhaps be a method on the handle: It actually is, but the method is called restore, which Routine.unwrap merely delegates to (cf core/Routine.pm:110..113):
method unwrap($handle) {
$handle.can('restore') && $handle.restore() ||
X::Routine::Unwrap.new.throw
}
If you want the full story, besides core/Routine.pm, there's also Perl6::Metamodel::WrapDispatcher defined in Perl6/Metamodel/Dispatchers.nqp. From what I can see, it certainly should be possible to implement the original design I conjectured, but it would need someone feeling strongly enough about the issue to actually do it...
I have problem many time understanding the errors i get in the squeak program, and unable to fix and debug it. like in this case:
I wrote the following code is smalltalk language in squeak:
initilize
super initialize.
path := OrderedCollection new.
-
drawOn: aCanvas
| colors |
colors := Color wheel: 10.
colors withIndexDo:[:c :i |
aCanvas fillOval: (self bounds insetBy: self width/25*i+1 )
color: c
].
-
handlesMouseDown: evt
^true.
-
mouseDown: evt
self position: self position + (10#0).
-
startAnimation
path reset.
0 to: 9 do: [:i | path add: self position +(0#(10*i))].
path := path, path reverse.
self startStepping.
-
step
path size > 0 ifTrue: [self position: path removeFirst].
and this is the code I wrote in the workspace:
myMorph := TestMorph new openInWorld.
but I am getting and error that I wrote up, something about problem with "size" in "step" method
can someone see the problem?
When you get an error message about UndefinedObject it often means that some variable was not initialized correctly, it has the value nil.
The error is that you misspelled initialize
You wrote initilize instead. This method is not called when creating your object and thus the path instance variable is let undefined (nil).
The following code works correctly - output: You chose Test 1
package main
import (
"fmt"
)
type TNameMap map[int]string
var nameMap TNameMap
func init() {
nameMap = make(TNameMap)
nameMap[1] = "You chose Test 1"
nameMap[2] = "You chose Test 2"
nameMap[3] = "You chose Test 3"
}
func main() {
fmt.Println(nameMap[1])
}
If I comment out the first line in init() i.e //nameMap = make(TNameMap) , I get a panic when main() runs, because nameMap was never initialized:
panic: runtime error: assignment to entry in nil map
But - if in init() I write nameMap := make(TNameMap)
instead of nameMap = make(TNameMap) , I get no panic, but also no output - main() simply runs and process terminates.
I understand that if I use the Initialization operator - nameMap := make(TNameMap) - I have declared a new variable nameMap that is scoped only to the init() function and so only the package level variable var nameMap TNameMap is in scope for main(), resulting in no output, because the package level var holds no map data.
But, I am confused: Why don't I get the panic in that situation? If main() is making the call on the package var, it was never initialized - so why no panic?
According to the Go spec:
A nil map is equivalent to an empty map except that no elements may be
added.
This means that you can read from a nil map, but not write. Just like the panic says "assignment to entry in nil map". If you comment out just the line nameMap = make(TNameMap) it will crash because you attempt to write to it in init (which is where the panic happens). If you comment out the entirety of init the Println will not crash because you're permitted to access (read from) a nil map.
Changing the assignment to a declaration is just masking the real issue here, what's happening is it's making all the assignments valid, and then discarding the result. As long as you make the assignments valid (either by removing them or making a temporary variable), then you will observe the same behavior in Println.
The value returned by a nil map is always the zero value of the value type of the map. So a map[T]string returns "", a map[T]int returns 0, and so on. (Of course, if you check with val,ok := nilMap[key] then ok will be false).
I have something that looks like the similar specification:
def "my spec"(Record record) {
given:
Something something = getSomething()
and:
otherThing = getOtherThing()
doFlow(something, record)
if (record.someType = Types.SOME_SPECIFIC_TYPE) {
doFlow(something, record)
}
}
def doFlow(Something something, Record record) {
when:
//code
then:
//asserts
when:
//code
and:
//mode code
then:
//code
}
However, at runtime, I get: groovy.lang.MissingMethodException: No signature of method doFlow() is applicable for arguments Something, Record values: [given values].
Both "my flow" and "doFlow" are feature methods as they have blocks such as given, when, and then. It's Spock's responsibility to invoke feature methods, and one feature method cannot call another one. If doFlow is meant to be a helper method, it should use explicit assert statements, and shouldn't have any blocks.
PS: Feature methods can't declare method parameters unless they are data-driven (i.e. have a where block).
PPS: A feature method can't just have a given/and block. (You'll get a compile error for this.)
How can I simulate a key (ctrl) being hold down while some other code is executed? What would be the implementation of the following method?
self ctrlDownWhile: [self doSomething]
You could try to "trick" the input state, by changing its "ctrlDown" state.
The bad news is that it does not have a setter-method to access it (maybe only in my version), so you may have to get there with a trick:
ctrlDownWhile:aBlock
"aBlock will see ctrlDown as true"
|indexOfCtrlState|
indexOfCtrlState := InputState allInstVarNames indexOf:'ctrlState'.
InputState default instVarAt:indexOfCtrlState put:1.
aBlock
ensure: [
InputState default instVarAt:indexOfCtrlState put:0.
].
an alternative is to create keyPress & keyRelease-events for the CTRL-key, and enqueue them into the WindowSensor, before and after the block's evaluation.