【问题标题】:How can I add methods to a class at runtime in Smalltalk?如何在 Smalltalk 运行时向类添加方法?
【发布时间】:2010-12-16 12:59:19
【问题描述】:

我正在为基于 XML 的 Web 服务构建 Smalltalk API。 XML 服务是如此的有规律,以至于与其手动编写方法,我想我只需覆盖#doesNotUnderstand: 以通过MyApi class>>compile: 动态添加方法,然后在工作区中调用所有方法一次,然后删除 DNU 并有我的好 API。

这很好用,但是将一个巨大的字符串传递给#compile: 对我来说感觉真的不对;在 Python 和其他语言中,我可以将一个经过语法检查的 lambda 附加到一个类,以更安全的方式实现类似的效果。例如:

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)

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

肯定有像 Python 版本一样干净的东西吗?

【问题讨论】:

    标签: smalltalk squeak


    【解决方案1】:

    如果你只是想让源字符串更清晰地体现方法:

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

    如果你想对源代码进行语法检查,你可以从这样的模板方法开始:

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

    然后相应地填充模板,如:

    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.
    

    当然,如果提取一些方法,所有这些都会更清楚:)

    【讨论】:

      【解决方案2】:

      假设你有模板方法:

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

      然后你可以将它复制到其他类,只是不要忘记设置选择器和类 如果您不想混淆系统浏览器。或者,如果您不在乎,只需将副本安装在方法字典中即可。

      | 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.
      

      【讨论】:

      • 嗯,我也想改一个参数;不是简单的复制。 Sean 建议稍微修改一下这个总体思路,调用 decompileWithTemps to get a string that I can perform substitutions on, but that still involves a textual replacement. It seems to me that String>>format:` 可能确实是目前清理这个问题的最佳方法。
      【解决方案3】:

      好吧,compile: 接受一个字符串。如果你想要更安全的东西,你可以构建一个分析树并使用它。

      【讨论】:

        【解决方案4】:

        我会使用块:

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

        你仍然可以让himaker成为一种方法

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

        【讨论】:

        • 如果你可以将一个块绑定到一个方法,那将是完美的,但除了反编译成 AST、修改它是一个方法 AST,重新编译,然后附加到类。不完全是微不足道的。
        • 哈我明白了,我认为那是因为在 Python 中方法和块重合。而在 smalltalk 中,返回语义使块和方法成为不同的“对象”。否则你可以做到AClass mathodAt: #myselector put: aBlock block(在 GNU Smalltalk 中)
        • Smalltalk 方法是类方法字典中 CompiledMethod 的实例。多亏了鸭子类型,如果你真的想在这个字典中放一个块,你可以继承 BlockClosure 并从 CompiledMethod 添加一些必需的方法(例如#run:with:in:),它的行为就像一个编译的方法(见Pharo By Example 第 14.7 章)。这是一种有用的技术,但在这里似乎有点过头了。
        • @Sean DeNigris:我说的不是#run:with:in: 技巧,而是让VM 认为你在methodDict 中有一个CM。这不适用于 Pharo(字节码的块参考 CM),但在 gnu smalltalk 块中是真正的编译代码(CompiledBlock)。所以在 gnu smalltalk 中,如果字节码 non-local-return 它可以工作。我仍然不确定 external-context-pop-push 是否有效。
        猜你喜欢
        • 1970-01-01
        • 2017-08-20
        • 1970-01-01
        • 2023-04-09
        • 1970-01-01
        • 2011-10-04
        • 2013-01-21
        • 1970-01-01
        相关资源
        最近更新 更多