RBNode theory

sendso predicate

Consider the message

"RBProgramNodeSendsoVisitor, protocol visiting"
visitMessageNode: aMessageNode

   super visitMessageNode: aMessageNode.

   goals add: (selectorVar unifyo value: aMessageNode selectorNode)

We list the selectors that it sends with

"RBNodePredicatesTest, protocol tests"
testSenderoForCompiledMethod

   self
      assert: [ :selector | 
         (RBProgramNodeSendsoVisitor >> #visitMessageNode:) sendso value:
            selector ] asGoal solutions
      equals: { 
            (RBSelectorNode value: #visitMessageNode:).
            (RBSelectorNode value: #unifyo).
            (RBSelectorNode value: #selectorNode).
            (RBSelectorNode value: #value:).
            (RBSelectorNode value: #add:) }
      modulo: #asOrderedCollection

where

"CompiledMethod, protocol *MicroKanren-RB"
sendso

   ^ self sourceNode body sendso

delegates to

"RBNode, protocol *MicroKanren-RB"
sendso

   ^ [ :anObject | 
     RBProgramNodeSendsoVisitor new
        selectorVar: anObject;
        visitNode: self;
        asGoal ]

BlockClosure objects also allow us to retrieve their own representation in terms of RBNode objects, therefore they respond to

"BlockClosure, protocol *MicroKanren-RB"
sendso

   ^ self sourceNode body sendso

as CompiledMethod objects do, so the initial test can be rephrased for them as

"RBNodePredicatesTest, protocol tests"
testSenderoForBlockClosure

   | aBlock |
   aBlock := [ :aMessageNode :goals :selectorVar | 
             super visitMessageNode: aMessageNode.
             goals add:
                (selectorVar unifyo value: aMessageNode selectorNode) ].
   self
      assert:
      [ :selector | aBlock sendso value: selector ] asGoal solutions
      equals: { 
            (RBSelectorNode value: #visitMessageNode:).
            (RBSelectorNode value: #unifyo).
            (RBSelectorNode value: #selectorNode).
            (RBSelectorNode value: #value:).
            (RBSelectorNode value: #add:) }
      modulo: #asOrderedCollection.

   self deny: (super respondsTo: #visitMessageNode:)

Observe that aBlock sends #visitMessageNode: to super even though RBNodePredicatesTest superclass doesn’t implement #visitMessageNode:, as the last #deny: checks. This shows that we use the block aBlock just for its own AST discarding its computation.

As corner cases, both for the empty block

"RBNodePredicatesTest, protocol tests"
testSenderoForEmptyBlockClosure

   self
      assert: [ :selector | [  ] sendso value: selector ] asGoal solutions
      equals: #(  )
      modulo: #asOrderedCollection

and for the identity block

"RBNodePredicatesTest, protocol tests"
testSenderoForIdentityBlockClosure

   self
      assert:
      [ :selector | [ :o | o ] sendso value: selector ] asGoal solutions
      equals: #(  )
      modulo: #asOrderedCollection

sendso yields no solutions at all.

Because we are training on a logic engine, it makes sense to use #sendso backwards, as the following test shows

"RBNodePredicatesTest, protocol tests"
testSenderoBackwards

   self arguments_testSenderoBackwards bind: [ :receiverNode :messageNodes | 
      self
         assert: ([ :aSend | 
             [ :o :ast | ast sendso value: aSend ] asGoalWithASTof: [ :o | o ] ]
                asGoal solutions next: messageNodes size)
         equals: messageNodes
         modulo: #asOrderedCollection ]

This should be a link Succeed. and onState:.