Distributed Declarative Ruby
(following is an excerpt from my e-mail to YUPI mailing list)
I would like to present you Distributed Declarative Ruby.
It is a simple declarative language based on clips (syntax, ideas),
and ruby (parser,interpreter,implementation).
It’s existence serves few purposes.
- to prove:
- that it’s possible and easy to make distributed declarative programs
- that ruby syntax is flexible enough to represent such a language
- to show:
- declarative programming to some of you
- ruby to all of you
- to stimulate you to share with others with your own (unfinished) projects and ideas
Ok, let’s start (1)
we will run Ddr program from file t12.2.ddr
find free console and run:
ruby ddrserver.rb
find another one and run:
ruby ddr.rb t12.2.ddr someone1
type something, hit enter
find another one and run:
ruby ddr.rb t12.2.ddr someone2
write something here, hit enter
nice?
For every example application you want to run remember to:
- stop server
- run server
- run application
Ok, let’s start (2)
Declarative language consist of two things: facts and rules.
Fact looks like this:
[ :animal, :cat ]
as you see it’s intention is to mean that cat is an animal
technically it’s just a tuple of two symbols (~strings)
and here is some longer fact:
[ :little, :cat, :kitty ]
in DDR you can define fact with “fact” keyword :)
fact [ :animal, :dog ]
fact [ :little, :dog, :doggy ]
Rules are constructed with two things:
- conditions - that defines when the rule should start working
- body - that defines what will that rule change (in facts set)
Simplest rule may look like this:
rule( [ :animal, :cat ] ) {
fact [ :yes, :definitely, :cat, :is, :an, :animal ]
}
The meaning of this rule is:
If there is any fact that looks like [ :animal, :cat ] in the fact set
then add [ :yes, :definitely, :cat, :is, :an, :animal ] to the fact set
In this rule [ :animal, :cat ] is a condition.
And fact [ :yes, :definitely, :cat, :is, :an, :animal ] is a body.
That’s enough of theory, let’s start with examples.
I’ve attached you a file t01.1.ddr
It contains the following:
#simple match
fact [ :animal, :cat ]
fact [ :animal, :dog ]
fact [ :cat, :rulez ]
fact [ :dog, :sucks ]
rule( [ :animal, :dog ] ) {
p "dog is animal"
}
“p” in rule is just a function that will print it’s parameter
So, this program will only print “dog is animal”
Simple enough?
Ok, another one: t02.2.ddr
#wildcard match
fact [ :animal, :cat ]
fact [ :animal, :dog ]
fact [ :cat, :rulez ]
fact [ :dog, :sucks ]
rule( [ :animal, Any ] ) {
p "animal found"
}
The only difference is ” Any” keyword in place of “:dog”
As you see Any is not preceded by “: “, which means that it’s not a symbol.
Any have special meaning.
[ :animal, Any ] condition will match any two-element fact with first element being :animal
Run it and check if you predicted the results correctly.
And another one: t03.1.ddr
#wildcard with variable
fact [ :animal, :cat ]
fact [ :animal, :dog ]
fact [ :cat, :rulez ]
fact [ :dog, :sucks ]
rule( [ :animal, { Any => :NAME } ] ) {
p "animal found: " + NAME
}
Things got a little complicated here.
“Any” has changed to ” { Any => :NAME }”
It means we take the value matched by Any (it can be any value :)) and put it into constant named NAME.
As you see we then use that constant in rule’s body.
Run it and check if it’s still working till that point :)
t04.1.ddr:
#wildcard with variable
fact [ :animal, :cat ]
fact [ :animal, :dog ]
fact [ :cat, :rulez ]
fact [ :dog, :sucks ]
rule( [ :animal, { Any => :NAME } ] ) {
p "animal found: " + NAME
fact [ :lifeForm, NAME ]
}
Here additionally we define a fact whenever we find some animal.
t05.1.ddr
See it.
Nothing new here, just longer facts.
t06.1.ddr
No new logic. But you can see how rules start to work on newly added facts.
Still with me?
Make sure you understand everything above and feel OK with syntax.
We are going to develop something real this time!
It will be …. chat.
NO WAY! you will surely say, but it is possible! and we will do it!
t07.1.ddr
Look in file. Really. Please :)
Quite a few of new things here this time.
First $ME - it’s just global variable. Every time you see $ME in a program think of it like you were seeing “xxx”.
Next - fact [ :message, “lol” ] - string in fact.
Don’t worry, it works exactly like a symbol, but allows spaces.
BTW, you can also use numbers or any other ruby type in facts.
Next, there is /.*/. It’s just a regexp. This one works like Any but only for strings.
You can use Any here if you wish instead of regexp.
Next,
rule( [ :welcomeMessage, { /.*/ => :MSG } ],
Not[ :welcomed, $ME ] ) {
there are actually 2 conditions, not one.
One is [ :welcomeMessage, { /.*/ => :MSG } ], then is coma, and then Not[ :welcomed, $ME ].
Conditions are logically AND-ed, so all must match for the whole rule to match.
Additionally second condition is preceded by ” Not”.
It’s just simple negation. It means that in fact set no rule can match against [ :welcomed, $ME ].
So.. what this rule actually means?
If there is a fact [ :welcomeMessage, Any ] and there is no fact that matches [ :welcomed, “xxx” ]
then rule matches and it’s body will be run.
Second rule have 3 conditions.
And one more new thing is hiding in third condition.
Not[ :messagePrinted, $ME, :MSG ]
Look at :MSG .
Notice that :MSG has been assigned to, one line earlier.
So, it’s now representing some value.
If MSG were eg. “asdf” then this condition would match only if there is no fact [ :messagePrinted, “xxx”, “asdf” ]
If you feel you don’t understand something here I will help you by saying that interpreter is trying to match every possible combination of facts to your conditions. And rule is run on every combination that matches.
How does it work as a whole? Think for yourself. Or just check it.
I know, nothing amazing yet.
t09.1.ddr
It’s chat with working input.
Second rule has a bug. Anyone found it? Why it’s working correctly anyway?
New things are: “proc” in condition, and input.
In a rule like that one:
[ :lastChecked, proc { VAL+1 > 10 } ]
” proc” means “procedure”
This procedure is contained between “{” and “}”.
It is run every time the condition tries to match.
When interpreter tries to match fact [ :lastChecked, 20 ] with mentioned condition it do the following:
- check if first element is the same. It’s :lastChecked in both condition and fact. So that part matches.
- next it runs procedure from condition. inside procedure VAL is substituted with value from fact. This time it’s 20.
Procedure compares 21 > 10 , so it returns true. Interpreter treat true as positive match, so all condition matches.
Second new thing is:
begin
fact [ :message, $stdin.read_nonblock(1000) ]
fact [ :messagesSent, MSG_ID + 1 ]
rescue
end
this will try to create fact with one element being read from standard input.
If there is no data on standard input - exception will be thrown -
and program will continue to run outside begin/end block. No facts will be inserted in that scenario.
If stdin have some data - they will be read correctly and put as one element of a fact.
Second fact will also be added to fact set in that scenario.
t10.1.ddr
3 new things here.
First is ” proc” instead of first condition.
Ddr follows the concept that says that rule can be spawned only once for specific set of rules.
In other words, if some rule will spawn once for facts 1, 2, 3, then it can spawn again if (eg.) it matches against facts 3,4,5
but it will never again spawn for facts 1,2,3.
That is why proc is being run as first condition. This proc will change it’s value every second, giving the rule a chance to spawn for the same following facts every new second.
Don’t think about it yet if it’s too hard.
Next:
{ [ :messagesSent , { Any => :MSG_ID } ] => :LAST_MSG_FACT }
As you see normal condition [ :messagesSent , { Any => :MSGID } ] is surrounded by {} and bound (=>) to LASTMSG_FACT constant.
Every fact has it’s own number. If the rule is spawned for some facts, the number of fact that matched [ :messagesSent , { Any => :MSGID } ] will be accessible in rule’s body by LASTMSG_FACT constant.
Next:
retract LAST_MSG_FACT
This will simply delete some fact from fact set.
“retract” takes a number of fact (fact index)
t11.1.ddr
it’s similar but additionally will delete unneeded [ :lastChecked, Any ] facts.
t12.2.ddr
final version, it has to be run with parameter
” p” changed to “print” to give better visual effect
some logic corrections :)
t12.3.ddr
stripped version
no fact cleanup
no welcome message
summary
It works, it’s nice. It was fun to write. I see no use for declarative languages for myself, so I don’t expect to write any more of it.
