# \[SUMMARY\] Inference Engine (#37)

**URL:** <https://rubytalk.org/t/summary-inference-engine-37/19394>\
**Category:** ruby-talk\
**Created:** [7 July 2005 13:05 UTC](https://rubytalk.org/t/summary-inference-engine-37/19394 "2005-07-07T13:05:51Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![James\_Edward\_Gray\_II](https://avatars.discourse-cdn.com/v4/letter/j/ea5d25/32.png) [@James\_Edward\_Gray\_II](https://rubytalk.org/u/James_Edward_Gray_II)\
**Post date:** [7 July 2005 13:05 UTC](https://rubytalk.org/t/summary-inference-engine-37/19394/1 "2005-07-07T13:05:51Z")

</div>

irb(main):005:0\> are any programmers happy programmers?  
&nbsp;&nbsp;=\> Yes, some programmers are happy programmers.  
&nbsp;&nbsp;irb(main):006:0\> are all Ruby programmers happy programmers?  
&nbsp;&nbsp;=\> Yes, all ruby programmers are happy programmers.  
&nbsp;&nbsp;irb(main):007:0\> are any Python programmers happy programmers?  
&nbsp;&nbsp;=\> I don't know.

True artificial intelligence doesn't seem that far off after all.

Did you know you could use irb as an interface like that? I didn't! Be sure  
and glance at Pit Capitain's code to see just how that is done.

Daniel Sheppard sent in a nice object model, translating the rules into a class  
hierarchy. I recommend browsing through that too.

Here though, I want to look into Paulo Capriotti's code. Paulo decided to model  
the relationships as a directed graph, where vertices are the groups and edges  
represent their relationships. To save a little work, Paulo used the graph Ruby  
library as a starting point. Here's the beginning of the solution:

&nbsp;&nbsp;require 'graph'

&nbsp;&nbsp;def opposite\_type(type)  
&nbsp;&nbsp;&nbsp;&nbsp;type == :provable\_true ? :provable\_false : :provable\_true  
&nbsp;&nbsp;end

&nbsp;&nbsp;class Property  
&nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :name, :type  
&nbsp;&nbsp;&nbsp;&nbsp;def initialize(name, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@name = name  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@type = type  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def opposite  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.new(@name, @type == :positive ? :negative : :positive)  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def Property.create(x)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if x.respond\_to?(:opposite)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.new(x, :positive)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def hash  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"#{@name}##{@type}".hash  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def eql?(other)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@name == [other.name](http://other.name) and @type == other.type  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;alias == eql?

&nbsp;&nbsp;&nbsp;&nbsp;def to\_s  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = @name  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @type == :negative  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"not-" + res  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
&nbsp;&nbsp;  
&nbsp;&nbsp;# ...

First we see the definition of a simple helper method to convert between two  
opposites: Things we can prove to be true and things we can prove are false.

Next, we have a helper class for a Property. I wasn't sure what that meant at  
first, so I had to dig a little. Obviously, much of this class is just support  
for hashing the object (hash() and eql?()). We have a class method to create()  
a Property when needed and an interesting method to give us an opposite()  
Property. The to\_s() method is also interesting. We can see from it that a  
negated Property gets a leading "not-" when converted to a String.

All of this clicked into place for me when I turned on Paulo's debug mode and  
watched the code work. Here's an example:

&nbsp;&nbsp;\> All dogs are mammals  
&nbsp;&nbsp;adding edge dog, not-dog, provable\_false  
&nbsp;&nbsp;adding edge not-dog, dog, provable\_false  
&nbsp;&nbsp;adding edge dog, dog, provable\_true  
&nbsp;&nbsp;adding edge not-dog, not-dog, provable\_true  
&nbsp;&nbsp;adding edge mammal, not-mammal, provable\_false  
&nbsp;&nbsp;adding edge not-mammal, mammal, provable\_false  
&nbsp;&nbsp;adding edge mammal, mammal, provable\_true  
&nbsp;&nbsp;adding edge not-mammal, not-mammal, provable\_true  
&nbsp;&nbsp;...  
&nbsp;&nbsp;adding edge dog, mammal, provable\_true  
&nbsp;&nbsp;adding edge not-mammal, not-dog, provable\_true  
&nbsp;&nbsp;...  
&nbsp;&nbsp;adding edge not-mammal, dog, provable\_false  
&nbsp;&nbsp;adding edge not-dog, mammal, provable\_false  
&nbsp;&nbsp;...  
&nbsp;&nbsp;adding edge mammal, not-dog, provable\_false  
&nbsp;&nbsp;adding edge dog, not-mammal, provable\_false  
&nbsp;&nbsp;...

As you can see, a Property is a representation of the groups we are describing  
to the inference engine. Opposites represent everything not in that Property,  
or group. So the "dog" Property has an opposite of "not-dog", or non-dog, if  
it's easier to think of that way.

What's the rest of that stuff about edges and what we can prove though? That's  
the graph being built, as I mentioned earlier. Let's examine that code now:

&nbsp;&nbsp;# ...  
&nbsp;&nbsp;  
&nbsp;&nbsp;class Knowledge \< DirectedHashGraph  
&nbsp;&nbsp;&nbsp;&nbsp;attr\_reader :contradiction

&nbsp;&nbsp;&nbsp;&nbsp;def initialize  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@contradiction = false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Add a property and some tautologies.  
&nbsp;&nbsp;&nbsp;&nbsp;# Here we assume that the property and  
&nbsp;&nbsp;&nbsp;&nbsp;# its opposite are not void.  
&nbsp;&nbsp;&nbsp;&nbsp;def add\_property(x)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x = Property.create(x)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;safe\_add\_edge(x, x.opposite, :provable\_false)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;safe\_add\_edge(x.opposite, x, :provable\_false)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;safe\_add\_edge(x, x, :provable\_true)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;safe\_add\_edge(x.opposite, x.opposite, :provable\_true)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Add en edge. Never throw.  
&nbsp;&nbsp;&nbsp;&nbsp;def safe\_add\_edge(x, y, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(:add\_edge\_throw) do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_edge(x, y, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Add an edge. Throw if the edge already exists.  
&nbsp;&nbsp;&nbsp;&nbsp;def add\_edge(x, y, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;debug\_msg "adding edge #{x}, #{y}, #{type}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self[x,y]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;unless self[x,y] == type  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@contradiction = true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;debug\_msg " \tcontradiction"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw :add\_edge\_throw, :contradiction  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;debug\_msg "\ti know"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;throw :add\_edge\_throw, :i\_know  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super(x, y, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Add an edge and its contrapositive.  
&nbsp;&nbsp;&nbsp;&nbsp;def add\_assertion(\*args)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x, y, type = get\_stmt(\*args)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;catch(:add\_edge\_throw) do  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_edge(x, y, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_edge(y.opposite, x.opposite, type)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:normal  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Extract statement values.  
&nbsp;&nbsp;&nbsp;&nbsp;def get\_stmt(\*args)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case args.size  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 1  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x, y, type = args[0].x, args[0].y, args[0].type  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when 3  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x, y, type = args[0], args[1], args[2]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;raise "Invalid argument list in #{caller.first}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return add\_property(x), add\_property(y), type  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Discover all possible deductions  
&nbsp;&nbsp;&nbsp;&nbsp;# and add the corresponding edges to the graph.  
&nbsp;&nbsp;&nbsp;&nbsp;def deduce  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;each\_vertex do |v1|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;each\_vertex do |v2|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;each\_vertex do |v3|

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self[v1,v2] == :provable\_true and  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self[v2,v3] == :provable\_true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_assertion(v1, v3, :provable\_true)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self[v2,v1] == :provable\_false and  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self[v2,v3] == :provable\_true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_assertion(v3, v1, :provable\_false)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if self[v1,v2] == :provable\_true and  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self[v3,v2] == :provable\_false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add\_assertion(v3, v1, :provable\_false)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;break if @contradiction  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Return true if a statement is provable.  
&nbsp;&nbsp;&nbsp;&nbsp;# Return false if its negation is provable.  
&nbsp;&nbsp;&nbsp;&nbsp;# Return nil if it is undecidable.  
&nbsp;&nbsp;&nbsp;&nbsp;def test(\*args)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;x, y, type = get\_stmt(\*args)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case self[x,y]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when nil  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when type  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
&nbsp;&nbsp;  
&nbsp;&nbsp;# ...

This class isn't as complicated as it appears, once you grasp the flow of how  
it's used. When an assertion is made to the engine, it will first call test()  
to see if it already knows that information or if it contradicts any other  
information.

Assuming the test passes, add\_assertion() gets called. That method starts by  
calling get\_stmt() to turn its arguments into two properties and a type  
(:provable\_true or :provable\_false). Note that the return sequence of  
get\_stmt(), uses add\_property() to ensure that the graph already contains basic  
relationships for that property (all dogs are dogs, for example). These basic  
relationships are added using safe\_add\_edge(), which is really just a shell over  
add\_edge() that filters out the symbols that are thrown by the latter. Getting  
back to add\_assertion(), we can see that it too uses add\_edge(), but it returns  
the symbol thrown or its own :normal to indicate if we already knew about the  
assertion or if it contradicts what we did know. I know that's a lot to take  
in, but if you just follow the chain of method calls it's pretty basic stuff.

Finally, deduce() is called after an assertion has been added. By walking the  
edges, deduce() will fill in any details the new assertion uncovers. This is  
done by checking relationships between three independent complete sets of  
vertices, so relationships of the form one -\> two -\> three will be spotted.

You can see that this class is always monitoring for a @contradiction in each of  
these methods. However, I don't think this ever comes into play, since each  
assertion is tested before it's added. (Correct me if I'm wrong Paulo!)

On to the user interface:

&nbsp;&nbsp;# ...  
&nbsp;&nbsp;  
&nbsp;&nbsp;["Assertion", "Question"].each do |c|  
&nbsp;&nbsp;&nbsp;&nbsp;Struct.new(c, :x, :y, :type)  
&nbsp;&nbsp;end

&nbsp;&nbsp;class UI

&nbsp;&nbsp;&nbsp;&nbsp;# Parse input and return a statement  
&nbsp;&nbsp;&nbsp;&nbsp;def get\_statement(line)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case line  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# assertions  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^all (.\*)s are (.\*)s\.?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Assertion.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_true )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^no (.\*)s are (.\*)s\.?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Assertion.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2).opposite,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_true )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^some (.\*)s are not (.\*)s\.?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Assertion.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_false )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^some (.\*)s are (.\*)s\.?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Assertion.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2).opposite,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_false )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# questions  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^are all (.\*)s (.\*)s\?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Question.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_true )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^are no (.\*)s (.\*)s\?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Question.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2).opposite,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_true )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^are any (.\*)s not (.\*)s\?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Question.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_false )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^are any (.\*)s (.\*)s\?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Struct::Question.new( Property.create($1),  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Property.create($2).opposite,  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:provable\_false )  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;# description  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when /^describe (.\*)s\.?$/  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return Property.create($1)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return nil  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Return a description of the relation  
&nbsp;&nbsp;&nbsp;&nbsp;# between x and y.  
&nbsp;&nbsp;&nbsp;&nbsp;# Assume that x is positive and that  
&nbsp;&nbsp;&nbsp;&nbsp;# x -\> y is not undecidable.  
&nbsp;&nbsp;&nbsp;&nbsp;def describe\_edge(x, y, aff = true)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case @k[x,y]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when :provable\_true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case y.type  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when :positive  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "All #{[x.name](http://x.name)}s are #{[y.name](http://y.name)}s"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when :negative  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "No #{[x.name](http://x.name)}s are #{[y.name](http://y.name)}s"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when :provable\_false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case y.type  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when :positive  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if aff  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "Some #{[x.name](http://x.name)}s are not #{[y.name](http://y.name)}s"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "Not all #{[x.name](http://x.name)}s are #{[y.name](http://y.name)}s"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when :negative  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if aff  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "Some #{[x.name](http://x.name)}s are #{[y.name](http://y.name)}s"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return "Not all #{[x.name](http://x.name)}s are not #{[y.name](http://y.name)}s"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;# Return a list of sentences which describe  
&nbsp;&nbsp;&nbsp;&nbsp;# the relations between x and each other node.  
&nbsp;&nbsp;&nbsp;&nbsp;# Assume that x is positive.  
&nbsp;&nbsp;&nbsp;&nbsp;def describe\_node(x)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = []  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@k.each\_vertex do |y|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if y.type == :positive and not x == y  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if @k[x,y] == :provable\_true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res \<\< describe\_edge(x,y)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif @k[x,y.opposite] == :provable\_true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res \<\< describe\_edge(x,y.opposite)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif @k[x,y]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res \<\< describe\_edge(x,y)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif @k[x,y.opposite]  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res \<\< describe\_edge(x,y.opposite)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def say(value)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case value  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"Yes"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"No"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"I don't know"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def initialize  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@k = Knowledge.new  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def wait\_for\_input  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print '\> '  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;gets  
&nbsp;&nbsp;&nbsp;&nbsp;end

&nbsp;&nbsp;&nbsp;&nbsp;def run  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while line = wait\_for\_input  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;line.chomp!  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;line.downcase!  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;stmt = get\_statement(line)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if stmt.class == Struct::Assertion  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;case @k.test(stmt)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when true  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "I know"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;when false  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "Sorry, that contradicts what I already know"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@k.add\_assertion(stmt)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@k.deduce  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "OK"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif stmt.class == Struct::Question  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;value = @k.test(stmt)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print say(value)  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if value.nil?  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print "\n"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts ", #{describe\_edge(stmt.x, stmt.y, value).downcase}"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;elsif stmt.class == Property  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;describe\_node(stmt).each do |sentence|  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts sentence  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;puts "I don't understand"  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;&nbsp;&nbsp;end  
&nbsp;&nbsp;end  
&nbsp;&nbsp;  
&nbsp;&nbsp;def debug\_msg(msg)  
&nbsp;&nbsp;&nbsp;&nbsp;puts msg if $debug  
&nbsp;&nbsp;end

&nbsp;&nbsp;if $0 == \_\_FILE\_\_  
&nbsp;&nbsp;&nbsp;&nbsp;ui = UI.new  
&nbsp;&nbsp;&nbsp;&nbsp;ui.run  
&nbsp;&nbsp;end

Again, this looks like a lot of code, but it's not overly complicated, once you  
find the flow. Look at the last few lines to get a hint about where to begin.  
First, initialize() creates a Knowledge object to keep tract of our assertions  
and answer our questions. From that point on, run() handles the interactive  
session.

The first thing you need to know is that run() uses a couple of helper methods:  
say() for output and wait\_for\_input() for input. It also calls get\_statement()  
to parse input. The return from get statement is either a single Property (the  
"describe" command), a Struct::Assertion representing an assertion made, or a  
Struct::Question representing a question asked. You can see those Structs  
defined just before the UI class starts. They're just two properties and a  
type.

The other thing to notice about get\_statement() is that it does very basic  
Regexp based parsing. It uses a super clever trick of spotting a trailing "s"  
to find the two groups in a line like, "Are all Ruby programmers happy  
programmers?" Of course, this does not work across all legal inputs:

&nbsp;&nbsp;\> All oxen are mammals.  
&nbsp;&nbsp;I don't understand  
&nbsp;&nbsp;\> All James's pets are Dana's pets.  
&nbsp;&nbsp;OK  
&nbsp;&nbsp;\> Are all James's pets Dana's pets?  
&nbsp;&nbsp;I don't know

To see another approach, examine Pit Capitain's parsing, especially the  
IE.split() method.

Getting back to run() in Paulo's UI class, you'll see that the rest of the  
method just handles the three different returns from get\_statement().  
Assertions are handled as I explained earlier. Questions rely on  
describe\_edge() for answers and the "describe" command triggers a call to  
describe\_node(). That's the entire UI.

I promise to end this long summary very soon now, but I wanted to mention just  
one more detail. Paulo uses a helper method all through the code that's defined  
near the end. Here's another look at it:

&nbsp;&nbsp;def debug\_msg(msg)  
&nbsp;&nbsp;&nbsp;&nbsp;puts msg if $debug  
&nbsp;&nbsp;end

The goal here is obvious, print a lot of extra information if I set this global  
variable to true. This is a trick that we all use sometimes in testing. I just  
wanted to suggest one minor change to enhance this trick: Switch $debug to  
$DEBUG. Then you can trigger the messages with a command-line switch like so:

&nbsp;&nbsp;$ ruby -e 'p $DEBUG'  
&nbsp;&nbsp;false  
&nbsp;&nbsp;$ ruby -d -e 'p $DEBUG'  
&nbsp;&nbsp;true

Slide that one into your bag of tricks, because it comes in handy.

My thanks to all three submitters for some really great code to poke around in  
and play with.

Tomorrow, we have a simple but handy hack to explore. It's an easy quiz so I  
want to see all you beginners out there playing along!

---

<div class="post-metadata">

**Author:** ![Paolo\_Capriotti](https://avatars.discourse-cdn.com/v4/letter/p/3d9bf3/32.png) [@Paolo\_Capriotti](https://rubytalk.org/u/Paolo_Capriotti)\
**Post date:** [8 July 2005 02:39 UTC](https://rubytalk.org/t/summary-inference-engine-37/19394/2 "2005-07-08T02:39:50Z")

</div>

> You can see that this class is always monitoring for a @contradiction in each of  
> these methods. However, I don't think this ever comes into play, since each  
> assertion is tested before it's added. (Correct me if I'm wrong Paulo!)

Yes, if no assertion is added when the opposite is already in the  
graph after a deduction, one is granted not to obtain contradictions  
on successive deductions.  
This follows from the fact that (after the deduce method has been  
called) the assertions form a theory which satisfies the following  
property:  
every theorem of either of the forms a -\> b, not (a -\> b) is an axiom.  
This is not difficult to prove. One can use, for instance, boolean  
ring theory and the Stone theorem.

Paolo
