2010-12-16 16 views
7

Estoy creando una API Smalltalk para un servicio web basado en XML. El servicio XML es tan regular que, en lugar de escribir los métodos a mano, pensé que simplemente anularía #doesNotUnderstand: para agregar métodos dinámicamente a través de MyApi class>>compile:, luego llamar a todos los métodos una vez en un área de trabajo, luego eliminar el DNU y tener mi API agradable .¿Cómo puedo agregar métodos a una clase en tiempo de ejecución en Smalltalk?

Esto funciona muy bien, pero pasar una cuerda gigante al #compile: me parece realmente malo; en Python y en otros idiomas, podría adjuntar una lambda bien revisada por sintaxis a una clase para lograr un efecto similar de una manera más segura. Ej .:

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) 

frente

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 

Seguramente debe ser algo tan limpia como la versión de Python?

Respuesta

4

Si lo que desea es la cadena de origen para reflejar con mayor claridad el método:

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

Si desea que la fuente que se va a comprobar la sintaxis, que podría comenzar con un método plantilla de la siguiente manera:

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

y luego rellenar la plantilla en consecuencia, como:

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. 

Por supuesto, todo esto sería más claro si se extrajeron algunos métodos :)

4

Suponga que tiene método de la plantilla:

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

A continuación, se puede copiar a otra clase, sólo que no se olvide de ajustar el selector de clase y si no No quiero confundir el navegador del sistema. O si no te importa, simplemente instala la copia en el diccionario de métodos.

| 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

Bueno, también quiero cambiar un parámetro; no solo copiar Sean sugirió modificar ligeramente esta idea general, llamando a 'decompileWithTemps' para obtener una cadena en la que pueda realizar sustituciones, pero que todavía implique un reemplazo textual. Me parece que el formato 'String >>:' probablemente sea la mejor manera de solucionar esto en este punto. –

0

Bueno, compile: takes a String. Si quieres algo más seguro, puedes construir un árbol de caracteres y usarlo.

0

me gustaría utilizar bloques:

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

Todavía se puede hacer himaker un método

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

Si pudieras * enlazar * un bloque a un método, sería perfecto, pero no veo ninguna manera de hacerlo sin descompilar en AST, modificándolo como un método AST, recompilando y luego adjuntando a la clase. No es exactamente trivial. –

+0

Ha veo, creo que es porque en Python el método y el blok coinciden. Mientras que en smalltalk el retorno semántico hace que el bloque y el método sean distintos "objetos". De lo contrario, podría haber hecho 'AClass mathodAt: #myselector put: aBlock block' (En GNU Smalltalk) – mathk

+1

Los métodos de Smalltalk son instancias de CompiledMethod en el diccionario de métodos de la clase.Gracias a la tipificación de pato, si realmente deseaba poner un bloque en este diccionario, podría crear una subclase de BlockClosure y agregar algunos métodos requeridos desde CompiledMethod (por ejemplo, #run: with: in :), y actuaría como un método compilado (ver [Pharo por ejemplo] (http://pharobyexample.org/) Capítulo 14.7). Esta es una técnica útil, pero parece exagerada aquí. –

Cuestiones relacionadas