LogicJS adds logic programming to JavaScript
github.com
github.com
I am among them. Really appreciate the post just for inspiring me to learn more about it. Anyone have a favorite go to resource?
Personally, I find declarative logic programming to be a great tool in domain analysis -- it's a powerful technique to document and prototype domain rules. I've never used it as an actual business rules engine, but I always feel like I should explore Prolog/Datalog or answer-set programming as a possible solution.
https://mitpress.mit.edu/books/reasoned-schemer
It's from the author of miniKanren on which LogicJS is based.
There are implementations for a lot of other languages (other JS implementation too) on
http://www.amzi.com/AdventureInProlog/advtop.php
There was a great PDF that explained how unification and substitution of symbols worked in a concise and readable fashion, but I can't find it right now. Hm, maybe this one:
http://www.cs.ubbcluj.ro/~gabis/plf/Doc/SWI-Prolog/AnIntrodu...
To generate the first example in the readme:
import {solve} from 'logic'
function father(x,y) {
return x =='mcbob' && y == 'bob' ||
x =='bob' && y == 'bill';
}
solve(father) // a noop function used as a marker by babel import {solve} from 'logic'
solve((x,y) => x === "mcbob" && y === "bob" || x === "bob" && y === "bill");Made a Babel transform real quick for this particular example:
It should be more obvious what expressions are supported when it's validated by a Javascript parser. This is in contrast to the 'dynamic' approach which has no problem with the following until runtime:
and(gherkin(or()))> the Norwegian lives next to the blue house
Which I'm not sure how encode without a huge decision tree, or some sort of dynamic logic encoding.
function house(position, country, color, pet, drink, cigar);
You need to define that relation by codifying the following:a) The exclusivity rule. I.e. the fact that no two houses are the same colour, and no two owners have the same pet, and so on. You'll need to define your own "not equal" relation, the absence of which is a major issue with this library.
b) the positioning. Since this library can do arithmetic, I would just make position an integer, and do something like this:
function left_of(pos_a, pos_b) {
eq(logic.sub(pos_b, pos_a), 1)
}
function adjacent(pos_a, pos_b) {
or(left_of(pos_a, pos_b), left_of(pos_b, pos_a))
}
c) finally, just go in and codify all of the hints. This will probably be quite long and awkward because you'll need lots of lvars, and the conjunction relation is binary only, but it should be easy to do nonetheless.For example:
> the Norwegian lives next to the blue house
and(and(
house(pos_a, 'Norwegian', col_a, pet_a, drink_a, cigar_a),
house(pos_b, country_b, 'Blue', pet_b, drink_b, cigar_b)),
adjacent(pos_a, pos_b)
) firstVar.or(secondVar.eq('Bob').and(thirdVar.eq(41)));Chainable calls work well with linear operations. With nested scope, it becomes less clear.
{
and: [
{or: [a, b]},
{a: {gt: b}}
]
}For example: In your code sample, it's a bit unclear what nesting level the .and() call is occurring at. It could be a method call from the chain that starts at secondVar, or the chain that starts at firstFar. Distinguishing which one requires counting all the intervening parentheses. This okay for the given example, but gets out of hand as the conditions get complicated.
Ergonomics aside, I don't think the prefix notation is unintuitive. It's a bit verbose, since JavaScript doesn't have operator overloading, but to me it seems the most sensible and straightforward way of structuring the relationships.
I don't think the prefix notation is much more readable than what I suggested, if it is more readable at all... But without any hands on experience with the library, I am in no position to make that argument.
In the spirit of excessive, needless suggestions: Maybe they can make it like the testing libraries, where you can import different packages for different syntaxes... (not serious)
In that context, I think "any" (for "or") and "all" (for "and") make a lot more sense. E.g.
all(first, second)
It'd read as "ALL of CONDITION1, CONDITION2". More complex combinations are also easier. all(any(first, second), third) var any = logic.or,
all = logic.and; firstVar || (secondVar=='Bob' && thirdVar==41)
seems very clear to me.On the other hand JavaScript has always had logic operators... therefore has always been able to perform logic programming.....
From http://www.ecmascript.org/es4/spec/overview.pdf page 18
“&&=” and ”||=” operators
ES4 introduces assignment operators for the logical
“&&” and “||” operators. These assignment operators
are short-circuiting; if the value of the left-hand-
side determines the value of the result then the right-
handside is not evaluated.classical example in the readme: https://github.com/mcsoto/LogicJS#goals
https://en.wikipedia.org/wiki/Logic_programming
IMO the title is fine.
What about expressing it as a tree based data structure?
+1 to seeing a standardized object structure for logical operations
Take a look here at the drools (the java equivalent to nools) documentation: https://docs.jboss.org/drools/release/5.4.0.CR1/drools-exper...
A rete-based rules engine is more than just a sequence of if-then statements, they allow for any rule to assert new facts that then cause other rules to trigger/retrigger. This also lets you gradually introduce new facts and have the results updated.
[1]: http://cjs.from.so
[0] https://github.com/soney/ConstraintJS/wiki/Constraint-Variab...
The same goal could also be achieved with multiple threads (webworkers for example). But the promises and async/await syntax is more convenient.