Implements a Prolog inference engine using Plain Old Ruby Objects


Keywords
engine, gem, logic, prolog
License
Unlicense
Install
gem install porolog -v 0.0.3

Documentation

porolog

Plain Old Ruby Objects Prolog

Gem Version Build Status Coverage Documentation Wiki

Introduction

porolog is a Prolog implementation using plain old Ruby objects with the aim that logic queries can be called within a regular Ruby program. The goal was not to implement a Prolog interpreter that is just implement in Ruby, but rather that a logic engine could be embedded in a larger program.

The need that this gem aims to meet is to have a Ruby program interact with a Prolog program using native Ruby objects (POROs); hence the name Porolog. The goal was to implement a minimal logic engine in the style of Prolog where Ruby objects could be passed in and Ruby objects were passed back.

This version completes modularisation, so that include Porolog is no longer required.

Dependencies

The aim of porolog is to provide a logic engine with a minimal footprint. The only extra dependency is Yard for documentation.

Installation

gem install porolog

Usage

porolog is used by:

  • requiring the library within a Ruby program
  • defining facts and rules
  • solving goals

It is entirely possible to create a Ruby program that is effectively just a Prolog program. The main purpose of Porolog though is to add declarative logic programming to Ruby and allow hybrid programming, in the same way that Ruby allows hybrid programming in the functional programming paradigm.

This then enables Ruby programs to be written spanning all the major programming paradigms:

  • Imperative
  • Functional
  • Object Oriented
  • Declarative Logic

Basic Usage

Using porolog involves creating logic from facts and rules. An example of the most basic usage, using just facts.

require 'porolog'

prime = Porolog::Predicate.new :prime

prime.(2).fact!
prime.(3).fact!
prime.(5).fact!
prime.(7).fact!

solutions = prime.(:X).solve

solutions.each do |solution|
  puts "#{solution[:X]} is prime"
end

Common Usage

Common usage is expected to be including Porolog in a class and encapsulating the engine defined.

require 'porolog'

class Numbers

  include Porolog
  extend  Porolog
  
  Predicate.scope self
  predicate :prime, class_base: self

  prime(2).fact!
  prime(3).fact!
  prime(5).fact!
  prime(7).fact!

  def show_primes
    solutions = prime(:X).solve

    solutions.each do |solution|
      puts "#{solution[:X]} is prime"
    end
  end

  def primes
    prime(:X).solve_for(:X)
  end

end


numbers = Numbers.new
numbers.show_primes
puts numbers.primes.inspect

Scope and Predicate Usage

A Predicate represents a Prolog predicate. They form the basis for rules and queries.

The Scope class enables you to have multiple logic programs embedded in the same Ruby program. A Scope object defines a scope for the predicates of a logic programme. This allows you to have different predicates with the same name.

require 'porolog'

# -- Prime Numbers Predicate --
prime = prime1 = Porolog::Predicate.new :prime, :numbers

prime.(2).fact!
prime.(3).fact!
prime.(5).fact!
prime.(7).fact!
prime.(11).fact!

# -- Pump Predicate --
prime = prime2 = Porolog::Predicate.new :prime, :pumps

prime.('pump A').fact!
prime.('pump B').fact!
prime.('pump C').fact!
prime.('pump D').fact!

# -- Assertions --
assert_equal  [:default,:numbers,:pumps],     Scope.scopes
assert_equal  :prime,                         prime1.name
assert_equal  :prime,                         prime2.name

solutions = [
  { X:  2 },
  { X:  3 },
  { X:  5 },
  { X:  7 },
  { X: 11 },
]
assert_equal  solutions,  prime1.(:X).solve

solutions = [
  { X: 'pump A' },
  { X: 'pump B' },
  { X: 'pump C' },
  { X: 'pump D' },
]
assert_equal  solutions,  prime2.(:X).solve

A Complete Example

This example shows:

  • how to define facts,
  • how to define rules,
  • how to encapsulate predicates within a class,
  • how to specify a cut,
  • how to use builtin predicates, and
  • how to initiate queries.
require 'porolog'

class Numbers

  Porolog::Predicate.scope self
  Porolog::builtin   :gtr, :is, :noteq, :between,  class_base: self
  Porolog::predicate :prime, :search_prime,        class_base: self

  prime(2).fact!
  prime(3).fact!
  prime(:X) << [
    between(:X, 4, 100),
    is(:X_mod_2, :X) {|x| x % 2 },
    noteq(:X_mod_2, 0),
    :CUT,
    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),
  ]
  
  # Unexpose `search_prime`
  private               :search_prime
  private_class_method  :search_prime

  def show_primes
    solutions = prime(:X).solve

    solutions.each do |solution|
      puts "#{solution[:X]} is prime"
    end
  end

  def primes
    Numbers.prime(:X).solve_for(:X)
  end

  def self.kind(number)
    prime(number).valid? ? 'prime' : 'not prime'
  end

  def initialize(number)
    @number = number
  end

  def kind
    prime(@number).valid? ? :prime : :not_prime
  end

end


Numbers.show_primes
number = Numbers.new 23
puts number.primes.inspect

# `prime` predicate is exposed as a class method
puts Numbers.prime(3).valid?.inspect

# `prime` predicate is exposed as an instance method
puts number.prime(3).valid?.inspect

puts ARGV.inspect
ARGV.map(&:to_i).each do |arg|
  puts "#{arg.inspect} is #{Numbers.kind(arg)}"
  number = Numbers.new arg
  puts number.kind.inspect
end

Porolog Wiki

See the Wiki for further details. https://github.com/wizardofosmium/porolog/wiki

Testing

rake test

or

rake core_ext_test
rake porolog_test
rake scope_test
rake predicate_test
rake arguments_test
rake rule_test
rake goal_test
rake variable_test
rake value_test
rake tail_test
rake instantiation_test
rake samples_test

Author

Luis Esteban MSc MTeach