了解 GNU Smalltalk 闭包

Understanding GNU Smalltalk Closure

下面的一段代码给出了错误 error: did not understand '#generality'

pqueue := SortedCollection new.
freqtable keysAndValuesDo: [:key :value |
   (value notNil and: [value > 0]) ifTrue: [
      |newvalue|
       newvalue := Leaf new: key count: value.
       pqueue add: newvalue.
   ]
].

    [pqueue size > 1] whileTrue:[
      |first second new_internal  newcount|
      first := pqueue removeFirst.
      second := pqueue removeFirst.
      first_count := first count.
      second_count := second count.
      newcount := first_count + second_count.
      new_internal := Tree new: nl count: newcount left: first right: second.
      pqueue add: new_internal.
    ].

不一致在行 pqueue add: new_internal 中。当我删除这一行时,程序会编译。我认为问题与迭代块 [pqueue size > 1] whileTrue:pqueue add: new_internal.

有关

注:这是基于哈夫曼码构建解码树的算法。

错误消息扩展

Object: $<10> error: did not understand #generality
MessageNotUnderstood(Exception)>>signal (ExcHandling.st:254)
Character(Object)>>doesNotUnderstand: #generality (SysExcept.st:1448)
SmallInteger(Number)>>retryDifferenceCoercing: (Number.st:357)
SmallInteger(Number)>>retryRelationalOp:coercing: (Number.st:295)
SmallInteger>><= (SmallInt.st:215)
Leaf>><= (hzip.st:30)
optimized [] in SortedCollection class>>defaultSortBlock (SortCollect.st:7)
SortedCollection>>insertionIndexFor:upTo: (SortCollect.st:702)
[] in SortedCollection>>merge (SortCollect.st:531)
SortedCollection(SequenceableCollection)>>reverseDo: (SeqCollect.st:958)
SortedCollection>>merge (SortCollect.st:528)
SortedCollection>>beConsistent (SortCollect.st:204)
SortedCollection(OrderedCollection)>>removeFirst (OrderColl.st:295)
optimized [] in UndefinedObject>>executeStatements (hzip.st:156)
BlockClosure>>whileTrue: (BlkClosure.st:328)
UndefinedObject>>executeStatements (hzip.st:154)
Object: $<10> error: did not understand #generality
MessageNotUnderstood(Exception)>>signal (ExcHandling.st:254)
Character(Object)>>doesNotUnderstand: #generality (SysExcept.st:1448)
SmallInteger(Number)>>retryDifferenceCoercing: (Number.st:357)
SmallInteger(Number)>>retryRelationalOp:coercing: (Number.st:295)
SmallInteger>><= (SmallInt.st:215)
Leaf>><= (hzip.st:30)
optimized [] in SortedCollection class>>defaultSortBlock (SortCollect.st:7)
SortedCollection>>insertionIndexFor:upTo: (SortCollect.st:702)
[] in SortedCollection>>merge (SortCollect.st:531)
SortedCollection(SequenceableCollection)>>reverseDo: (SeqCollect.st:958)
SortedCollection>>merge (SortCollect.st:528)
SortedCollection>>beConsistent (SortCollect.st:204)
SortedCollection(OrderedCollection)>>do: (OrderColl.st:64)
UndefinedObject>>executeStatements (hzip.st:164)

我们可以从这个问题中学到的一个知识是养成阅读堆栈跟踪以试图理解它的习惯。让我们关注最后几条消息:

1. Object: $<10> error: did not understand #generality
2. MessageNotUnderstood(Exception)>>signal (ExcHandling.st:254)
3. Character(Object)>>doesNotUnderstand: #generality (SysExcept.st:1448)
4. SmallInteger(Number)>>retryDifferenceCoercing: (Number.st:357)
5. SmallInteger(Number)>>retryRelationalOp:coercing: (Number.st:295)
6. SmallInteger>><= (SmallInt.st:215)
7. Leaf>><= (hzip.st:30)
8. optimized [] in SortedCollection class>>defaultSortBlock (SortCollect.st:7)

每一行代表一个方法的激活。每行代表一条消息,消息的顺序向上(就像在任何 Stack 中发生的那样)。可以在调试器中看到每个激活的完整细节。然而,在这里,我们只看到 class >> #selector 对。我们可以从这些汇总信息中发现几个有趣的事实:

  1. 在第 1 行中,我们得到了实际错误。在这种情况下,我们得到了一个 MessageNotUnderstood 异常。消息的接收者是 Character $<10>,即换行符。

  2. 第 2 行和第 3 行确认未理解的消息是 #generality

  3. 第 4、5 和 6 行显示了最终将 #generality 发送到错误对象(换行)的消息的进程。虽然第 4 行和第 5 行对于没有经验的 Smalltalker 来说可能看起来晦涩难懂,但第 6 行具有关键信息:一些 SmallInteger 收到了 <= 消息。此消息将失败,因为参数不合适。根据我们已经得到的信息,我们知道参数是换行符。

  4. 第 7 行显示 SmallInteger >> #<= 来自相同选择器 #<=Leaf 中的实现方式。它告诉我们 Leaf#<= 委托给它已知的某些 Integer

  5. 第 8 行说明了我们为什么要处理比较选择器 #<=。原因是我们正在整理一些集合。

因此,我们正在尝试对 Leaf 对象的集合进行排序,这些对象依赖于一些整数进行比较,但其中一个 "integers" 不是 Number,而是Character换行。

如果我们在考虑这些信息的情况下查看 Smalltalk 代码,我们会看到:

  • SortedCollectionpqueueLeaf 对象是添加到其中的项目。

  • SortedCollection不变属性是它的元素总是按照给定的标准排序。因此,每次我们向它 add: 一个元素时,该元素都会被插入到正确的位置。因此比较消息 #<=.

  • 现在让我们在代码中查找#add:。除了上面那个,下面还有一个:

      new_internal := Tree new: nl count: newcount left: first right: second.
      pqueue add: new_internal.
    

    这个很有趣,因为这是错误发生的地方。但是请注意,我们在这里添加的不是 Leaf,而是 Tree。但是等等,可能 TreeLeaf 属于同一层次结构。实际上,TreeLeaf都代表无环图中的个节点。此外,代码在读取时证实了这个想法:

      Leaf new: key count: value.
      ...
      Tree new: nl count: newcount left: first right: second.
    

看到了吗? LeafTree都有一些keynew:的参数)和一些count。此外,Treesleftright 分支,Leaf 没有(当然!)

因此,原则上,可以将 Tree 的实例添加到我们的 pqueue 集合中。这不可能是导致错误的原因。

现在,如果我们仔细观察 Tree 的创建方式,我们可以看到一个可疑的参数 nl。这很有趣,原因有二:(i) 变量 nl 没有在我们给出的代码部分定义,以及 (ii) 变量 nlkeyTree 用于响应 #<= 消息。因此,nl 必须是换行符 $<10>。这很有意义,因为 nlnewline 的缩写,而在 Linux 世界中,换行符是换行符。

结论:问题似乎是由于 Tree 的密钥使用了错误的参数 nl