Propositional logic
Nelumbo is three-valued: a query is true, false, or
unknown. The result notation [facts][falsehoods] shows the two
sides; [()][] means "true", [][()] means "false", and
[..][..] means "unknown". Edit and press Run (or Cmd/Ctrl+Enter).
Make the query below come out true by changing only the
operator (the expected-result annotation [()][] means the editor will underline
a mismatch until it is true).
import nelumbo.logic false | true ? [()][]
Facts & queries
Ground truth is asserted with fact. Objects and their functors are declared first
(Person :: Object introduces a type, FactType ::= ... a fact shape),
then a query binds a variable and enumerates every matching answer.
Add a fact so that friends(Klaas, Kees) holds.
import nelumbo.logic Person :: Object FactType ::= friends(<Person>,<Person>) Person ::= Piet, Jan, Klaas, Kees fact friends(Klaas, Kees) friends(Klaas, Kees) ? [()][]
Rules & recursion
A rule <=> defines how a functor is derived; if attaches a guard
to each alternative, and a rule may reference itself to recurse. The classic Fibonacci rule has
a base case for n<=1 and a recursive step for n>1.
Define factorial so that factorial(5) = 120.
import nelumbo.integers Integer ::= factorial(<Integer>) Integer n, r factorial(n)=r <=> r=1 if n<=0, r=n*factorial(n-1) if n>0 factorial(5)=r ? [(r=120)][..]
Data types
Nelumbo ships built-in types - integers, rationals, strings, collections, datetime - each
importable, with its own operators and functions. Declare a variable of a type, then let a query
compute and bind it. Operators like + and functions like len come from
the imported module.
Fix the concatenation so a = "lotus" and its length check
(len(a) = 5) passes.
import nelumbo.strings String a Integer d "lo"+"tus"=a & len(a)=d ? [(a="lotus",d=5)][..]
Relational KB
A knowledge base is facts plus rules over relations. Base facts (here pc,
parent-child) are asserted directly; rules with quantifiers such as E[x]
(there exists) and A[y] (for all) derive new relations from them. Below, the mother
relation m is derived from pc via an existential over female children.
Add a father relation f that mirrors the mother rule, using
E[y] over male children, so f(Beatrix) = Bernhard.
import nelumbo.logic
Person :: Object
Male :: Person
Female :: Person
FactType ::= pc(<Person>,<Person>)
Person ::= p(<Person>), c(<Person>), m(<Person>), f(<Person>)
Person a, b
Female x
Male y
c(a)=b <=> pc(a,b)
p(a)=b <=> pc(b,a)
m(a)=b <=> E[x](c(x)=a & b=x)
f(a)=b <=> E[y](c(y)=a & b=y)
Female ::= Juliana, Beatrix
Male ::= Bernhard
fact pc(Juliana, Beatrix),
pc(Bernhard, Beatrix)
Person who
f(Beatrix)=who ? [(who=Bernhard)][..]
Custom DSL
Nelumbo is a meta-language: declare your own functors and patterns with an explicit precedence
(#N) and natural-language statements and queries become first-class syntax. Here a
friend functor is derived from base friends facts, then the phrase
X is a friend of Y is declared as a Boolean pattern that reads as
ordinary English.
A synonym phrase X knows Y has been declared but its rule is
wrong (X knows Y <=> X=Y). Fix the rule so Who knows Piet lists the
same friends as the phrase above.
import nelumbo.logic Person :: Object FactType ::= friends(<Person>,<Person>) Person ::= friend(<Person>) Person ::= Piet, Jan, Klaas Person A, B, Who friend(A)=B <=> friends(A,B) | friends(B,A) | friend(friend(A))=B Boolean ::= <Person> knows <Person> #30 Person X, Y X knows Y <=> friend(Y)=X fact friends(Piet, Jan), friends(Jan, Klaas) Who knows Piet ? [(Who=Jan),(Who=Klaas),(Who=Piet)][..]
Transformations
A transformation rule ::> rewrites one construct into several. The
attr declaration below expands - at declaration time - into a type, a
:= setter, a private fact relation, and a getter rule. Declaring
attr Person name String then behaves as if all four were written by hand.
An address attribute has been added to Person, but
Piet's address is set to the wrong value. Set it to "Kalverstraat" so the query
matches.
import nelumbo.strings
Root ::= attr <Type> <NAME> <Type> #100
Type OT, AT
NAME AN
attr OT AN AT ::> {
AT ::= <OT>.AN
Root ::= <OT>.AN := <AT>
private FactType ::= AN(<OT>,<AT>)
OT o
AT a
o.AN=a <=> AN(o,a)
o.AN := a ::> {
fact AN(o,a)
}
}
Person :: Object
attr Person name String
attr Person address String
Person ::= Piet
Piet.name := "Piet"
Piet.address := "Kalverstraat"
String a
Piet.address=a ? [(a="Kalverstraat")][..]
Scoping
A { } block introduces a namespace; a private declaration is hidden
outside its block; and the same name can be reused independently in separate scopes. Both scopes
below declare their own XXX member without any clash.
The third scope declares Cc with members XXX and
YYY, but its query binds z to the wrong one. Fix the query so
z=XXX holds.
import nelumbo.logic
{
Aa :: Object
private Aa ::= XXX
Aa x
x=XXX ? [(x=XXX)][..]
}
{
Bb :: Object
private Bb ::= XXX
Bb y
y=XXX ? [(y=XXX)][..]
}
{
Cc :: Object
private Cc ::= XXX, YYY
Cc z
z=XXX ? [(z=XXX)][..]
}