[HN Gopher] An embeddable Prolog scripting language for Go
       ___________________________________________________________________
        
       An embeddable Prolog scripting language for Go
        
       Author : triska
       Score  : 112 points
       Date   : 2022-01-26 20:28 UTC (1 days ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | gmfawcett wrote:
       | Every language should come with a Prolog interpreter. :) Well
       | done, ichiban team!
        
         | para_parolu wrote:
         | I never used prolog. In my mind it is some old unused language.
         | What is good about it and why should it be embedded into other
         | languages?
        
           | Avshalom wrote:
           | My elevator pitch for Prolog is this:
           | 
           | It's a constraint solving language, linear problem solving
           | language, a database query language, a parsing language, and
           | an expert systems language. All unified (sorry that's a pun)
           | transparently in a single language suitable for general
           | purpose programming instead of half a dozen partially
           | interoperable DSLs.
        
           | Jtsummers wrote:
           | A practical (but not fielded) use that I found with Prolog:
           | programs are multi-use. That is, if you mentally model (and I
           | think this is natural for new Prolog programmers) a program
           | as running in one direction, you will be pleasantly surprised
           | to find that you can use the same program to accomplish
           | multiple effects. Using the built-in _append /3_ as an
           | example:                 % find the list resulting from
           | appending two lists       append([1],[2],C).   %% => C =
           | [1,2]       % find the prefix given a suffix
           | append(A,[2],[1,2]). %% => A = [1]       % find the suffix
           | given a prefix       append([1],B,[1,2]). %% => B = [2]
           | % find all prefixes and suffixes       append(A,B,[1,2]).
           | A = [], B = [1,2];         A = [1], B = [2];         A =
           | [1,2], B = [];         false ;; no more results
           | 
           | The first line is what most programmers new to Prolog will be
           | used to, a function performs a computation in a forward
           | fashion (here: given two knowns, calculate something). But
           | all the others come along "for free" (if you write it
           | correctly, which isn't too hard once you've learned a bit of
           | the language and style).
           | 
           | My practical use for this was with a (prototyped, not
           | fielded) scenario generator. Using the rules (specification
           | for the protocol) I could generate arbitrary scenarios. I
           | could constrain them by filling in some variables, but
           | leaving others blank. And I could take an existing scenario
           | and determine if it was even valid. Using _append_ again:
           | append(A,[3],[1,2]). %% => false
           | 
           | Now, in my situation instead of a hardcoded [1,2] I had a
           | recording of an exchange between nodes, and I could use this
           | to get an answer to _why_ it didn 't conform to the spec. One
           | program that could be used in many different ways.
           | 
           | The other reason to use Prolog, this was a much higher level
           | of abstraction to work with than what we ended up using. It
           | was _much_ shorter, but less familiar to others. So we ended
           | up with a C# + SQL solution where we had to write each
           | variation of the program (generate, test for validity,
           | identify problems).
        
           | ModernMech wrote:
           | For the same reasons that LINQ is used in C# programs.
        
           | zimbatm wrote:
           | Yet another explanation: we have imperative languages,
           | functional languages, and declarative languages. Ordered by
           | level of abstraction. Prolog fits in the latter category,
           | together with SQL and LINQ.
        
           | YeGoblynQueenne wrote:
           | Prolog, like Lisp, excels in problems where the best solution
           | is to create a language in which to describe your problem,
           | and then solve it by using some kind of inference engine.
           | Unlike Lisp, it comes with an inference engine built-in, one
           | that is as good as possible (formally speaking, it's sound
           | and complete). So if that's the kind of use case you have
           | your options are either: use Prolog; embed it; or implement
           | an informally-specified, bug-ridden, slow implementation of
           | half of Prolog.
        
           | convolvatron wrote:
           | rule-based programming is a slightly different take than
           | procedural programming, and can be quite a bit more
           | expressive and efficient to evaluate than procedural for the
           | right kind of problems.
           | 
           | 'make' is pretty good example, but 'package manager' might be
           | more relevant to people. instead of having explicit control
           | flow you have rules that say 'this is the case if these other
           | things are the case', and they get triggered to evaluate
           | implicitly (and recursively).
           | 
           | variables and values in declarative languages are implicitly
           | quantified to be sets of objects, so we say 'for all X where'
           | .. instead of '_the_ X where', which makes it similar to SQL.
           | 
           | actually there you go - pretend this is an embedded database
           | - that's clearly useful, except with a better query language.
        
           | slaymaker1907 wrote:
           | Unification and constraint solving is a big reason why.
           | Prolog is definitely the easiest language to write a type
           | checker in because it not only has unification built in, it
           | is also excellent at navigating trees like type hierarchies.
           | 
           | Something I've personally used it for was navigating the
           | parse tree of some T-SQL I was analyzing (it was 100k+ LoC so
           | manual analysis was out of the question). There is a pretty
           | good C# library for parsing T-SQL, but it is unwieldy to use
           | since it relies on visitors and has tons of different typed
           | nodes with a huge inheritance hierarchy. I ended up exporting
           | this to JSON including type information and initially used jq
           | for analysis. However, even that ended up being hard to
           | manage so I switched to Prolog and it was way easier to
           | write. In terms of this project, I particularly liked that
           | the code was so dense and expressive that I could write the
           | program by first testing things out in the REPL.
        
         | Zababa wrote:
         | Some of them do, for type resolution. Rust has Chalk:
         | https://github.com/rust-lang/chalk, Haskell has something that
         | can be used too: https://aphyr.com/posts/342-typing-the-
         | technical-interview.
        
       | LeonidBugaev wrote:
       | If you are interested in logic programming itself, check
       | http://minikanren.org/. Go community has a bunch of its
       | implementations.
        
       | RcouF1uZ4gsC wrote:
       | What are the trade offs of embedding a Prolog interpreter versus
       | having a library that implements prolog logic without having to
       | have another language.
       | 
       | Are there any high quality prolog as a library implementations in
       | languages such as Rust, C++, Java, Go?
        
         | aarroyoc wrote:
         | IMHO you loose a lot of expresiveness. Real Prolog code is not
         | just simple rules and facts. They can be more complex than
         | that. For example, in my case, I'd like to use DCGs a lot,
         | which are a bit difficult to translate using "normal languages"
         | constructs. Many Prolog systems implement DCGs with Prolog
         | macros because Prolog is homoiconic, which most normal
         | languages aren't (Lisp is homoiconic too).
         | 
         | Also "logic" arithmetic (clpz, clpfd,...) is not the one that
         | comes with normal languages and that breaks a lot of logical
         | properties. Doing the sum of two variables in Java side will
         | not respect logical properties. Standard is/2 in Prolog is also
         | bad, but at least you can replace it with #= and that's it.
        
         | jfmc wrote:
         | Some Prolog systems (like Ciao Prolog https://github.com/ciao-
         | lang/ciao/blob/master/core/lib/forei...) implement
         | bidirectional foreign interfaces. Once you have C bindings it
         | is easy to write bindings from Rust, C++, or any other language
         | (that can interoperate with C). I give here some details about
         | Ciao because this is the system I know better but it should be
         | similar for other popular Prolog implementations.
         | 
         | The tradeoffs depend on the complexity of the Prolog code and
         | your needs for performance and features: pure LP, Prolog
         | (search+unification+cut), garbage collection, dynamic database
         | updates, constraint domains, etc. The Ciao Prolog engine is
         | around 300-400KB. Adding a few libraries, compiler, etc. it
         | goes to 2MB. Naive Prolog systems can be one order of magnitude
         | smaller at the cost of sacrificing ISO compatibility,
         | performance, etc. Note that "performance" can be very
         | misleading. Some Prolog programs may run particularly fast in
         | some Prolog system and very badly in others.
        
         | exDM69 wrote:
         | > What are the trade offs of embedding a Prolog interpreter
         | versus having a library that implements prolog logic without
         | having to have another language.
         | 
         | A library that implements Prolog logic is an interpreter.
         | 
         | The difference here is that Prolog source code is passed in as
         | a string as opposed to having an embedded domain specific
         | language where you construct the abstract syntax tree.
         | 
         | Go is a very simple language and it doesn't have the kind of
         | metaprogramming facilities that are typically used for creating
         | embedded DSLs. Hence it's a reasonable choice to just have a
         | parser and pass in the logic code as a string. There's probably
         | a way to pass in the AST as well but it may not be very
         | ergonomic.
         | 
         | Parsing the source and constructing the AST isn't very
         | expensive and it's only done once per program, so the runtime
         | cost isn't huge.
         | 
         | There are Prolog and Prolog-like embeddable interpreters and
         | logic programming systems for many other prorgamming languages,
         | including Rust, C++ and Java.
         | 
         | The best known ones are Clojure's core.logic (based on the
         | logic interpreter in the SICP book) and Minikanren.
        
           | tgv wrote:
           | Indeed. Text is "unreasonably efficient" in this case.
           | Passing a struct is easy enough in go, but that's all it can
           | do for you. So you'd have to pass arguments like
           | PLClauseList{PLLoadClause{...}, ..., PLFact{Head:
           | PLAtom{"human"}, Arguments: PLArgList{PLAtom{"socrates"}}},
           | ...}. Unwieldy, unreadable, and hard to edit.
        
           | vvern wrote:
           | There are advantages to embedding. You can retain the host
           | language type system and and object model. If you have a
           | great query language and model but have to write a ton of
           | code to marshal back and forth, it might not be adding that
           | much value (classic impedance mismatch).
           | 
           | While go's compile time metaprogamming is virtually non-
           | existent, it's runtime metaprogramming with reflection is
           | more or less complete. There's a runtime cost to using it,
           | but that can be mitigated.
           | 
           | See https://github.com/cockroachdb/cockroach/tree/master/pkg/
           | sql... for a reflection-driven, embedded logic query language
           | in go that achieves pragmatic goals of writing logic queries
           | over data structure graphs at reasonable performance and
           | pretty good expressibility.
        
         | Avshalom wrote:
         | I've never used it but for Java there's
         | https://gitlab.com/pika-lab/tuprolog/2p-in-kotlin (
         | documentation seems to be lagging but here's the 3.3 manual
         | https://gitlab.com/pika-lab/tuprolog/2p/-/wikis/uploads/d67e...
         | )
         | 
         | And specifically for Javascript there's http://tau-prolog.org/
         | 
         | Also pretty much every lisp that's gotten past toy stage has a
         | Prolog implementation
         | 
         | Although both Sicstus and SWI have strong Java and C(++)
         | interop
         | https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.ht...
         | https://www.swi-prolog.org/pldoc/doc_for?object=packages
        
       | vvern wrote:
       | What does the interop with go look like? Is there a story for
       | writing prolog queries over go data structures and iterating the
       | results. If this could be made ergonomic, it'd be very cool.
        
         | zimpenfish wrote:
         | From the given example, basically the same as `database/sql` -
         | you `Exec` or `Query` a Prolog doodah, iterate over the
         | solutions with `Next`, and `Scan` the results into your
         | structs.
        
       | stryan wrote:
       | I've been keeping an eye on this to use for the rules engine in a
       | card game I'm writing[0]. Very excited to get back into using
       | Prolog; I think it's fallen by the wayside a bit in the last
       | decade or two but there's some sectors that still have strong
       | arguments for using it if not as the main language then at least
       | an extension language.
       | 
       | [0] Inspired by a HN comment a while back about Gleemin, the MTG
       | expert engine in Prolog: https://github.com/stassa/Gleemin
        
         | YeGoblynQueenne wrote:
         | Oh, hey, that's cool! Good luck with your card game, I hope
         | we'll get to see a "Show HN" soon (no pressure!) :)
        
           | stryan wrote:
           | Thanks! Your post was really inspiring to me because I'd been
           | grappling with the issue for a while; the rules are all
           | hardcoded in Go right now and I'd been slowly trying to
           | migrate the engine to either Guile or (more likely, since
           | Guile in Go is through cgo) Lua. Reading your comment
           | reminded me that A) that's similar to how Magic Arena handles
           | cards and B) I studied Prolog back in uni and totally could
           | do something similar.
           | 
           | If something interesting comes out of it I'd love to do a
           | Show HN but I'd probably have to work things out with my
           | colleague first: he's the brains behind the game design and
           | project, I'm just the code monkey :).
        
         | mfollert wrote:
         | Have you had a look at Expr? https://github.com/antonmedv/expr
         | 
         | We are using it for ETL processing and are very happy with it.
         | The performance is excellent and it is easily extendable.
        
           | stryan wrote:
           | You know, I didn't think about Expr for this project but I
           | did futz around with it as a plugin-like system for another
           | project of mine. I liked it well enough, but I feel like it's
           | not quite out-of-the-box robust enough for how I'm currently
           | designing the engine.
           | 
           | On the other hand, definitely something to keep in mind for
           | the matchmaking system I'm writing along with the game, so I
           | appreciate you bringing it up.
        
         | spicybright wrote:
         | I would love to hear more about this! Do you blog?
        
           | stryan wrote:
           | Unfortunately I don't have anything written about it now (or
           | a blog at all), but once things get more solid and I have
           | code I'm not embarrassed to show I'd love to do a writeup or
           | two.
        
             | mst wrote:
             | Usually the best time to show code - and, indeed, to
             | publish a blog post - is when you're still at least a bit
             | embarassed by it.
             | 
             | I've learned over time that what I consider a barely
             | acceptable first draft of either is often even so of great
             | interest to other people and reminding myself that my own
             | perfectionism is in fact mostly counterproductive. Ok, for
             | critical code that really really needs to work reliably I
             | let myself go full pure math grad on the problem but for
             | everything else ... shipping is a feature.
             | 
             | A suggestion to try and help with this - I'll often show
             | stuff to a couple of trusted friends first and if the
             | result is mostly approving noises that helps a lot as
             | leverage against my brain to get it to agree I should hit
             | publish in spite of my reservations.
             | 
             | Either way, good luck, and I hope whatever else happens you
             | have fun working on this :D
        
       ___________________________________________________________________
       (page generated 2022-01-27 23:02 UTC)