【问题标题】:Smalltalk: What is the sender of a message?Smalltalk:消息的发送者是什么?
【发布时间】:2018-10-18 00:14:33
【问题描述】:

在 smalltalk 中,一切都是通过向接收者对象发送消息来实现的。它的语法通常遵循格式receiver message,其中receiver 是消息发送到的对象。现在我忍不住想知道,smalltalk 消息的发送者是什么?考虑以下 smalltalk 语句:

aMorph color: Color yellow

我可以将 aMorph 视为消息的接收者,但发送者呢?标准的 smalltalk 消息语法只有接收者和消息(选择器 + 参数),我无法确定发送者是什么以及在哪里。或者,一条消息实际上可以发送自己?

我记得浏览过一篇关于 pharo smalltalk 中的反射的文章,其中提到了消息的发送者,但我无法找到或理解这个“发送者”是什么。任何人都可以向我解释这个吗?谢谢。

【问题讨论】:

  • 根据this answer,Pharo理解sender这个词。你还记得看到另一个词吗?还是您在问如何检索消息的发件人?
  • 我是从这篇文章中找到的:pharo.gforge.inria.fr/PBE1/PBE1ch15.html 并且看完之后,我仍然无法理解发件人是什么。同一网站上的前一篇文章解释了消息语法(第 4 章),其中没有提到消息发送者之类的东西。

标签: oop message smalltalk


【解决方案1】:

无论何时发送消息,发送者都是在运行时确定和设置的。从当前执行方法的角度来看,它回答了“我们是如何到达这里的?”这个问题。在最常见的情况下,发送者将是发送导致当前方法被调用的消息的任何方法。 (一个例外是 #doesNotUnderstand: 处理程序,它将消息重定向到原定目的地以外的某个地方)例如,在 Squeak 中,如果您 doIt on aMorph color: Color yellow从工作区中,发送者将是 UndefinedObject>>DoIt。如果您从 MyObject>>myTestSender 发送相同的消息,则发件人将是 MyObject>>myTestSender

现在假设您将aMorph 包装在一个proxy object myProxy 中,这是MyProxyObject 的一个实例,它的doesNotUnderstand: 方法将它接收到的所有内容转发到底层aMorph 对象。在这种情况下,当你 doIt myProxy color: Color yellow 时,发送者将是 MyProxyObject>>doesNotUnderstand:。 (除非您的 doesNotUnderstand: 方法进一步操纵运行时...如果需要,它可以这样做)这实际上是一个很好的例子,说明您何时可能需要查看 #color: 的发件人是谁:它正在被调用但你不明白从哪里来,因为代理添加了一个对你来说可能并不明显的间接级别。

因此,为了查看发件人是谁,您可以在color: 方法中添加以下内容:

Transcript show: thisContext sender asString.

从您的代码的角度来看,处理发送者是隐式的,并且在正常代码执行期间由 Smalltalk 运行时为您处理。除非您正在对某些代码进行故障排除或需要在运行时自省或更改事物,否则您不会经常查看发件人。

现在这可能会引发一个问题:“thisContext 到底是什么?”这是一个代表调用堆栈顶部的特殊变量,许多人最初都很难理解。请参阅How does Smalltalk manipulate call stack frames 了解更多信息。

附录(希望这将消除 Leandro 的答案与我的答案之间的任何混淆)

Leandro 的回答是将 sender 视为一个通用术语,并考虑更大的历史背景,而我的答案是更现代的以 Squeak/Pharo 为中心的术语,具有非常具体的含义。我同意 Leandro 的观点,即术语 sender 是模棱两可的,并且在实现中没有标准化(正如我们不同的答案所证明的那样。)只是为了进一步混淆水域,在 Blue Booksender 的引用 正在谈论发送上下文...既不是self 也不是thisContext sender。但是,问题的 cmets 中提到的链接的含义是明确的(即thisContext sender),正如在提到 Squeak/Pharo 代码时通常所说的那样。因此,哪个答案是正确的取决于您是否正在查看特定的 Smalltalk 实现(在这种情况下,正确的用法是您使用的实现所决定的任何一个)或者在谈论没有特定的 Smalltalk 实现时作为更通用的术语(在这种情况下,Leandro 是正确的:它需要解释,因为它的用法已经超载到几乎毫无意义)

【讨论】:

    【解决方案2】:

    您可以将发件人视为self。换句话说,当一个方法被激活时(即在其执行期间),self 所代表的对象可以被解释为方法体中发送的所有消息的发送者。

    考虑例如方法#paint:,在OurClass中定义为

    paint: aMorph
      aMorph color: Color yellow
    

    一旦执行此方法,OurClass 的(子)实例接收到paint: 消息,将变为self。那么,在这个方法的激活过程中,self 可以将color: 的发送者的角色归结为aMorph

    但是请注意,这只是一个解释问题。例如,您还可以考虑正在执行的进程并将发送者标识为激活#color: 的进程框架。

    虽然这两种解释都是有效的,但现实情况是,在 Smalltalk 中,发送者的概念是无关紧要的,因为发送消息的行为是原始的,即在虚拟机中实现,而不是在虚拟映像中实现。

    当然,出于交流目的,将角色分配给某人甚至谈论发件人是很有用的。但隐含的对象取决于上下文。例如,在调试时,您将使用调用帧来识别发送者。但是,由于消息发送“神奇”地发生,因此实际上不需要将发送者的角色附加到任何对象。

    即使在 Bee Smalltalk 中,由于没有 VM,您可以访问运行时的内部,但发送者的概念也很繁琐,而且相当不必要。从技术上讲,Bee 中的每个发送都有一个 SendSite 对象,该对象执行发送消息所需的所有步骤(PIC、查找等)并且由于您可以检查这些对象并向它们发送消息,您可以推测在 Bee发件人是SendSite。但同样,这是有待解释的。事实上,SendSite 类中没有 sender ivar,只是因为 Smalltalk 语义不需要这样的东西。

    附录

    当我说sender 的概念需要解释时,我的意思是在发送机制的任何实现中都没有使用这样的概念。更准确地说,执行发送的(原始)代码包含一个执行方法查找的缓存例程,它只考虑receiverbehaviorselector,而忽略“发送者”。

    还要注意,消息发送从“调用者”获取的主要“数据”是参数。如果我们深入挖掘所有这些的机器代码实现,我们可以说另一个是返回地址,用于链接帧。这就是为什么我提到调用者进程框架的“发送者”的概念,这对于它在调试器的实现中的具体化是有意义的。

    我的观点是,在 Smalltalk 中,sender 没有明确的定义,这就是为什么与receiverselectorargumentsbehavior 等相关概念相比,很难识别它和method send

    确实可以使用伪变量thisContext获取当前激活的sender。如果这样做,您将在调用框架中获得模拟self 的对象,这是sender 的另一种解释。即使通过引用该对象,您可以利用它来提供更多功能,sender 仍将不存在于 Message 对象和消息发送机制中。

    【讨论】:

    • 我可以眯着眼睛说“好吧,我可以看到他要去哪里”,直到最后三段作为一般性陈述是错误的。虽然发送消息的实际机制是在 VM 中实现的,但它绝对暴露在我熟悉的所有 Smalltalks 的映像中。发送者既可以是自省的(即 thisContext 发送者)也可以是被操纵的(即 MethodContext sender:receiver:method:arguments: 至少在 Squeak 方言中)。公平地说,我不熟悉 Bee,它可能会像你说的那样工作,但你的断言与“大多数”Smalltalks 冲突。
    • @blihp 感谢您的反馈。我已经扩展了我的答案,试图解决你的反应,这对我来说是有效和有趣的。
    • 好的,我想我明白了问题所在:我们正在回答两个不同的问题。 cmets 中没有足够的空间来解决...我将更新我的详细答案。
    • @blihp 感谢您的澄清。好答案!
    【解决方案3】:

    如果您对 Smalltalk 的工作原理感兴趣,请查看 @blihp 和 @leandro-caniglia 的答案。此外,Deep Into Pharo14.5 上下文:表示方法执行)有关于 Context(在 Pharo 3 之前命名为 MethodContext)的信息。

    如果你想尝试一下,至少在 Pharo 中,伪变量 thisContext 可以访问当前执行点。你可以放:

    thisContext copy inspect.
    

    在您的方法中查看您可以获得有关特定执行点的哪些信息。此信息包括发件人。

    但如果您想知道是否应该在您的方法中定期访问消息的发件人,答案是。如果您需要知道以常规方法发送消息的对象,请将发送者 (self) 作为附加参数传递。

    【讨论】:

    • 到目前为止,您的最后一段是本次讨论中最有用的建议!
    • @LeandroCaniglia 抱歉拼错了你的名字 :)
    【解决方案4】:

    您已将 aMorph 标识为消息的接收者。 现在,aMorph 做了什么? 它向各种事物发送消息。 当 aMorph 响应它收到的消息时, 它是发件人。它是接收者,它成为发送者。 当 aMorph 完成后,它不再是发送者,而是对发送它正在处理的消息的任何内容给出答案。

    当然,每次 aMorph 发送消息时,接收者都会成为发送者,同时它会得出答案。

    等等。

    【讨论】:

      【解决方案5】:

      在消息anObject bar 的下方明确指出发送消息的人是SomeClass instance。但是在响应消息的方法中,你必须求助于thisContext的服务。

      SomeClass>>foo
          | anObject |
          anObject := AnotherClass new.
          anObject bar
      
      AnotherClass>>bar
          | context senderObject receiver |
          context := thisContext.
          senderObject := context sender receiver.
          receiver := self
      

      【讨论】:

      • 所以上下文对象是发送者,而接收者是获取消息并通过激活其方法来响应的对象?这是正确的解释方式吗?
      • @LordYggdrasill,我纠正了我的答案,因为有一个错误。我不知道所做的更正是否澄清了您的问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-04
      • 2010-10-27
      • 2017-09-17
      • 2015-07-28
      • 2016-04-14
      • 2012-04-07
      • 2013-08-13
      相关资源
      最近更新 更多