A quick read of the comments here gives me the impression a lot of people are unfamiliar with what logic programming is, how it works, and what it might be used for.
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.
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:
Could you describe a classic use case? I'm also interested in learning some logic programming, but it's hard for me to conceptualize a use case that isn't already covered by the languages I regularly use.
Back in the day the two common use cases were expert systems and natural language processing (which are somewhat related). In this day and age I would most likely use Prolog for rule-based simulations or high-level game AI.
Russell and Norvig's AI textbook has a good introduction, although maybe it'll go into more detail than you would like. Stanford has some classes on coursera, too.
One of the issues with standard logic programming is that sooner or later you want to be controlling the search strategies. Mozart/Oz has the notion of "computational spaces" as a primitive on which to build custom strategies. Quite a while back, I wrote tan implementation of this in JS called FD.js[1] for finite domain constraint programming. Might be useful for those interested in such in JS.
To solve the readability issue, why not use Babel to transpile JS expressions into the equivalent syntax?
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
I feel like that just obfuscates it. You can no longer dynamically use it, and it's completely unobvious what javascript expressions are supported or not.
Babel has to turn it into valid JS for it to work so you'd still have the 'and' and 'or' functions. In the bottom right corner of Hzoo's example you can see what I mean (http://astexplorer.net/#/ZT6HYai08w)
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:
How easy would it be to encode and solve "Einstein's Riddle" https://udel.edu/~os/riddle.html in LogicJS? I looked into starting it but there's things like this:
> 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.
The core relation involved is one that captures the characteristics for an individual house:
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.
I think the parent is referring to the particular stylistic decisions that differ from what's most common in the JS community. The author uses tabs to indent (instead of spaces), snake case (instead of camel), no semicolons, etc.
Wow, didn't even notice that. Now that I look at it, it does seem more airy and spacious. The style is also a bit eclectic and unsound. Dropping the curly braces on that else on L698 irks me to no end.
You've hit on one of my pet peives with JSON as a transport... likewise with PascalCase from java and .net projects. I tried using snake_case once to placate some PHP, and other guys, but in the end it would have been better to just use camelCase from the beginning as it wound up pretty alien in every environment.
Nifty, but having "and", "or" and the rest before both of the arguments instead of in the middle is incredibly confusing to read. Very unintuitive. Why not restructure it to be chainable with something like:
Which style is more readable depends on whether in practice you end up with more sequence-like structures, or more tree-like structures. Chaining makes sense for sequences. For tree-like structures, they get confusing because the nesting levels aren't apparent.
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.
Good point, and I agree that the code in my example wasn't perfectly clear, though I think some formatting could help it go a long way.
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)
“&&=” 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.
This library can do Prolog-style solving of logical expressions. As in "here is a set of expressions, find me values for the variables that fulfill them." Completely different thing.
Prefix notation already makes it a tree. Why would an explicit data structure tree instead of the AST make it better? Outside of being able to manipulate logic program structure at runtime.
Not only is being able to manipulate logic program structure at runtime super cool, but it would be great to be able to pass around logic as data more easily.
I mentioned in another comment, but there are many json structures meant for representing logical operations currently in use by ORMs (see mongoose, sequelize, loopback, etc).
+1 to seeing a standardized object structure for logical operations
Business rules systems and logic programming are definitely similar. Most business rules are based on "Forward Chaining" (https://en.wikipedia.org/wiki/Forward_chaining), basically using what we know to satisfy the left hand side condition and then using the right hand side to assert new facts. Logic programming is usually based on "Backward Chaining" (https://en.wikipedia.org/wiki/Backward_chaining), where the system attempts to work backwards from a goal to what facts are needed to satisfy the goal.
What would be the use case for using something like nools? Compared to just writing it in JS or Java or whatever the rest of your business logic is written in.
Any business rules that need to be changed easily later, that is almost anything and everything. Examples include discount rates, loyalty points, insurance premiums and so on. Especially when you have domain experts creating and updating the rules.
The usual idea is (1) to encourage stronger separation between the actual business logic and any boilerplate that surrounds it and (2) to write business logic in a way more accessible to non-developers.
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.
Maybe the example of the front page is misleading, but fwir ConstraintJS is a JS library adding constraints to JS, the DOM/CSS are use cases but the library is not limited to those use cases
From [0] and [1], it seems that ConstraintJS does not try to solve constraints, it propagates changes among variables when they change. In particular, variables are not tied to domains.
Why is this at 44 points? I respect this is trying to hit a nitch but the nitch looks more like a step backwards than forwards. This does not seem more reasonable than what we have available. Maybe when it handles promises will it have a more obvious use case.
What we have available cannot do what this does - logical resolution. The point is not the syntax for boolean operatons, it's that it can find solutions by resolving logical expressions against each other in a way that lets you say "add(2,x)=6, what is x?" and get an answer from the solver. This is basically doing Prolog using JavaScript, a thing vanilla JS is absolutely not equipped for.
To me originally I thought it was just a library that replaced || with or() with no added benefit while promise support would have at least reduced the amount of overhead to create a readable logic wuth asynchronous situations (which is common in js). I now understand thos is a step towards making test code and runnable code one in the same.
There's absolutely nothing about this that would benefit from having promises - none of the operations performed are blocking. It's constraint solving, not I/O.
Promises can be helpful in a pure CPU bound workload as well: they help to avoid blocking the main thread, by yielding more often. This is typically done by breaking a long running computation into a series of sub-computations that are scheduled on a timer, and the top level API returns a Promise that completes when all computations are over.
The same goal could also be achieved with multiple threads (webworkers for example). But the promises and async/await syntax is more convenient.
I am among them. Really appreciate the post just for inspiring me to learn more about it. Anyone have a favorite go to resource?