Here is a description of my problem. I have a Person class which has three attributes: lastname, name and birthDate.
Object subclass: #Person
instanceVariableNames: 'name lastName birthDate'
classVariableNames: ''
package: 'Moi'
i have the setters:
name: aString
name := aString
and similarly for birthDate and lastName.
For this class, i have a constructor:
Person class >> withName: aName birthDate: aDate lastName: aLastName
| person |
person := self new.
person
name: aName;
birthDate: aDate;
lastName: aLastName.
^person
To create an instance of a class i send new to the class:
person := Person new
Then, i provide values to its ivars:
person name: 'toto'; birthDate: '13 Sep 2022'; lastName: 'tata'
Now, I'm not going to have the user enter the method values himself
Person>>#withName:andBirthDate:andLastName:
For this I wrote a method generateData which takes between a method and generates the values that the method receives as arguments. i call it like this in my playground:
generateData:Person>>#withName:andBirthDate:andLastName:
once inside the method, I start by retrieving the instance variables of the class via:
iVars := aMethod variableWriteNodes.
(iVars collect: [ :i | myAllInstVars add:i name ]).
at the end, myAllInstVars has all the instance variables of the class where the method has been implemented. now i am generating random value for each variable based on its type. to do it, i do this:
resultTypeVariables collect: [ :i |
(i isFloat ) ifTrue: [ items add: ((1 to: 1000) atRandom asFloat) ].
(i = SmallInteger) ifTrue: [ items add:(1 to: 256) atRandom ].
(i isInteger) ifTrue: [ items add:(1 to: 256) atRandom ].
(i isNumber) ifTrue: [ items add:(1 to: 256) atRandom ].
(i isString ) ifTrue: [ items add:UUID new asString36].
(i == ByteString ) ifTrue: [ items add:UUID new asString36].
(i == Date) ifTrue: [ items add:(Date fromDays: (1 to: 36000)atRandom) ].
].
items contains the generated values.
This is where my problem begins.
I would like to rebuild the Person>>#withName:andBirthDate:andLastName: method by adding in its signature the values contained in items.
here is the idea i implemented. I retrieve the setters in the setter protocol like this:
setter := classMethod allSelectorsInProtocol: #'setter'.
( setter ) do: [:i|
instObject := setter,':',items.
].
but when i return instObject i get this as result:
I don't know what to do right now.
I think that the part you are missing here is the #perform: family of messages. They transform selectors into messages as follows
person := Person new.
person perform: #name: withArgument: 'toto'
where #perform:with: builds the message with selector #name: and argument 'toto'. While there are variants for any number of arguments, what you need is the one I just described.
Thus, if you have say ivars := #('toto' '12 Sep 2022' 'tata') you will be done with
setters with: ivars do: [:setter :ivar | person perform: setter with: ivar]
where setters := #(#name: #birthDate: #lastName:).
Given that in your case #allSelectorsInProtocol: collects the selectors in a Set, you might want to put them in an Array instead and sort them alphabetically for indentification:
(class allSelectorsInProtocol: #setters) asArray sorted
which will produce #(#birthDate: #lastName: #name:). Note also that this will require collecting your data in the same order so to match the arguments.
Related
I advanced a little in my code but I find myself facing another problem for two days. I would like to generate a test method using only the source code. But I have no idea how to do it.
I have a method that allows me to build the name of a test method but I can't write in it.
buildSelectorFor: aMethod
^ String streamContents: [:i || capitalize |
capitalize := true.
i << 'test'.
aMethod selector do: [:charactar |
charactar= $:
ifTrue: [ capitalize := true ]
ifFalse: [ capitalize
ifTrue: [
capitalize := false.
i << charactar asUppercase. ]
ifFalse:[ i << charactar ]]]]
so if I execute this method with this for example:
buildSelectorFor:Car>>#speed:mark:
I get this:
testSpeedMark
my goal is to get something like
testSpeedMark
self assert:....equals:...
I added a method writeTestMethod.
writeTestMethod: aMethod with: anObject
^(self buildTestSelectorFor: aMethod),'
|classMethod setter instObject method|
classMethod := aMethod methodClass.
setter := (classMethod allSelectorsInProtocol: #setter) asArray.
instObject := classMethod new.
(setter with: anObject do: [:set :ivar | instObject perform: set with: ivar]).
self assert: instObject class equals: (classMethod new) class.'
So here is what I get:
I don't know how to integrate the parameters of writetestMethod in the code I want to generate
I have a scenario where a class holds two instance variables that are mutually exclusive. That is only one can be instantiated at a time. To be precise, I have a Promise class (trying to add promises to Pharo) and it holds promiseError and promiseValue instance variables. I then want to implement the method "then: catch:".
This method should work as follows:
promiseObject := [10/0] promiseValue.
promiseObject then : [ : result | Transcript crShow : result ]
catch : [ : failure | Transcript crShow : failure ] .
I got an idea on how to implement methods that take a block as an argument from method that accepts a block and the block accepts an argument.
My attempt below will obviously not work but I have no idea on how to make it work.
then:aBlock catch: anotherBlock
|segment|
promiseValue ifNil: [ segment := promiseError ] ifNotNil: [ segment := promiseValue ].
promiseValue ifNil: [ segment := promiseValue ] ifNotNil: [ segment := promiseError ].
aBlock value:segment.
anotherBlock value: segment
This should work analogously to a try-catch block.
Have you tried something like this?
then: aBlock catch: anotherBlock
promiseError notNil ifTrue: [^anotherBlock value: promiseError].
^aBlock value: promiseValue
Note that the code does not rely on promiseValue being nil or not because nil could be a valid answer of the promise. However, if there is some promiseError, we know the promise failed, and succeeded otherwise.
Of course, here I'm assuming that this message will get sent once the promise has been successfully or unsuccessfully finished. If this is not the case, then the code should be waiting on the promise semaphore.
In Pharo I want to create a Class Method that creates a new person object and sets a name and age all in one method (two arguments)
Object subclass: #Person
instanceVariableNames: 'name age'
classVariableNames: ''
category: '...'
However I am unable to access the instance variables within the class method.
name: n : age: a
"Class method that creates a Person Object and defines its name and age"
| person1 |
person1 := self new.
person1 name := n. "Unable to compile past this point due to red syntax highlighting
person1 age := a.
^person1.
My goal is to be able to call:
aPerson := Person name: 'Pharo' age: '4'
You cannot set instance variables from a class method.
To solve your problem you could create accessor methods for your instance variables (on the instance side..) and call those from your class side constructor method:
name: n age: a
"Class method that creates a Person Object and defines its name and age"
| person1 |
person1 := self new.
person1 name: n.
person1 age: a.
^ person1
But for this purpose it is common to code a single instance method to set all the variables and preface its name with set, followed by the variable names:
setName: aString age: aNumber
personName := aString.
age := aNumber.
^ self
Now your constructor class method would look like this:
name: aString age: aNumber
^ self new setName: aString age: aNumber
I want to create a method that gets a block as an argument, and the block gets a parameter as well.
If the block returns true it should do something ( for example return 1), and if it returns false it should do something else.
this is what I did.. but I am getting syntax error on the ifTrue...
is this the way I should get as a parameter a block that receives an argument?
Mymethod: Block
Block value: 'argument'
ifTrue: [ ^1].
ifFalse: [^2].
and the call to the method :
object := myClass new.
argument :=1
boolValue := object Mymethod : [:argument | argument ==1 ]
the way you wrote it means that #value:ifTrue: message to the Block, and then you are sending #ifFalse: message to nothing (which is not possible at all. If you want to do it in one line, you should use parenthesis:
(Block value: 'argument')
ifTrue: [ ^1]
ifFalse: [^2]
Also in smalltalk it's a convention to name variables with uncapitalized, like block or aBlock
I'm new in Smalltalk (VisualAge environment) and I try to make a class that counts number of her instances. Unfortunately something dosen't work when I override the 'new' method. This is my class code:
Object subclass: #TestClassB
instanceVariableNames: 'niceVariable '
classVariableNames: 'InstanceCounter '
poolDictionaries: ''!
!TestClassB class publicMethods !
initWithNiceParameter: parameter
|testClassBInstance|
testClassBInstance:= self new.
^(testClassBInstance niceVariable: parameter)!
new
super new.
InstanceCounter isNil
ifTrue: [InstanceCounter := 0]
ifFalse: [InstanceCounter := InstanceCounter + 1].
^self
! !
!TestClassB publicMethods !
niceVariable: anObject
"Save the value of niceVariable."
niceVariable := anObject.
! !
I'd like to create new object with 'initWithNiceParameter' message:
TestClassB initWithNiceParameter: 'my super string'
But all I get is error:
TestClassB does not understand niceVariable:
It's because 'TestClassB' is also an object and seems it has no 'niceVariable' setter.
Do you have any idea how to create objects, when 'new' method is overrided?
The implementation of your method new returns self. The value of self there is the class TestClassB because new is a class method and self in a class method is the class itself.
You should return the object that got created by sending super new:
new
|instance|
instance := super new.
InstanceCounter isNil
ifTrue: [InstanceCounter := 0]
ifFalse: [InstanceCounter := InstanceCounter + 1].
^instance
or shorter:
new
InstanceCounter isNil
ifTrue: [InstanceCounter := 0]
ifFalse: [InstanceCounter := InstanceCounter + 1].
^super new
I was confused because I didn't know if #initialize method is called automatically. I use VisualAge 7.5 and I noticed, that if you create a new class using GUI (right-click, "new" -> "part...") and then save it, #initialize isn't called automatically! Even if you create a instance of your class in workspace. But if you export your class and then load it again, #initialize is called. To be more specific, class definition looks like this:
Object subclass: #InitTest
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''!
!InitTest class publicMethods !
initialize
Transcript show: 'initialize method'; cr.!
new
Transcript show: 'new method'; cr.
^super new.! !
InitTest initialize! "<- it's created automatically"
InitTest initializeAfterLoad!
I think it's very tricky. Do you know how to (re)load a class definition in VisualAge workspace, to be sure that #initialize is called (without writing InitTest initialize)?
Slightly OT, but the #ifTrue:ifFalse is unnecessarily complex. The Smalltalk way to initialize class-level variables is in a class-side #initialize* like so:
TestClassB class>>#initialize
InstanceCounter := 0
Now, when you load TestClassB into the system, InstanceCounter will be initialized and you can simplify from Johan's short version to:
TestClassB class>>#new
InstanceCounter := InstanceCounter + 1.
^super new
or lazily