> Everybody who does PL theory more seriously probably knows that, but I didn't realize how close type inference and prolog-like systems look.
Yup! This is more to do with a very specific part of type theory - ie. 'type reconstruction' (more commonly referred to as 'type inference'). One of the most notable examples of this is the so-called 'Hindley-Milner type system' which can be proven to decidable. Here is a good explanation of the constraint-solving algorithm: http://jozefg.bitbucket.org/posts/2015-02-28-type-inference....
Algorithm W, the algorithm used to do type inference in Hindley Milner is effectively just unification (the process by which Prolog infers facts, as mentioned in the post you referenced), applied to types.
I've looked at Prolog for implementing a rule-based workflow system, but I find that it's very hard to embed eg. SWI Prolog into Python, in which the rest of the project is written. Does anyone know of an embeddable Prolog, preferably open source?
ECLiPSe, for example. See [1] for documentation about how to embed it. However, this is targetted at C, Java and Tcl/Tk, not Python. Another approach is to use a client/server approach with two processes (python starts a background prolog, for example).
It's a bit unstable but if you really, really want to embed Swi Prolog it's available as a .dll - which is how pyswip and all other foreign language interfaces work.
>> Is there a deeper link between logical programming and type theory?
There is: types are pattern that data must conform to (and operations also- so, data). To declare a value "x" as having the type "int" is to assert that "x" matches whatever pattern "int" stands for.
The connection to logic programming is that logic programming languages, certainly Prolog, are implemented as relational databases with a search procedure using pattern matching to find relations that conform to the pattern of a query. The pattern matching algorithm in Prolog is called unification: it's like the pattern matching in regular expressions, only Turing complete, so it can represent any program. In fact, Prolog programs are patterns representing themselves [1]. But let's not get too excited.
Prolog's pattern matching makes it indeed very convenient for representing types, even complex types and nested hierarchies of such. For example, Haskell uses something very similar to unification for its type inference system [2].
However- a word of caution. Doing simple type inference ish is not that complicated in Prolog (at least if you're doing it as an exercise- doing it industrial-strength would take a lot of effort). Doing something more complicated, like representing Hindley-Milner like in [3], is more of an undertaking, which is (partly) why Prolog itself doesn't have any types in the conventional sense. Then again predicates with implicitly universally quantified variables are themselves types, er, implicitly, so in a way each Prolog program declares its own type system exactly matching itself.
See [4] for a discussion of logic progamming, types and category theory (I'm not affiliated with the author in any way).
The variations are in the implementation detail, the type checking algorithm that Kiselyov describes in your link is extensionally equivalent to Algorithm W. OCaml's type checker is slightly different to HM, but only to deal with the type semantics of things that HM was not originally meant to deal with (like mutable references for instance).
The link Kiselyov mentions is between Garbage collection (-ish, really it's more lifetime analysis a la Rust) and when it is appropriate to generalise a free type variable and how they both interact with aliasing. Take the following expression:
fun (x) let y = x in y
Here's what we expect:
* It has type `forall 'a . 'a -> 'a` (Keeping the universal quantification explicit).
* The value held in `y` outlives the scope of `y`, because it is aliased in `x`.
But the naïve solution results in:
* The type `forall 'a 'b . 'a -> 'b` (Any function of this type is pure sorcery)
* The value of `y` (and `x`) gets deallocated at the end of `y`'s scope.
The solution is that each type variable should keep track of the scope it was introduced in (like de Bruijn indices), and when generalising, we only universally quantify variables at more deeply nested scopes (which corresponds to deallocating unreachable things).
This has singlehandedly made me interested in learning more about prolog. I mean, if you can do things like that that simply... Holy crap. No wonder so many lisps incude a prolog implementation of some sort.
It's an interesting language no doubt. There are a lot of different implementations though. I generally prefer free implementations and would recommend SWI-Prolog as it's reasonably fast and has the most documentation.
Of the proprietary Prologs I've mostly worked with Sicstus which was the fastest one I've used and had pretty good constraint libraries. It also has a lot of extra features (logical loops etc.) but I mostly used it for legacy reasons and generally prefer a free implementation (for any programming language).
I've heard good things about Eclipse (unfortunate naming, hard to search for) when it comes to constraint programming.
Some random observations: It usually takes my brain a bit to switch to "Prolog mode". You can build some fairly elegant things (constraint based poker hand rankings was the last thing I built) but it's usually not very fast. However I'm not a very proficient Prolog programmer so it could very well be on me for not optimizing enough. If you want some book recommendations I think the best first book is "Prolog Programming for Artificial Intelligence" (best intro section which is the first half of the book so highly recommended even if you don't care about AI at all). The classics are great followups to that (Art of Prolog first, Craft of Prolog second).
Prolog was designed to do this kind of things easily. You can parse languages with easy, and perform all kinds of programming transformations that would take thousands of lines in other languages in just a few rules. The problem with prolog is when you try to do the run-of-the-mill stuff. Not because it is impossible or even hard, but because you need to change the way you think about programming. This is very difficult for programmers that are already used to imperative languages.
Hm after scanning it, I think the reason I never really got it the first time around is that I don't know Prolog :) Might be time to fix that.
What turned me off to Prolog is that there's no way to reason about the computational complexity of the algorithm. Declarative languages often have this problem. That's why you have SQL queries that literally take thousands of times longer than a Python script in production (hours vs seconds). Programming models also need performance models.
Also, if you want to dive deeper, the book, "Prolog and Natural Language Analysis" by Fernando C. N. Pereira and Stuart M. Shieber, is available as a free PDF from the publisher:
And there's a set of course materials in HTML for "Natural Language Processing in Prolog", a course at Union College in Schenectady, NY by Patrick Blackburn and Kristina Striegnitz that seems worthwhile:
>> What turned me off to Prolog is that there's no way to reason about the computational complexity of the algorithm.
That's easy to figure out once you realise your code also has an imperative reading. So for instance it's obvious that reversing a list:
reverse(A, B) :-
reverse(A, [], B, B).
reverse([], A, A, []).
reverse([B|A], C, D, [_|E]) :-
reverse(A, [B|C], D, E).
takes time proportional to the lists' length, since you're walking through the list an element at a time. And it's easy to figure out that consing, as in [B|C], is a constant-time operation.
Or, for instance, say you wanted to apply reverse/4 to elements of a list-of-lists:
You can see that this is going to take the time to go through Ls, times the time to reverse each L. Same goes for the recursive version, except we need to reverse the output also to get it back in the right order:
So it's not different than any other language really, and in fact you can, up to a point, completely ignore the declarative meaning of your program if you just want to think about how fast it's going to go.
On my degree we had two tracks on logic programming, the first one just using a visual language for logic and the second one with Swift Prolog, shared between EE and CS degrees, for some of us like myself it just felt natural but the majority hated those lectures.
Logic programming seems to be like monads, either you get it or not, but it is hard to explain to others.
Prolog and Lisp were the two languages we were forbidden to use in compiler design project, as it was considered "cheating" versus using something harder like C.
> Prolog and Lisp were the two languages we were forbidden to use in compiler design project, as it was considered "cheating" versus using something harder like C.
"Cheating"? Smells like a chance for the revenge of the pattern-matching metacompiler!
For me Mercury is sort of an objective "best language". You can do logic programming, functional programming, and you get the same type-level expressivity as an ML.
The implementation and ecosystem are quite far from something like Python, of course, but it does look like the most beautiful house in the street to me when you just walk past it.
> [Mercury] feels like lego where prolog is duplo.
Except for things like the system described in the featured article, where the unique strength of Prolog is the ability to pass around partially instantiated data structures and use full unification. Last time I checked, Mercury still insisted that terms be ground (or completely free variables), which means that for this kind of problem you don't gain much from its flavor of logic programming :-(
Sweet! I've always wished a better type systems were available in dynamic languages.
These deficiencies are clearly visible in Lisps where you're given great meta-power with macros, but where the type system starts fighting petulantly, whenever one wants to do something fast. In this respect C++ templates are much better.
Shen on the other hand seems quite interesting (and seems to come with its own prolog engine!); anyone here have any views on this Lisp ?
Sadly, Typed Racket always seemed to me like some kind of shoehorning an ad-hoc system onto an AST. For that purpose, a deductive system seems much more natural. There may be problems with decidability, but the fortuitous thing is that this is not a deal-breaker for a primarily dynamic language. You should always be able to rewrite your modules so that they're easier to check, but even if you can't do that outright, the system could still find faster versions if a few type guards or other invariant checks get inserted here and there, or perhaps some form of assistant in your development environment could hint at the need to insert them manually.
Anyone have any thoughts on an idea I've been pondering for a while: Prolog as the type system for dependant typing?
The type systems I've seen for dependantly typed languages have mostly tried to use traditional formal logic syntax, with resulting usability issues and frequently a mismatch with the programming language.
For example, ATS is based on ML syntax, but the typing frequently results in functions of the form:
fun foo =
let _ = ...
x = ...
_ = ...
in
()
So, why not use a programming language for type-level programming?
> Anyone have any thoughts on an idea I've been pondering for a while: Prolog as a type system [...] traditional formal logic syntax, with resulting usability issues
You haven't really explained what your idea is or how Prolog differs from "traditional formal logic syntax". If anything, Prolog's predicate syntax is closer to formal logic than the many ML-based dependently typed programming languages; except that quantifiers are implicit in Prolog.
In my programming career 3 languages have changed how I look at programming. The first one was scheme which is still my go to language for most of my personal projects. The second one was prolog and the third was Forth/factor.
Whenever someone asks where to "widen their view" I generally recommend prolog or factor (15 years ago, I would have said scheme, but lisps seem to have been all the rage so I no longer need to).
I have these three in my list, and one more: APL (or J or K -- especially K)
Scheme/Lisp is about how data and code are equivalent in a practical sense.
I'm not sure how I would describe Prolog in a single sentence - it's about using a powerful concept (Unification) as a base of computation - it makes a lot of logic programming simple (and unfortunately, it makes some simple iteration complicated).
Forth is an exercise in minimalism and blurring (or actually removing) the line between compiling, interpreting, parsing. I wouldn't treat Forth and Factor equivalently - Factor borrows Forth's concatenativeness, but it is very far from Forth's minimalism.
K is an exercise in choosing the minimal set of operators and functions that would produce very terse programs that need much less abstraction, without having to compromise on speed. Unlike Lisp and Forth, the base K has some 50-100 mostly orthogonal operators (depending on how you count) that work together to give you a lot without forcing you to abstract.
Can you elaborate on what is special about Forth? I see Forth articles show up on HN now and again but I've been able to glean why its special to so many. I know its been around forever.
> Can you elaborate on what is special about Forth?
Not the OP here, but I'll give it a partial shot. The power of Forth comes from its extremely simple syntax and the ways you can extend it. A Forth program is just a sequence of "words", which are any sequences of non-whitespace characters separated by whitespace. Users can define new words. These can be "parsing words" which can consume the input that follows them in arbitrary ways.
So, for example, given appropriate definitions, this is a valid Forth program:
ax bx mov \ mov ebx,eax
3 # ax mov \ mov eax,3
(this is taken directly from GForth's inline assembly docs: https://www.complang.tuwien.ac.at/forth/gforth/Docs-html/386... ; the \ introduces a comment to the end of the line and shows the assembly code as it would be written in a traditional assembler)
Or, given appropriate definitions, this is a valid Forth program:
select * from customers
You see where this is going: Programmers have a lot of possibilities to extend the language or implement embedded DSLs (which is pretty much the same thing in Forth). A popular demo of this power are things along the line of http://c2.com/cgi/wiki?ForthObjects, where an object system for Forth (which is not object oriented at all) is implemented in 20 lines of code.
All that said, it's ugly and complex and I always find it difficult to figure out what's going on with all the strangely named concepts and compile-time effects and run-time effects of everything. But still. Interesting stuff.
i think ML has changed the way i look at programming more than anything else. pattern matching over algebraic datatypes is a very powerful way to think about modelling something as code even if your language doesn't support either feature. (to paraphrase ashley yakeley, "every sufficiently well-documented lisp program contains an ML program in its comments")
Yup! This is more to do with a very specific part of type theory - ie. 'type reconstruction' (more commonly referred to as 'type inference'). One of the most notable examples of this is the so-called 'Hindley-Milner type system' which can be proven to decidable. Here is a good explanation of the constraint-solving algorithm: http://jozefg.bitbucket.org/posts/2015-02-28-type-inference....