Class method that makes an object from multiple arguments - smalltalk

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

Related

initialize with pharo smalltalk

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.

How does one access a derived classes member variables from within the superclass in Tcl?

Code:
package require TclOO
oo::class create Supe {
variable cape boots
constructor {} {
puts -nonewline "Supe: "
puts [info class variables [self class]]
}
}
oo::class create Clark {
superclass Supe
variable glasses suit
constructor {} {
puts -nonewline "Clark: "
puts [info class variables [self class]]
next
}
}
set hero [Clark new]
Output:
Clark: glasses suit
Supe: cape boots
Is it possible to get a list of Clark's member variables from within Supe's constructor without passing them into Supe as an argument?
Ultimately, the goal is to dynamically set derived class variables from a dict argument:
foreach {varName} [info class variables [self class]] {
variable $varName [dict get $args $varName]
}
If the above code can be used in the superclass constructor, it would avoid putting it in each derived class constructor.
You can get the name of the object with self object, or just self. You can then get the class of the object with info object class. And finally, you can get the member variables of a class with info class variables.
Putting it all together results in:
[info class variables [info object class [self]]]
My take does not add to the answer already given, but looks at the motivating problem of the OP:
Ultimately, the goal is to dynamically set derived class variables
from a dict argument:
What I have been using in the past to batch-update an object's state is sth. along the lines of:
oo::class create C {
variable a b
constructor {aDict} {
my variable {*}[lsort -unique [my lookupVars [info object class [self]]]]
# dict with aDict {;}
lassign [dict values $aDict] {*}[dict keys $aDict]
}
method lookupVars {currentClass} {
set v [info class variables $currentClass]
foreach scl [info class superclasses $currentClass] {
if {$scl eq "::oo::object"} {
break
}
lappend v {*}[my lookupVars $scl]
}
return $v
}
method print {} {
foreach v [info object vars [self]] {
my variable $v
puts "<$v> [set $v]"
}
}
}
The key items are:
lookupVars is a naturally recursive implementation walking the class, direct, and indirect superclasses to collect of defined per-class variables. This follows up on what Donal describes as a discovery of "properties". Note that this is limited in several ways (e.g., mixins are ignored, also TclOO's internal linearisation scheme is not reflected, no control for duplicates etc.)
lassign can be used to set multiple variables at once. Alternatively, but with some side effects, dict with "loads" a given dict's content into variables available for the current scope. my variable ?varName ...? will provide method-local links to the collected per-class variables. This way, you save a script-level loop and don't have to filter the provided dict for unmatched keys.
Watch:
oo::class create D {
superclass C
variable c d
}
oo::class create E {
superclass D
variable e f
}
[D new {a 1 b 2 c 3 d 4}] print
[E new {a 1 b 2 c 3 d 4 f 5 e 8 x 3}] print
[D new {a 1 b 2 c 3 d 4 f 5 x 3}] print

Is it possible to assign a message to a variable?

I'm studying different kinds of programming languages to see how they differ and their advantages/disadvantages.
I'm currently particularly interested in languages that use messages for method calls; and I was wondering if it's possible to somehow assign a message to a variable in Squeak/Pharo/Smalltalk/etc.
So let's say both class A and B have the message foo:; how can I then do something like this:
|msg|
msg := foo: 12.
a msg.
b msg.
Where a and b are instances of A and B respectively
Pharo has Message class. So you can create it as
Message selector: #foo: argument: 12
But currently Message is not used for execution purposes.
What you are looking for are perform: messages.
So you can do what you need like this:
| selector arg |
selector := #foo:.
arg := 12.
a perform: selector with: arg.
b perform: selector with: arg
"for messages of other `shape`"
a perform: selector.
a perform: selector with: arg with: arg. "up to 3 args"
a perform: selector withArguments: { arg . arg }
As for fancy syntax
msg := foo: 12.
does not make any sense according to Smalltalk. But what you can do is to define a class like GenericMessage with 2 instance variables: selector and arguments. Then you redefine doesNotUnderstand: on the class side like this:
GenericMessage class >> doesNotUnderstand: aMessage
^ self new
selector: aMessage selector;
arguments: aMessage arguments;
yourself
Then you also define a method for Object:
Object>>#performMessage: aGenericMessage
^ self
perform: aGenericMessage selector
withArguments: aGenericMessage arguments
Then your code will look like this:
|msg|
msg := GenericMessage foo: 12.
a performMessage: msg.
b performMessage: msg.
Depending whether you want just to send a message by it's name or store functionality for later use, you have different options. In the latter case you can use blocks which are Smalltalk's version of closures. You define a block as:
block = [ :arg | arg foo: 12 ]
this means that whenever you evaluate an arg with the block foo: 12 will be sent to the arg.
Your code will look like this then:
|block|
block := [ :arg | arg foo: 12 ].
block value: a.
block value: b
P.S. I bet you have the same thing in Objective-C and they are also called blocks

How to make instances of a class in Smalltalk?

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

Cincom Visualworks Smalltalk - Class Methods Initialization

Apologies for the newbie question, but I failed to figure this despite of long try.
I created a matrix class using NewClass feature in Cincom Visualworks.
Smalltalk.Core defineClass: #Matrix
superclass: #{Core.Object}
indexedType: #none
private: false
instanceVariableNames: 'rowCount columnCount cellValues '
classInstanceVariableNames: ''
imports: ''
category: ''
Added the following class method:
withRowCount: rowCount withColumnCount: columnCount withCellValues: cellValues
^self new rowCount: rowCount columnCount: columnCount cellValues: cellValues.
Added the following accessor methods:
cellValues
^cellValues
cellValues: anObject
cellValues := anObject
columnCount
^columnCount
columnCount: anObject
columnCount := anObject
rowCount
^rowCount
rowCount: anObject
rowCount := anObject
I have this code in workspace:
|myMatrix|
myMatrix := Matrix rowCount: 5 columnCount: 5 cellValues: 5.
Transcript show: (myMatrix rowCount).
But compiler says that message undefined.
I guess my class method is not working as expected.
Can someone please point out where I am going wrong?
First: Matrix doesn't have a rowCount:columnCount:cellValues: method. You probably meant Matrix withRowCount: 5 withColumnCount: 5 withCellValues: 5.
Second, I'm thinking methods return the value of the last expression. So chaining methods doesn't work quite like that. (And even if it did, that still looks like one message.)
Your class method should probably read like
withRowCount: rowCount withColumnCount: columnCount withCellValues: cellValues
| newMatrix |
newMatrix := self new.
newMatrix rowCount: rowCount;
columnCount: columnCount;
cellValues: cellValues.
^newMatrix
The ; breaks up the messages and tells Smalltalk to send all three to newMatrix.
Then you can use it like
|myMatrix|
myMatrix := Matrix withRowCount: 5 withColumnCount: 5 withCellValues: 5.
Transcript show: (myMatrix rowCount).