2010-12-16 7 views
7

Ich baue eine Smalltalk-API zu einem XML-basierten Webdienst. Der XML-Dienst ist so regelmäßig, dass ich, anstatt die Methoden von Hand zu schreiben, einfach #doesNotUnderstand: überschreiben würde, um Methoden dynamisch über MyApi class>>compile: hinzuzufügen, dann alle Methoden einmal in einem Arbeitsbereich aufzurufen, dann die DNU zu entfernen und meine nette API zu haben .Wie kann ich einer Klasse zur Laufzeit in Smalltalk Methoden hinzufügen?

Das funktioniert gut, aber eine riesige Zeichenfolge zu #compile: passiert nur fühlt sich wirklich falsch zu mir; In Python und anderen Sprachen wäre es mir möglich, ein schön Syntax-geprüftes Lambda an eine Klasse anzuhängen, um einen ähnlichen Effekt in einer sichereren Weise zu erzielen. Z.B .:

def himaker(name): 
    def hello(self, times): 
     for x in xrange(times): 
      print "Hi, %s!" % name 
    return hello 
class C(object): pass 
C.bob = himaker('Bob') 
C.jerry = himaker('Jerry') 
a = C() 
a.bob(5) 

gegen

SomeObject>>addHello: name 
    | source methodName | 
    methodName := 'sayHello', name, 'Times:'. 
    source := String streamContents: [ :s | 
     s nextPutAll: methodName, ' count'. 
     s nextPut: Character cr. 
     s nextPut: Character tab. 
     s nextPutAll: 'count timesRepeat: [ Transcript show: ''Hi, ', name, '!'' ].' ] 
    SomeObject class compile: source 

Es muss doch etwas so sauber wie der Python-Version sein?

Antwort

4

Wenn Sie nur die Quellzeichenfolge, um deutlicher die Methode zu reflektieren:

SomeObject>>addHello: name 
    | methodTemplate methodSource | 
    methodTemplate := 'sayHello{1}Times: count 
    count timesRepeat: [ Transcript show: ''Hi, {1}!'' ].'. 
    methodSource := methodTemplate format: { name }. 
    self class compile: methodSource. 

Wenn Sie die Quelle wollen Syntax-geprüft werden, können Sie mit einer Vorlage Methode wie folgt beginnen könnte:

sayHelloTemplate: count 
    count timesRepeat: [ Transcript show: 'Hi, NAME' ]. 

Und dann füllen die Vorlage entsprechend, wie:

addHello2: name 
    | methodTemplate methodSource | 
    methodTemplate := (self class compiledMethodAt: #sayHelloTemplate:) decompileWithTemps. 
    methodTemplate selector: ('sayHello', name, 'Times:') asSymbol. 
    methodSource := methodTemplate sourceText copyReplaceAll: 'NAME' with: name. 
    self class compile: methodSource. 

natürlich all dies wäre klarer, wenn einige Methoden extrahiert wurden :)

4

Angenommen, Sie Template-Methode haben:

SomeClass>>himaker: aName 
    Transcript show: 'Hi ...' 

Dann können Sie es auf andere Klasse kopieren, nur nicht vergessen, die Selektor-und-Klasse festlegen , wenn Sie don Ich möchte den Systembrowser nicht verwirren. Oder wenn es Ihnen egal ist, installieren Sie einfach die Kopie im Method Dictionary.

| method | 

method := (SomeClass>>#himaker:) copy. 

method methodClass: OtherClass. 
method selector: #boo: . 
OtherClass methodDict at: #boo: put: method. 

method := method copy. 
method selector: #bar: . 
method methodClass: OtherClass2. 
OtherClass2 methodDict at: #bar: put: method. 
+0

Nun machen himaker, ich möchte auch einen Parameter ändern; nicht einfach kopieren. Sean schlug vor, diese allgemeine Idee etwas zu modifizieren, indem er 'decompileWithTemps 'aufruft, um eine Zeichenfolge zu erhalten, mit der ich Ersetzungen durchführen kann, die aber immer noch einen Textaustausch beinhaltet. Es scheint mir, dass "String >> format:" wahrscheinlich wirklich der beste Weg ist, um dies an dieser Stelle aufzuräumen. –

0

Nun, kompilieren: nimmt einen String. Wenn Sie etwas mehr typsicher möchten, können Sie einen Partree erstellen und diesen verwenden.

0

Ich würde Block verwenden:

himaker := [:name | [:n | n timesRepeat: [Transcript show: 'Hi , ', name, '!']]] 
hibob = himaker value: 'bob'. 
hialice = himaker value: 'alice'. 
hialice value: 2 

Sie können immer noch ein Verfahren

himaker: name 
    ^[:n | n timesRepeat: [Transcript show: 'Hi, ', name, '!']] 
+1

Wenn Sie einen Block an eine Methode * binden * könnten, wäre das perfekt, aber ich sehe keine Möglichkeit, das zu tun, um nicht in den AST zu dekompilieren, ihn als Methode AST zu modifizieren, neu zu kompilieren und dann anzuhängen zur Klasse. Nicht gerade trivial. –

+0

Ha ich sehe, ich denke das liegt daran, dass in Python Methode und Blok zusammenfallen. Während in Smalltalk die Rückkehr Semantik machen Block und Methode distinct "Objekt". Sonst hättest du 'AClass mathodAt: #myselector put: aBlock block' (In GNU Smalltalk) – mathk

+1

Smalltalk-Methoden sind Instanzen von CompiledMethod im Methodenwörterbuch der Klasse.Dank Duck Typing, wenn Sie wirklich einen Block in dieses Wörterbuch einfügen möchten, könnten Sie BlockClosure ableiten und fügen Sie einige erforderliche Methoden von CompiledMethod (zB #run: mit: in :), und es würde genau wie eine kompilierte Methode handeln (siehe [Pharo by Example] (http://pharabyexample.org/) Kapitel 14.7). Dies ist eine nützliche Technik, aber scheint hier zu übertrieben. –