Annotated implementation of microKanren: an embeddable logic language
github.com
github.com
Logic programming always appealed to me, but I always felt like I never understood what to actually do with it other than write toy programs about parent-child relationships (I tried learning Prolog a few times, but stopped for this reason).
I am kind of half-considering it for mud scripting? Each line from the mud represents a fact or relation so this could be used to query for specific information as needed.
For this case though, it doesn't really seem better than writing a regex-based DSL though, something I'm far more familiar with and that is better supported.
I guess I think of logic languages as SQL but I actually want to write my whole program in it.
Generally speaking, you'll see the same pattern for any problem involving searching a space of potential solutions. In a word, the strength of LP is flexibility when facing the unkown (future feature requirement).
EDIT: you mention parsing. If you're planning anything context-sensitive, you have to check out eDCGs. You probably won't use them, but they'll give you an idea of what's possible.
This feels like a much better path than trying to get it into an AST with like yacc or whatever. But since it isn't actually natural language NLP didn't seem right either. I've got a lot of reading to do but this is very interesting.
Your mud language is probably a "Controlled Natural Language", btw:
https://en.wikipedia.org/wiki/Controlled_natural_language
Game languages often are.
https://github.com/kamahen/edcg , https://occasionallycogent.com/prolog_edcgs/index.html
Curious - I fuzzily recall usually ending up manually threading state (eg, doing a Perl-Compatible Regular Expression engine (apropos "why LP?" - it was just a page of code, with good performance)), but I don't recall what drove that decision...
You have to solve some kind of 3-SAT-reducible problem that would be difficult to implement otherwise.
You have to do some heavy pattern-matching.
Closely related: https://en.wikipedia.org/wiki/Answer_set_programming
I've seen things like this in the context of "algebraic effects", where instead of performing I/O (and instead of using monadic I/O) you build up what amounts to an execution graph of effectful operations, which is then "interpreted" at runtime. Is that what you're describing?
Check this out for prolog:
https://www.swi-prolog.org/pldoc/man?section=pio
https://github.com/mthom/scryer-prolog/blob/084fc845902f7b43...
"New" stuff in the MiniKanren world:
https://staff.fnwi.uva.nl/c.u.grelck/nl-fp-talks/kourzanov.p...
https://ifl2014.github.io/submissions/ifl2014_submission_18....
I mean, my intuition is that a file is a stream of bytes and wading through that stream is not going to be "pure" no matter what, just because a byte is not a concept in First Order Logic. But, OK, "pure" Prolog is not the same as FOL anyway, and so I may be convinced otherwise when I have the chance to peruse your links. So thanks again and sorry for any negative vibes emanating from my comment.
I guess my confusion about pure I/O stems from the documentation of phrase_from_file/2 in SWI-Prolog. The documentation says the predicate is for pure I/O and how it was implemented, and by whom, but it doesn't say what pure I/O is, and why it is needed. I guess the information is somewhere in one (or more) of the canonical Prolog textbooks, which I've only read several years ago (though multiple times over, each).
I have to confess also I'm not much of a purist. My Prolog code looks like bog standard imperative code where I feel it doesn't really matter (e.g. I don't have any prejudice about setof/3 or maplist/2 over findall/3), and I guess file I/O is one of those cases.
Anyway, thanks again, for your links and explanation. I owe you a quantum of knowledge :)
I don't want to have to define security groups, subnets, route tables, etc. Instead, I want to declaratively say "Service A must communicate with Service B", and based on the properties of those services, all the correct cloud resources will be defined. Subject, of course, to constraints around security, etc.
We are tantalizingly close to this with current IaC tools (CDK, Pulumi, etc) but at the end of the day you still need to define each granular cloud resource (even if the code to do so can be tucked away in a library abstraction.)
I'm busy with my current gig now, but if you forced me to launch a startup at gunpoint, this would definitely be it.
We'll see if performance is an issue, but the space I'm most interested in is turn based strategy / board games, where it doesn't seem to be a big deal if things are as fast as possible.
It feels like a pretty natural fit, e.g.:
can_move_to(unit_id, tile) :-
Unit(unit_id), Tile(tile),
reachable_to(unit_id, tile, move_cost),
unit_move_points_remaining(unit_id, mv),
move_cost <= mv.
You can define a lot of rules from basic unit movement to "what happens when you activate Super Special Rule-Bending Ability" this way, and it's pretty easy to change the definitions if you want to experiment with different game rules / logic. In practice, you want various syntactic sugar.Haven't got a chance to take this very far yet, just casual experimentation, but it seems like there's potential.
The jump from a well-written, unambiguous and precise set of board game rules and its translation in Prolog is a tiny step, that could even be automated with some elbow grease.
Now, you mention "rendering" and that, on the other hand, I'm not sure is a very good application for Prolog. Nothing to do with speed, but arithmetic in Prolog is a bit meh, and if you want to do stuff like matrix arithmetic, you have to roll your own. Most Prologs don't even have arrays, as such (they kind of do but it's a bit of a hack).
you also might enjoy looking at incremental datalog (differentiating rules and operating on events). this generally does a really good job of touching just what need to update the global state.
this is a great application
Most of the benefits I found come down to two things:
a) Prolog, like the various kanrens, is a relational language so a program is effectively a database. There's no need to do anything special to glue together a data layer and a logic layer, because you have both written in Prolog. No object-relational impedance mismatch whatsoever.
b) Prolog's declarative style makes translating rules and directives into code a breeze. The three projects below are all games and benefit heavily from this feature. The same would go for business logic of any shape or form.
1. Warhammer 40K simulation:
https://github.com/stassa/wh40ksim
Runs simulations of combat between WH40k units.
2. Gleemin, a Magic: the Gathering expert system:
https://github.com/stassa/Gleemin
Doesn't work anymore! Because backwards compatibility. Includes a) a parser for the rules text on M:tG cards written in Prolog's Definite Clause Grammars notation, b) a rules engine and c) a (primitive) AI player. The parser translates rules text from cards into rules engine calls. The cards themselves are Prolog predicates. Your data and your program are one and now you can also do stuff with them.
3. Nests & Insects, a roguelike TTRPG:
https://github.com/stassa/nests-and-insects
WIP! Here I use Prolog to keep the data about my tabletop rpg organised, and also to automatically fill-in the character sheets typeset in the rulebook. The Prolog code runs a character creation process and generates completed character sheets. I plan to do the same for enemies' stat blocks, various procedural generation tables, etc. I also use Prolog to typeset the ASCII-styled rulebook, but that's actually not a good application of Prolog (too imperative).
You asked about "logic programming" in general and not miniKanren in particular. I haven't actually used miniKanren, so I commented about the logic programming language I've used the most, Prolog. I hope that's not a thread hijack!
All three of the projects above are basically games. I have more "serious" stuff on my github but I am currently experiencing a certain shortfall of gravitas.
It didn't work out in my case. I found it really fiddly to work with, and the implementation I was loosely working from was very idiomatic Lisp.
What specifically would make it easier? I'm not that familiar with miniKanren. Is it stuff covered by the convenience macros from the paper?
https://github.com/webyrd/mediKanren
This is a FOL theorem prover that uses medical research articles as terms. They use it to do genetics and drug repurposing metaresearch. It's like the wet dream of all the biomed machine learning fanboys out there, except that:
1. it's not machine learning
and
2. it really works
https://github.com/stassa/louise
In which case it _is_ machine learning and it still really works :D
You can do this with Prolog because you can keep adding clauses to a program, or removing them from it, for as long as you want, and the program won't (necessarily) break, it will just become more specialised, or generalised (respectively). John McCarthy called that "elaboration tolerance".
To, er, elaborate a bit on this, a logic program is a set of clauses interpreted as a conjunction (even if they look like alternatives!) so that the program as a whole has a set of "facts" it entails, but without any other assumptions about the relation between the clauses with each other. If you add clauses, your program entails more facts. If you remove clauses, it entails fewer facts. With negation-as-failure, the relation is non-monotonic, but it's still the same idea.
This is what makes it possible to learn Prolog programs from examples, in the first place. With Louise in particular, because it builds up programs clause-by-clause, you can even train it with a single example at a time. It used to be it was limited in its one-shot learning capabilities but it's much better now.
In fact, if you're doing any preliminary work with Louise, you really want to start by training it on a handful of examples at a time, so you can best figure out the right configuration to use to learn the kind of program you want it to learn. Otherwise, if you throw the entire database at it, even if it fits into memory, you'll get so much in the output that it will just be overwhelming.
ILP and Louise work a bit differently than ordinary machine learning. You want to go slowly, processing small data in small batches, otherwise you get a firehose of clauses in the face and drown. It's the tradeoff you get for being able to inspect the learned "model": there's still a lot of it and you have to develop strategies to deal with its complexity.
Hope that makes sense? Please do get in touch if you need help with Louise. Or anything ILP!
I did hew to the standard convention of representing a type environment with Γ (Gamma); hopefully that's not too confusing for people looking at the type checker. :)