How can I add methods to a class at runtime in Smalltalk?

为君一笑 提交于 2019-12-30 01:33:28

问题


I'm building a Smalltalk API to an XML-based web service. The XML service is so regular that, rather than write the methods by hand, I figured I'd just override #doesNotUnderstand: to dynamically add methods via MyApi class>>compile:, then call all the methods once in a workspace, then remove the DNU and have my nice API.

This works great, but passing a giant string to #compile: just feels really wrong to me; in Python and other languages, I'd be able to attach a nicely syntax-checked lambda to a class to achieve a similar effect in a safer manner. E.g.:

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)

versus

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

Surely there must be something as clean as the Python version?


回答1:


If you just want the source string to more clearly reflect the method:

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

If you want the source to be syntax-checked, you could start with a template method like this:

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

And then fill the template accordingly, like:

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.

Of course, all of this would be clearer if some methods were extracted :)




回答2:


Suppose you have template method:

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

Then you can copy it to other class, just don't forget to set selector and class if you don't want to confuse the system browser. Or if you don't care, that just install the copy at 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.



回答3:


Well, compile: takes a String. If you want something more typesafe, you could build a parsetree and use that.




回答4:


I would use block:

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

You can still make himaker a method

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


来源:https://stackoverflow.com/questions/4460991/how-can-i-add-methods-to-a-class-at-runtime-in-smalltalk

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!