No namespace pollution - wizardofosmium/porolog GitHub Wiki
Creating a Logic Engine without Adding Methods
This example shows:
- how to define predicates without creating methods, and
- how to create separate independent engines per instance.
The commands
builtin :member
predicate :prime
provide handy shortcuts to the predicates by creating methods to access them.
The default adds these methods to Object so they are available everywhere.
In small programs, this can be quite useful, but in large programs, this can be
quite a problem; called namespace pollution.
The commands
builtin :member, class_base: Widget
predicate :prime, class_base: Widget
also provide handy shortcuts to the predicates by creating methods to access them;
however, using class_base: Widget, the methods are added to Widget as class methods
and as instance methods, so they are available everywhere in the class Widget.
This scoping of methods is probably sufficient in most cases.
If you need to completely avoid creating methods, you can simply create the predicates
and store them in instance variables by calling Porolog::Predicate.new directly.
Creating Predicates
Porolog::Predicate.new takes three parameters:
name, which is the name of the predicatescope, which is the "name" of the logic namespace the predicate is to be defined inbuiltin:, which is a boolean (defaulting to false) indicating whether the predicate is to be evaluated using a library definition
The scope does not have to be a String or Symbol; it can be any instance. Thus, separate classes can contribute to the same logic space
if desired. If a specific instance is provided, then the predicates are only defined in that instance's logic space. Thus,
these commands have different logic space scopes:
class Widget
def initialize
predicate1 = Porolog::Predicate.new :my_pred, :Widget
predicate2 = Porolog::Predicate.new :my_pred, self.class
predicate3 = Porolog::Predicate.new :my_pred, self
end
end
Using :Widget defines a logic space, which can be referred to anywhere by using the Symbol.
Using self.class also defines a logic space, which can be referred to anywhere by using the Class.
These are effectively the same.
The third predicate is scoped to the specific instance. The logic space can be referred to anywhere but needs the instance to refer to it. Thus Ruby name spaces are independent of Porolog name spaces.
The above scopes can be referred to with the following commands:
class Gadget
def initialize(widget)
predicate1 = Porolog::Predicate.new :my_pred, :Widget
predicate2 = Porolog::Predicate.new :my_pred, Widget
predicate3 = Porolog::Predicate.new :my_pred, widget
end
end
The Complete Example
This example puts everything together so that namespace pollution is completely avoided.
Note that when providing arguments to predicates, you must now use a dot ., for example:
predciate :my_pred
my_pred(:X).fact!
becomes
my_pred = Porolog::Predicate.new :my_pred
my_pred.(:X).fact!
require 'porolog'
class Numbers
def initialize(number)
@number = number
@prime = Porolog::Predicate.new :prime, self
search_prime = Porolog::Predicate.new :search_prime, self
gtr = Porolog::Predicate.new :gtr, self, builtin: true
is = Porolog::Predicate.new :is, self, builtin: true
noteq = Porolog::Predicate.new :noteq, self, builtin: true
between = Porolog::Predicate.new :between, self, builtin: true
@prime.(2).fact!
@prime.(3).fact!
@prime.(:X) << [
between.(:X, 4, 370),
is.(:X_mod_2, :X) {|x| x % 2 },
noteq.(:X_mod_2, 0),
search_prime.(:X, 3),
]
search_prime.(:X, :N) << [
is.(:N_squared, :N) {|n| n ** 2 },
gtr.(:N_squared, :X),
:CUT
]
search_prime.(:X, :N) << [
is.(:X_mod_N, :X, :N) {|x,n| x % n },
noteq.(:X_mod_N, 0),
is.(:M, :N) {|n| n + 2 },
:CUT,
search_prime.(:X, :M),
]
end
def kind
@prime.(@number).valid? ? :prime : :not_prime
end
def solutions
@prime.(:x).solve_for(:x)
end
end
number = Numbers.new 120
puts number.kind.inspect
number = Numbers.new 23
puts number.kind.inspect
puts number.solutions.inspect