Rubylog - Prolog interpreter for ruby

Rubylog is a Prolog-like DSL for Ruby. The language is inspired by Jamis Buck, and the implementation is based on Yield Prolog, with lots of sintactic and semantic additions.

Getting started

First, install the gem

$ gem install rubylog

or, if you use bundler, add this line to your Gemfile:

gem 'rubylog', '~>1.0.0'

First, you have to create a theory and write your code in the block given to it:

require 'rubylog'

MyTheory = theory do
  # your code here
end

All further examples in this file should be written within the theory block.

Data types

Rubylog is similar to Prolog, but there are quite a few differences. In Rubylog, you can use any Ruby object as data.

Rubylog variables are (undefined) constant names:

A, B, ANYTHING

A variables whose name starts with ANY... (case-insensitive) is a don't-care variable (like _ in Prolog).

Structures are in a different order than they are in prolog:

functor_for String, :likes
'John'.likes('beer')

which would be likes('John','beer') in prolog.

Lists are just Ruby arrays:

[1, 2, 3]

They can have splats:

[1, 2, *T]

Which would be [1,2|T] in Prolog, however, in Rubylog, splats are not limited to the end.

Predicates

You can assert a rule with the if method:

X.drinks(Y).if X.has(Y).and X.likes(Y)

This would be drinks(X,Y) :- has(X,Y), likes(X,Y) in Prolog.

You can assert facts with if(:true), or, as a shorthand you can use the bang syntax:

'John'.likes! 'milk'

Bang assertions return their first argument (which is 'John' in this case), so they can be chained:

'John'.likes!('beer').has!('beer')

You can also use unless:

A.good.unless A.bad

Nullary predicates are symbols, similar to Prolog:

'John'.drinks('beer').if :false.and(:cut!).or(:true)

Built-in predicates

Some built-in predicates and their Prolog equivalents:

Rubylog  Prolog
-------  ------ 
:true     true
:fail     fail
.and      ,
.or       ;
:false    \+
.is       =
.is_not   =/=
.in       member
:cut!     !

There are some new ones:

is_not, not_in, all, any, one, none, iff

Unification

In Rubylog, unification works quite the same in Prolog, with the is functor.

A.is(B)

Using arrays, you can benefit the splats:

[1,2,3,4].is([A,B,*T])      # [1,2,3,4] = [A,B|T]     in prolog
[1,2,3,4].is([*H,*T])       # append(H, T, [1,2,3,4]) in prolog

The in predicate unifies the first argument with any member of the collection:

4.in([1,2,3,4])

You can use guards:

A[String].in(["asdf",5,nil]).each { p A }   # outputs "asdf"
A[/x/].in(["asdf","xyz"]).each { p A }      # outputs "xyz"
A[thats < 5].in([4,5,6]).each { p A }       # outputs 4

Moving between Ruby and Rubylog

Running a query

If you want to run a query, you have many different syntaxes:

prove ('John'.drinks 'beer')  # => true
true? ('John'.drinks 'beer')  # => true
('John'.drinks 'beer').true?  # => true
'John'.drinks? 'beer'         # => true

Enumerations

Structure implements Enumerable, and yields the solutions. Within the enumeration block, you can access the values of your variables.

'John'.drinks! 'beer'
('John'.drinks X).each {p X}      # outputs 'beer'
('John'.drinks X).map{X}          # => ['beer']
('John'.drinks X).count           # => 1

Procs as predicates

You can invoke Ruby codes in Rubylog rules with a proc:

'John'.likes(Y).if proc{ Y =~ /ale/ }

or in most cases you can use just a block:

'John'.likes(Y).if { Y =~ /ale/ }

The predicate succeeds if the block returns a true value.

Procs as functions

is and in can take a proc or block argument, which they execute and take its return value:

X.good.if X.is { 'BEER'.downcase }
X.good.if X.in { get_good_drinks() }

The two modes of Rubylog

Rubylog has two modes, DSL and native. DSL code is executed only once at compile time, and is used for describing the Rubylog program. Native code is executed runtime. Any block passed to Rubylog structures native code.

('John'.drinks X).and { X != 'beer'}.each { p X }
^^^^^^^^^^^^^^^^^^^^^^              ^^^^^^          dsl mode
                       ^^^^^^^^^^^^        ^^^^^    native mode

In dsl mode, variables are Rubylog::Variable objects. In native mode, variables are substituted with their respecitve value (or nil if they are not bound).

All built-in rubylog predicates are clean logical programming predicates witout a side-effect. If you want some side-effect, you always go into native mode.

Rubylog as a test suite

You can write simple tests using the check method:

theory do
  check :true
  check { 5+5 == 10 }
end

You sould put this file in “./logic/something_logic.rb”. Then you can run it with

rubylog logic/something_logic.rb

Or you can run all files in logic/**/*_logic.rb with

rubylog

Other built-in libraries

File system

You can make some queries on the file system:

require "rubylog/builtins/file_system"

theory do
  check "README".filename_in "."

  X.dirname_in(".").each { puts X }
end

Reflection

You can make some metaprogramming with Rubylog

require "rubylog/builtins/reflection"

theory do
  functor_for String, :likes

  check "John".likes("Jane").structure(:likes, ["John", "Jane"])

  "John".likes(X).if X.likes("John")
  "Jane".likes!("John")
  check "John".likes("Jane").follows_from "Jane".likes("John")

  "John".likes!("milk")
  check "John".likes("milk").fact
  check "John".likes("beer").fact.false

end

Contributing

To the language

  • Create an issue on the issue tracker.

  • We will discuss it.

  • Maybe I'll introduce it.

To the implementation

  • Create an issue on the issue tracker.

  • We will discuss it.

  • Maybe I'll implement it.

  • If not, fork the project.

  • Implement it.

  • Have fun.

  • Post a pull request.

Copyright

Copyright © 2013 Bernát Kalló. See LICENSE.txt for further details.