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.