Show HN: Hindley-Milner Type Inference Algorithm in OCaml
github.com
github.com
It's a little unfortunate that this version doesn't include let-polymorphism, since that's necessary for parametrically polymorphic functions to be actually polymorphic, and it's not a recent addition — it's in the original Damas-Milner paper. All the other stuff about tuples, lists, sum types, and whatnot can be emulated with functions, but not let-polymorphism, not without a potentially exponential blowup in the program size.
I'm assuming a lot developers like me would be keen to understand how HM works, so I was considering writing a detailed tutorial of the unification algorithm step-by-step. Would you be interested in something along those lines?
[0] - http://www.cs.columbia.edu/~sedwards/classes/2016/4115-sprin...
[1] - http://dev.stephendiehl.com/fun/006_hindley_milner.html
So, yes! I'd love a detailed tutorial of unification step-by-step (or perhaps recursive function by recursive function, if that turns out to be simpler). Maybe it already exists somewhere, perhaps in a textbook, and I just haven't found it. Or maybe I got intimidated and need to calm down and work some exercises instead of going off to read Facebook and HN.
The second thing I would like to say. And I've said this before. Languages like Ocaml and Haskell (never mind Agda, Coq, what have you) already have too much typing 'smarts' built into them. I would like to see -- hint, I'm probably going to have to get my hands dirty! -- an implementation in either a dynamically typed language like Ruby/Python/Perl/PHP/Javascript/… or a non-functional language like C/C++/Obj-C/…
What think ye?
If you take just the lambda calculus where you have:
1) Lambdas that bind variables
2) Usages of bound variables
3) Function applications
The inference rules for the first 2 are simple & straight-forward and require no unification.
For 1 (lambdas) - you create a fresh type variable (e.g: 'a') for the parameter type and infer the lambda body (e.g: 'T'), and the lambda's type is then 'a -> T'. While inferring the lambda body, you also pass the information that the parameter bound by the lambda has-type 'a'.
For 2 (variable usage), you just use the known type information that was passed down by the lambda inference.
For 3 (application), you need unification. Function application is between two subexpressions (e.g: 'f' and 'x'). You infer both of these recursively, and get 2 types (e.g: 'fType' and 'xType'). But you also know that 'fType' must look like: 'xType -> resType' because it's being applied to 'x'. You also know that 'resType' is the result of the entire application. So you have to unify 'fType' with 'xType -> resType'.
This is why inference relates to unification: You have 2 sources of information for 'fType' and 'xType' -- the recursive inference AND the fact they're being applied together.
This unification, by the way, is the only way that type information is learned for parameter types. If all parameter types are always specified (as in, e.g: C++) then formally, there's no type inference at all. So what C++ calls "local type inference" is formally just type checking.
I understand where you're coming from. I'm working on a mini-ML in Python as a learning exercise for myself and possibly as a tutorial for others; will open source it soon.
As such, I've come across a few good resources:
check out Robert Small's Hindley-Milner in Python[0], as well as alehander42's Hermetic language in Python[1].
I also just found out about Hask, an implementation of many Haskell language features in Python. Looking at the source code, it's well-commented and clear, so I suspect I'll learn a lot from it as well [2].
Finally, even though it's not in a dynamic or non-functional language like you request, I highly recommend Andrej Bauer's Programming Language Zoo[3], which contains very simple and easy-to-understand implementations of various type systems in OCaml. Very elucidating.
0. http://smallshire.org.uk/sufficientlysmall/2010/04/11/a-hind...
1. https://github.com/alehander42/hermetic
2. https://github.com/billpmurphy/hask
Edit: Oh, and one more resource that's been extremely helpful has been "Introduction to Functional Programming through Lambda Calculus" by Michaelson. Well worth the cost of the book.
Will check out those links.
When I have something workable, I'll post it to Github as well and then we can see about creating some kind of umbrella structure?
A related idea I have is that what is needed is some kind of grammar interchange format, or typing-relation interchange format -- kind of like JSON (or Amazon's ION) but for this sort of work. In some ways that is what S-expressions are, maybe there is no need to reinvent the wheel but my hunch is that a domain specific format is needed. Apologies if this is a bit vague, it's a hunch, and I'm going by intuition.
I'll ping you when I put my mini-ml in Python on Github.
I think it's maybe a solvable problem, but I haven't seen anyone try to solve it.
Thanks for the book reference!
ML in Lisp and Prolog:
https://github.com/combinatorylogic/mbase/tree/master/src/l/...
Yes! That would be very cool to read.
I've been working on an implementation of Mini-ML in Python using Robert Small's Hindley-Milner in Python [0] (and also eagerly awaiting the rest of Diehl's series on writing a Haskell).
I understand the fundamentals of the algorithm, but even now I don't think I could yet implement it from scratch. I'd love to read a detailed tutorial.
By the way, those Cornell lecture notes you referenced have also been very helpful to me.
0. http://smallshire.org.uk/sufficientlysmall/2010/04/11/a-hind...
[1]. http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.65....
He was studying computer science at the University of Illinois, and at one point he asked me in private message if I was interested in OCaml. I said sure, I've been playing around with it. Then he said he was on a bender and asked if I wanted to do his homework for him... due in a few days...
The assignment was to implement the Hindley-Milner type inference algorithm in OCaml. And that's how I got started with learning about type theory and language implementation. Thanks, guy, whereever you are. Last I heard you were in the military.
Damas-Milner type inference uses an "occurs check" to prevent the type equation solver from unifying a type variable with a compound type expression containing the same variable. The "obviously correct" way to perform this check is eagerly - as soon as possible.
However, performance-wise, delaying the occurs check can speed up the inference process - and this is exactly what OCaml does. The downside is that the resulting algorithm is quite involved, because the solver can now run into type expressions containing cycles, so naively recursively walking type expressions can cause an infinite loop.
Same. When I was just starting to learn about type systems, I found it very useful to have the concepts explained alongside an implementation, to see how they fit together. I don’t think I’ve ever gone so quickly from “I have no idea how this works” to “I could write this from memory” as I did when following that tutorial.
[0]: https://github.com/amnn/typed_geomlab/blob/c79c4bbb3179ef66b...
https://www21.in.tum.de/~nipkow/TRaAT/
From there it is a simple means to form and solve the type equations needed for Hindley Milner.
http://smallshire.org.uk/sufficientlysmall/2010/04/11/a-hind...
Here is the paper we did: http://www1.cs.columbia.edu/~sedwards/classes/2013/w4115-fal... and the full information is available here: http://www1.cs.columbia.edu/~sedwards/classes/2013/w4115-fal... our project was called "pubCrawl".