Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
LogicJS adds logic programming to JavaScript (github.com/mcsoto)
209 points by adamnemecek on Aug 15, 2016 | hide | past | favorite | 72 comments


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?


You may wish to check out the (free) books Logic, Programming and Prolog (http://www.ida.liu.se/~ulfni53/lpp/) or Learn Prolog Now! (http://learnprolognow.org).

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.


The Reasoned Schemer by Daniel P. Friedman, William E. Byrd and Oleg Kiselyov

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://minikanren.org/


Well, the classic logic programming language is Prolog, so learning it would be a good start. Here is something fairly lightweight on the subject:

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...


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.

[1] https://github.com/srikumarks/FD.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


Yeah, the transformation itself shouldn't be bad.

Made a Babel transform real quick for this particular example:

http://astexplorer.net/#/ZT6HYai08w


If you're going ES6, why not just:

  import {solve} from 'logic'
  
  solve((x,y) => x === "mcbob" && y === "bob" || x === "bob" && y === "bill");


Just because you can use the shorthand syntax, doesn't mean you always should.


I wouldn't say it's that bad. A curly brace would make it a bit clearer though.


I prefer my code to be more than "it's not that bad" when it comes to readability.


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:

    and(gherkin(or()))


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.

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)
    )


For reference, Clojure's core.logic: https://gist.github.com/rm-hull/6952960 Or, from Norvig, presumably as an update to the solution he made with Lisp and Prolog, here's a Python version that doesn't use any fancy logic engine: https://gist.github.com/joyrexus/5301901


Jesus, Norvig's version is so dauntingly short...


I have this exact example in FD.js (https://github.com/srikumarks/FD.js), plus Sudoku solvers and others.


A bit off topic, I like his JS style [1], almost doesn't look JS..

[1] https://github.com/mcsoto/LogicJS/blob/master/logic.js


Actually, this is the canonical JS with prototypes. The more common style nowadays is to (ab)use the module pattern.


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.


This style could be called 'No fear JS' :)



Ha, good get.


Looks like perfectly ordinary, old-style, prototype-based ES5 to me? Formatting is a bit idiosynchratic, of course (e.g. lines starting with comma).


ewwww_snake_case


iAlwaysStartGreenFieldJavascriptWithCamelCase_but_then_comes_the_json_and_all_is_lost.


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:

  firstVar.or(secondVar.eq('Bob').and(thirdVar.eq(41)));


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)


Prefix operator is the Lisp notation and it has work pretty well. The syntax rule is very simple. Once got used to it, it's very easy to deal with it.

Chainable calls work well with linear operations. With nested scope, it becomes less clear.


Agree in essence - the code is basically saying something like "AND CONDITION1, CONDITION2" which is awful to me.

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)


That would be a lot easier to do. Based on the example all you need to do is:

    var any = logic.or,
        all = logic.and;


Not at all, I think that many people have come to expect this syntax from many ORMs. You will often see syntax like:

  {
    and: [
      {or: [a, b]},
      {a: {gt: b}}
    ]
  }


Prefix operators are pretty common, actually. They have their strengths


Why is such complication introduced and what are the benefits of it?

      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.


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.

classical example in the readme: https://github.com/mcsoto/LogicJS#goals


Thanks for the reply; "Prolog-style solving of logical expressions in JS" sounds like a better title.


"Prolog-style solving of logical expressions" is actually called "logic programming". It's the name of the paradigm.

https://en.wikipedia.org/wiki/Logic_programming

IMO the title is fine.


Next time please at least click the link before chiming in with a comment. The title was not the problem here.


Very cool. The syntax is a bit verbose though.

What about expressing it as a tree based data structure?


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


It wouldn't be too hard to write a function to turn a tree-based structure of your design into a tree of function calls using the existing API.


Looks similar to a business rule engine (like nools http://c2fo.io/nools).


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.

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...


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.


How does this compare with constraintJS[1]

[1]: http://cjs.from.so


It's an entirely different concept; this is logic based programming and constraint solving, nothing to do with DOM elements or user input.


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.

[0] https://github.com/soney/ConstraintJS/wiki/Constraint-Variab...

[1] https://github.com/soney/constraintjs/wiki/Introduction


Logical constraints and CSS/DOM constraints are entirely separate things =)


Interesting. Took a bit to even understand what logic programming was.


is it necessary with ES6 Array methods?


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.


Thank you. You have introduced me to something I can get excited about


Why does this need promises? Logic operations in JS doesn't do any I/O operations. Making this promises compliant is just overkill.


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.


*niche


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 was unfamiliar with the use case that this offers and thought it was a mostly useless replacement or what we already have available.


niche not nitch




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: