hn-classics/_stories/2008/15054752.md

34 KiB
Raw Permalink Blame History

created_at title url author points story_text comment_text num_comments story_id story_title story_url parent_id created_at_i _tags objectID year
2017-08-19T18:13:24.000Z What is Hindley-Milner and why is it cool? (2008) http://www.codecommit.com/blog/scala/what-is-hindley-milner-and-why-is-it-cool jwdunne 185 46 1503166404
story
author_jwdunne
story_15054752
15054752 2008

Source

What is Hindley-Milner? (and why is it cool?) - Code Commit

Skip to content

Print

What is Hindley-Milner? (and why is it cool?)

29
Dec
2008

Anyone who has taken even a cursory glance at the vast menagerie of programming languages should have at least heard the phrase “Hindley-Milner”.  F#, one of the most promising languages ever to emerge from the forbidding depths of Microsoft Research, makes use of this mysterious algorithm, as do Haskell, OCaml and ML before it.  There is even some research being undertaken to find a way to apply the power of HM to optimize dynamic languages like Ruby, JavaScript and Clojure.

However, despite widespread application of the idea, I have yet to see a decent laymans-explanation for what the heck everyone is talking about.  How does the magic actually work?  Can you always trust the algorithm to infer the right types?  Further, why is Hindley-Milner is better than (say) Java?  So, while those of you who actually know what HM is are busy recovering from your recent aneurysm, the rest of us are going to try to figure this out.

Ground Zero

Functionally speaking, Hindley-Milner (or “Damas-Milner”) is an algorithm for inferring value types based on use.  It literally formalizes the intuition that a type can be deduced by the functionality it supports.  Consider the following bit of psuedo-Scala (not a flying toy):

| ----- | | |

def foo(s: String) = s.length
 
// note: no explicit types
def bar(x, y) = foo(x) + y

|

Just looking at the definition of bar, we can easily see that its type must be (String, Int)=>Int.  As humans, this is an easy thing for us to intuit.  We simply look at the body of the function and see the two uses of the x and y parameters.  x is being passed to foo, which expects a String.  Therefore, x must be of type String for this code to compile.  Furthermore, foo will return a value of type Int.  The + method on class Int expects an Int parameter; thus, y must be of type Int.  Finally, we know that + returns a new value of type Int, so there we have the return type of bar.

This process is almost exactly what Hindley-Milner does: it looks through the body of a function and computes a constraint set based on how each value is used.  This is what we were doing when we observed that foo expects a parameter of type String.  Once it has the constraint set, the algorithm completes the type reconstruction by unifying the constraints.  If the expression is well-typed, the constraints will yield an unambiguous type at the end of the line.  If the expression is not well-typed, then one (or more) constraints will be contradictory or merely unsatisfiable given the available types.

Informal Algorithm

The easiest way to see how this process works is to walk it through ourselves.  The first phase is to derive the constraint set.  We start by assigning each value (x and y) a fresh type (meaning “non-existent”).  If we were to annotate bar with these type variables, it would look something like this:

| ----- | | |

def bar(x: X, y: Y) = foo(x) + y

|

The type names are not significant, the important restriction is that they do not collide with any “real” type.  Their purpose is to allow the algorithm to unambiguously reference the yet-unknown type of each value.  Without this, the constraint set cannot be constructed.

Next, we drill down into the body of the function, looking specifically for operations which impose some sort of type constraint.  This is a depth-first traversal of the AST, which means that we look at operations with higher-precedence first.  Technically, it doesnt matter what order we look; I just find it easier to think about the process in this way.  The first operation we come across is the dispatch to the foo method.  We know that foo is of type String=>Int, and this allows us to add the following constraint to our set:

X  mapsto  String

The next operation we see is +, involving the y value.  Scala treats all operators as method dispatch, so this expression actually means “foo(x).+(y).  We already know that foo(x) is an expression of type Int (from the type of foo), so we know that + is defined as a method on class Int with type Int=>Int (Im actually being a bit hand-wavy here with regards to what we do and do not know, but thats an unfortunate consequence of Scalas object-oriented nature).  This allows us to add another constraint to our set, resulting in the following:

X  mapsto  String

Y  mapsto  Int

The final phase of the type reconstruction is to unify all of these constraints to come up with real types to substitute for the X and Y type variables.  Unification is literally the process of looking at each of the constraints and trying to find a single type which satisfies them all.  Imagine I gave you the following facts:

  • Daniel is tall
  • Chris is tall
  • Daniel is red
  • Chris is blue

Now, consider the following constraints:

Person1 is tall

Person1 is red

Hmm, who do you suppose Person1 might be?  This process of combining a constraint set with some given facts can be mathematically formalized in the guise of unification.  In the case of type reconstruction, just substitute “types” for “facts” and youre golden.

In our case, the unification of our set of type constraints is fairly trivial.  We have exactly one constraint per value (x and y), and both of these constraints map to concrete types.  All we have to do is substitute “String” for “X” and “Int” for “Y” and were done.

To really see the power of unification, we need to look at a slightly more complex example.  Consider the following function:

| ----- | | |

def baz(a, b) = a(b) :: b

|

This snippet defines a function, baz, which takes a function and some other parameter, invoking this function passing the second parameter and then “cons-ing” the result onto the second parameter itself.  We can easily derive a constraint set for this function.  As before, we start by coming up with type variables for each value.  Note that in this case, we not only annotate the parameters but also the return type.  I sort of skipped over this part in the earlier example since it only sufficed to make things more verbose.  Technically, this type is always inferred in this way.

| ----- | | |

def baz(a: X, b: Y): Z = a(b) :: b

|

The first constraint we should derive is that a must be a function which takes a value of type Y and returns some fresh type Y (pronounced like “why prime“).  Further, we know that :: is a function on class List[A] which takes a new element A and produces a new List[A].  Thus, we know that Y and Z must both be List[Y'].  Formalized in a constraint set, the result is as follows:

X  mapsto  (Y=>Y )

Y  mapsto  List[Y' ]

Z  mapsto  List[Y' ]

Now the unification is not so trivial.  Critically, the X variable depends upon Y, which means that our unification will require at least one step:

X  mapsto  ( List[Y' ]``=>Y )

Y  mapsto  List[Y' ]

Z  mapsto  List[Y' ]

This is the same constraint set as before, except that we have substituted the known mapping for Y into the mapping for X.  This substitution allows us to eliminate X, Y and Z from our inferred types, resulting in the following typing for the baz function:

| ----- | | |

def baz(a: List[Y']=>Y', b: List[Y']): List[Y'] = a(b) :: b

|

Of course, this still isnt valid.  Even assuming that Y' were valid Scala syntax, the type checker would complain that no such type can be found.  This situation actually arises surprisingly often when working with Hindley-Milner type reconstruction.  Somehow, at the end of all the constraint inference and unification, we have a type variable “left over” for which there are no known constraints.

The solution is to treat this unconstrained variable as a type parameter.  After all, if the parameter has no constraints, then we can just as easily substitute any type, including a generic.  Thus, the final revision of the baz function adds an unconstrained type parameter “A” and substitutes it for all instances of Y in the inferred types:

| ----- | | |

def baz[A](a: List[A]=>A, b: List[A]): List[A] = a(b) :: b

|

Conclusion

…and thats all there is to it!  Hindley-Milner is really no more complicated than all of that.  One can easily imagine how such an algorithm could be used to perform far more complicated reconstructions than the trivial examples that we have shown.

Hopefully this article has given you a little more insight into how Hindley-Milner type reconstruction works under the surface.  This variety of type inference can be of immense benefit, reducing the amount of syntax required for type safety down to the barest minimum.  Our “bar” example actually started with (coincidentally) Ruby syntax and showed that it still had all the information we needed to verify type-safety.  Just a bit of information you might want to keep around for the next time someone suggests that all statically typed languages are overly-verbose.

[Add to DZone][7] [Digg it][8] [Bookmark with Del.icio.us][9]

Comments

  1. Do you know of anyone working on static typing for Clojure? Id be very interested in a statically typed functional language on the JVM. (I like CAL — http://openquark.org very much, but it doesnt have Clojures momentum at the moment).

[Tom Davies][10] Monday, December 29, 2008 at 5:25 am

  1. This is probably approximentally what those IDEs do with typeless scripting langauges and auto-complete etc..

I wish Java would do something like this at least for generics. For a dumbass example (that doesnt even require HM) all Java folk wish that this:

ArrayList list = new ArrayList();

Could be this:

ArrayList list = new ArrayList();

But better would be more HM at deeper levels. For example, what about a “warning” for types that are less specific than they could be, e.g. returning Object when you could return String?

Also it would be interesting to do this at a data level for basic types. For example, you could do similar for “ranges of possible integer values” to declare what results are possible. As a simple example, String.length() would return [0,MAX_INT], not “integer.” Perhaps one could declare such a thing, or perhaps you could leave this at a compiler/IDE level. Wouldnt it be cool if the IDE annotated your int and String types with extra information like possible range of values?

Or even just null/not-null? On this last point I have a patent pending for exactly this kind of data-movement analysis, so this is near-and-dear to my heart.

Anyway, Ive lurked for a while and never posted a comment, so thanks for the interesting posts. Im still not convinced about functional programming for normal commercial applications, but Im continuing to read with an open mind!

(Yes I know the point of many of your posts is to show that Java is inadequate anyway… but still its fun for me to muse about how Java could be improved with some of the methods you suggest without throwing out the whole thing.)

[Jason Cohen][11] Monday, December 29, 2008 at 10:28 am

  1. Its worth mentioning that before HM is an inference algorithm, HM is a type system. Its actually a fairly limited type system, but has the big plus that in every valid program every expression will have exactly one most general type which can always be inferred.

Critically for OO programmers, HM has no notion of subtyping. So F#, which must interact with the rest of .NET, must extend HM to the point where it cant promise complete type inference.

Scala doesnt even give the illusion of using an HM type system and uses a much more local type inference scheme. It would be interesting to see if Scala can borrow F#s “Ill infer most things but not everything” stance or if thats just fundamentally out of reach for Scala.

[James Iry][12] Monday, December 29, 2008 at 10:44 am

  1. Thanks for the run through on how Hindly-Milner works. Ive often wondered how F# did its type inference. Ive got to say though, it must also have to construct some kind of “type tree” so that interfaces and parent types get inferred before children.

[RickM][13] Monday, December 29, 2008 at 11:19 am

  1. @Tom

Scala is a statically typed functional language on the JVM (sort of). Its very much typed, but less “functional” than even Clojure. Regarding your question, its almost impossible to statically type a Lisp without losing what it means to be a Lisp. Specifically, macros pretty much fall apart.

The research I was referring to was some of the work going on in advanced JavaScript VMs (V8, Tracemonkey, etc). While not exactly Hindley-Milner, there is a lot of type inference going on behind the scenes to enable better optimization. There is also a suggestion in the wind to implement fastpath optimizations within Clojure functions when the argument types can be inferred. The inference itself would be a form of Hindley-Milner adapted to an object-oriented type system.

@Jason

I dont think Java could ever implement full type inference without creating potential for unsoundness. I didnt show it so much in these examples, but HM does not mix well with OO languages. Part of the reason for this is nominal-vs-structural typing, but subtyping and uncontrolled overloading are also a huge factor. Consider the following example:

    class Test {
    a;    // field
    b;    // another field

    testMethod(x) {
        a.doSomething(x);
    }

    testMethod(x) {
        b.doSomething(x);
    }
}

I ask you, what types would you infer here? Should “a” and “b” have the same type? Or should they be different types which both happen to implement doSomething(…)?

@James @RickM

I really have to take a closer look at F#. Even Odersky himself has mentioned that their type system is worthy of further attention. Theyre really another language which has had to blend the functional and OO paradigms, but with a slightly different approach than Scala. You mentioned that their inference can cause unsoundness, is this something which is caught by an error or will your code just blindly execute until it runs into trouble?

[Daniel Spiewak][14] Monday, December 29, 2008 at 12:25 pm

  1. Tom,

I doubt very much that anybody is working on a statically typed variant of Clojure. The only statically typed functional language for the JVM with any momentum is Scala. CAL is a distant second. Beyond that there is an effort to bring OCaml (ocaml-java) and Ive read of various lurching efforts to bring Haskell to the JVM but I dont think any of them are ready for prime time.

Ive youre curious about statically typed Lisps (not for the JVM) see Liskell, Qi, and Typed Scheme.

Jason,

Every useful statically typed language does some type inference. When you write in Java “int x = (a + b) * c” theres an inference engine that determines the type of the expression (a + b) * c to make sure its assignable to int (perhaps with a coercion).

HM is a very specific type system and inference algorithm that is fundamentally incompatible with Java. But Java could do a heck of a lot more type inference than it currently does. See Scala and C# as examples.

RickM,

Type checking and type inference are related to logical unification. As such, “before” and “after” arent really thought of in the way that they are in imperative programming. In general, the ordering of logical unifications can be very hard to predict but should not affect the final outcome except when things dont terminate and most type systems guarantee that type checking and inference will terminate.

[James Iry][12] Monday, December 29, 2008 at 12:37 pm

  1. Daniel,

F#s type system is not unsound as far as I know! I dont know what I said to make you think that. But F#s type system does prevent complete type inference. No, your code wont run off a bridge if inference fails. Youll just get a compiler error that the system couldnt infer something.

[James Iry][12] Monday, December 29, 2008 at 12:46 pm

  1. @James

Ah, excellent! It would be a little silly if they had implemented things the way I thought you were implying…

Incidentally, Im not sure I know what you mean when you say that HM is a type system unto itself. As far as I know, HM is strictly just the algorithm for unifying type constraints. Constraint based typing has been around for much longer (I think Curry did it first, but it may have even been Church). Theres no restriction which says that languages with HM must have an ML-like type system. For example, Cat has Hindley-Milner type inference, and it is very different from ML (and family).

[Daniel Spiewak][14] Monday, December 29, 2008 at 1:01 pm

  1. I should more correctly say that HM is a family of closely related type systems, sometimes called HM(X). Informally you can say its those type systems for which the algorithm can always find a principle type. Heres a paper that generalizes the features found in that family: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.42.7215

The fact that the Cat language is different from the ML language doesnt mean it doesnt have an HM type system.

[James Iry][12] Monday, December 29, 2008 at 1:09 pm

  1. Woah, Nelly.

I might really simply be a clueness and pedantic n00b, but I sorta threw up a little when I read “Int” in the first example instead of seeing something like “Num”.

http://www.zvon.org/other/haskell/Outputprelude/Num_c.html

Especially because of the stressing of “must” up there.

Raoul Duke Monday, December 29, 2008 at 3:14 pm

  1. @Raoul

If this were type inference for Haskell, then yes, “Num a” would be the correct answer. However, this is Scala, and the foo method really does return Int. Therefore, because the Int#+(Int) method also returns Int, the final return value must itself be Int.

[Daniel Spiewak][14] Monday, December 29, 2008 at 3:48 pm

  1. @Haskell vs. Scala

Ah, OK, I get it — thanks for the explanation.

(I guess then I think saying “Just looking at the definition of bar, we can easily see that its type must be (String, Int)=>Int. As humans, this is an easy thing for us to intuit.” before explaining it as only Scala would see it is sorta misleading: the way I “easily intuit” things is not how Scala does if it isnt clear that I need to be thinking precisely how Scala would ![:-)][15]

Raoul Duke Monday, December 29, 2008 at 3:55 pm

  1. Good post.

What I do not understand though, taking your example

testMethod(x) {
a.doSomething(x);
}

why OO languages cant infer the type of a, but a functional language can (assuming this is then a function not a method)?

Peace
-stephan

[Stephan Schmidt][16] Monday, December 29, 2008 at 4:01 pm

  1. @Stephen

A functional language would never allow the situation I demonstrated because functional languages do not support OO features. The closest we could get is something like this in OCaml:

    let fun a =
  let testMethod x = a#doSomething x

Off the top of my head, Im not sure what type OCaml would infer here, but it would definately be structural. Probably something like:

    a : < doSomething 'a >
x : 'a    (* from enclosing scope *)

Object-oriented languages have a number of features which make HM more or less impossible. Subtyping is problematic, but not insurmountable. Overloading is real trouble, and nominal object typing is killer. If we allow for structural types and carefully existentialize overloading (probably using intersect types), we can make HM work in an OO language. However, the types inferred would be heinously long. You would end up with every class having dozens of type parameters; not the easiest thing to deal with in an error message! Furthermore, I dont think that the inference on such a system would be decidable in every situation (for the same reason that System F is undecidable).

Someone more familiar with Hindley-Milner probably be able to answer your question more fully.

[Daniel Spiewak][14] Monday, December 29, 2008 at 4:22 pm

  1. @jason cohen: i think a generic type in your comment got eaten by the blogging engine, probably due to angle brackets.

Aaron Davies Monday, December 29, 2008 at 6:27 pm

  1. @Aaron

Yup, brackets got smoked.

@Daniel

Yes of course polymorphism screws things too. Simplest example is that of an interface — by definition theres no code to infer from, therefore no types.

Still more support even at the IDE level in Java (or similar language) would be useful. Sure if you know nothing about the types (or data), fine, but when you do, how nice would it be for the IDE to tell you?

Theres still plenty of code that calls non-polymorphic other code where this inference could help.

Of course this could also help at the runtime-VM or JIT layer, and in fact they already do simple things like that for e.g. removing array-out-of-bounds or null-pointer checks when its clear that those errors are impossible. Still I wonder if it could be made smarter through more inter-functional logic like this.

[Jason Cohen][11] Tuesday, December 30, 2008 at 10:30 am

  1. Thanks a lot for your explaination Daniel. Peace -stephan

[Stephan Schmidt][16] Tuesday, December 30, 2008 at 11:20 am

  1. From the looks of it, it sure sounds like a simple Constraint Satisfaction Problem, solvable even with the most elementary artificial intelligence class. Naturally, I assume this algorithm in particular is highly optimized for use, but at least from what I read here, it sounds like the algorithm is expecting to find ONE type for each variable — highly unsatisfactory for generic functions such as those in Python or other such langauges.

QuacoreZX Wednesday, December 31, 2008 at 3:48 am

  1. @QuacoreZX

You can get surprisingly far with simple let-polymorphism. Saying that HM is inferring one type implies that only a single nominal type will suffice, when really it is far more robust than that. If you extend the algorithm to encompass intersect and union types, you get even more power. Things arent quite as bleak as you think. ![:-)][15]

However, generally speaking you are correct. In any Turing Complete language, there will exist the possibility of code which is perfectly valid at runtime but not accepted by any type system, inference or no inference. Consider the following Clojure tidbit:

    (defn do-stuff [c x]
  (if c
    (:foo x)
    (:bar x)))

(do-stuff true (struct (create-struct :foo) 123))

Even using every trick in the type systems book (structural types, intersect types, etc), there is no way to successfully type this snippet: the struct we are passing does not define a :bar property, meaning that it is possible that evaluation may fail. Of course, at runtime, things work out just fine since the (:bar x) form is in a non-evaluated branch. The type system doesnt know that though, and there is no way for it to reliably find out. Dynamic languages are permanently safe in this regard.

[Daniel Spiewak][14] Wednesday, December 31, 2008 at 4:07 am

  1. @QuacoreZX,

Type systems are constraint logic programming, exactly. And as Daniel says, for every type system there are true things it cant prove (see Godel). But when we say that HM always finds one principle type we dont mean it will necessarily find a primitive type. In Haskell for instance

id x = x — A function that returns the value passed in unchanged

Gets inferred as
id :: forall t. t -> t

Meaning that the id function can take a value of any type and will return a value of that type.

Similarly
singletonList x = [x] — a function that boxes a single value into a list

Haskell infers
singletonList :: forall t. t -> [t]

Meaning if you pass in a value of any type t the function gives back a value of type List of t.

Finally, a more complicated example using Haskell type classes

f x y z = x >>= y >>= z
f :: forall a, a1, b . (Monad m) => m a -> (a -> m a1) -> (a1 -> m b) -> m b

Which says that x must have a type “m a” where m is a special type constructor called a monad and “a” is some arbitrary type, y must have type “a -> m a1″ where a1 is some other arbitrary type, and z has type “a1 -> m b” where b is some arbitrary type. It will then return a value of type “m b”.

Hopefully this shows that principle types can be pretty sophisticated.

@Daniel,

Programs like what you wrote in Clojure above can be typed using dependent typing. However, afaik, theres no way to infer dependent types.

[James Iry][12] Wednesday, December 31, 2008 at 8:42 am

  1. @James

Good point; I hadnt considered dependent types. With that said, I tend to consider dependent types to be flirting a little too closely with a full-blown meta-language. In fact, type systems with dependent types have a tendency toward intractability. That doesnt make them any less valid, but it at least gives a better explanation for why I didnt even consider them in my example. ![:-)][15]

Regarding inference, Im pretty sure that it would be possible to infer the appropriate dependent type in examples as trivial as the one I gave. The trick would be to imbue the CT-If* rules with awareness of Boolean dependent types (required anyway to make the system work). However, in general, constraint-based inference for dependent types would certainly be undecidable. Shame…

[Daniel Spiewak][14] Wednesday, December 31, 2008 at 12:04 pm

  1. @ Daniel Spiewak

This is an old argument but here it is anyway.

The problem with HM and OO is that this typing system does not have a property called principal typing. When a typing system has this property, and it is decidable, one can give the PT for the piece of code and any other typing for this piece can be obtained from this one. This allows you to have smart recompilation in your system. The lack of PT property has nothing to do with functional programming itself, but with the typing system used with the most popular ones.

There is a work going on with ML using intersection types instead of HM type system. Polymorphism by intersection is a promissing field, it list the multiple types instead of quantifying over it, as is done for HM.

Note that HM has principal TYPE property but no principal TYPING. The lack of modulatiry in the Dama/Milner algorithm can be notice that for “let N in M” program, one has to give a type for N and then for M. In a compositional type inference one can give type for N and M in parallel and check whether they can be related.

[anonymous][14] Wednesday, December 23, 2009 at 6:17 am

  1. @Daniel: Strictly speaking, Hindley-Milner is the type system and Damas-Milner is the type inference algorithm, aka “Algorithm W”.

[Jon Harrop][17] Sunday, July 4, 2010 at 12:43 pm

  1. @Jon

I am quite aware of that now. Unfortunately, I wasnt too well versed in the distinction when I originally wrote this article. Ive considered revising it, but the changes would be fairly extensive, and given the fact that its a minor semantic point, I thought it best to leave things as they are.

There is also the fact that most people arent going to make the distinction and so would be confused by the proliferation of terminology. Generally, when someone says “Hindley-Milner”, they actually mean the type inference algorithm. Should they use the term “Damas-Milner” instead? Absolutely. Is it worth quibbling? Probably not.

[Daniel Spiewak][14] Sunday, July 4, 2010 at 1:55 pm

  1. You might be interested by the work of d.Nolen, author of CLojure core.logic who recently ttweeted :
    if a Hindley-Milner type checker for Clojure ends up being more than 800-1000 lines of core.logic Ill be very, very surprised.
    https://twitter.com/#!/swannodette

see https://groups.google.com/group/clojure/browse_thread/thread/7d1a9ad2ff860f60

wolfgangK Friday, June 24, 2011 at 6:09 am

Post a Comment

Comments are automatically formatted. Markup are either stripped or will cause large blocks of text to be eaten, depending on the phase of the moon. Code snippets should be wrapped in <pre>...</pre> tags. Indentation within pre tags will be preserved, and most instances of "<" and ">" will work without a problem.

Please note that first-time commenters are moderated, so don't panic if your comment doesn't appear immediately.

Name *

Email *

Website

Notify me of followup comments via e-mail

[The Joy of Concatenative Languages Part 3: Kindly Types][18]

[Joint Compilation of Scala and Java Sources][19]

  • Daniel Spiewak is a software developer based out of Wisconsin, USA. Over the years, he has worked with Java, Scala, Ruby, C/C++, ML, Clojure and several experimental languages. He currently spends most of his free time researching parser theory and methodologies, particularly areas where the field intersects with functional language design, domain-specific languages and type theory. He can be reached by [email][20].

If you're feeling particularly masochistic, you can follow Daniel on Twitter [@djspiewak][21].

  • Categories

    • [.NET][22]
    • [Cat][23]
    • [Database][24]
    • [DZone][25]
    • [Eclipse][26]
    • [Java][27]
    • [Linux][28]
    • [Ruby][29]
    • [Scala][30]
    • [Scripts][31]

[Is Scala Not “Functional Enough”?][32]
[Pipe Dream: Static Analysis for Ruby][33]
[Apple Blows Another Great Opportunity][34]
[DZone Gets a Facelift][35]
[Scala for Java Refugees Part 4: Pattern Matching and Exceptions][36]
[JRuby Event Proxy][37]

All content copyright © 2018 Daniel Spiewak. Distributed under [Creative Commons][38].
All code (unless otherwise stated) is licensed under the [BSD License.][39]

[7]: http://www.dzone.com/links/add.html?url=http://www.codecommit.com/blog/scala/what-is-hindley-milner-and-why-is-it-cool&title=What is Hindley-Milner? (and why is it cool?) [8]: http://www.digg.com/submit?phase=2&url=http://www.codecommit.com/blog/scala/what-is-hindley-milner-and-why-is-it-cool&title=What is Hindley-Milner? (and why is it cool?) [9]: http://del.icio.us/post?url=http://www.codecommit.com/blog/scala/what-is-hindley-milner-and-why-is-it-cool&title=What is Hindley-Milner? (and why is it cool?) [10]: http://diversions.nfshost.com [11]: http://blog.asmartbear.com [12]: http://james-iry.blogspot.com [13]: http://www.atalasoft.com/cs/blogs/rickm/ [14]: http://www.codecommit.com/blog [15]: http://www.codecommit.com/blog/wp-includes/images/smilies/icon_smile.gif [16]: http://www.codemonkeyism.com [17]: http://www.ffconsultancy.com [18]: http://www.codecommit.com/blog/cat/the-joy-of-concatenative-languages-part-3 [19]: http://www.codecommit.com/blog/scala/joint-compilation-of-scala-and-java-sources [20]: mailto:djspiewak%40gmail.com [21]: http://twitter.com/djspiewak [22]: http://www.codecommit.com/blog/category/net "View all posts filed under .NET" [23]: http://www.codecommit.com/blog/category/cat "View all posts filed under Cat" [24]: http://www.codecommit.com/blog/category/database "View all posts filed under Database" [25]: http://www.codecommit.com/blog/category/dzone "View all posts filed under DZone" [26]: http://www.codecommit.com/blog/category/eclipse "View all posts filed under Eclipse" [27]: http://www.codecommit.com/blog/category/java "View all posts filed under Java" [28]: http://www.codecommit.com/blog/category/linux "View all posts filed under Linux" [29]: http://www.codecommit.com/blog/category/ruby "View all posts filed under Ruby" [30]: http://www.codecommit.com/blog/category/scala "View all posts filed under Scala" [31]: http://www.codecommit.com/blog/category/scripts "View all posts filed under Scripts" [32]: http://www.codecommit.com/blog/scala/is-scala-not-functional-enough "Permanent Link: Is Scala Not “Functional Enough”?" [33]: http://www.codecommit.com/blog/ruby/pipe-dream-static-analysis-for-ruby "Permanent Link: Pipe Dream: Static Analysis for Ruby" [34]: http://www.codecommit.com/blog/java/apple-blows-another-great-opportunity "Permanent Link: Apple Blows Another Great Opportunity" [35]: http://www.codecommit.com/blog/dzone/dzone-gets-a-facelift "Permanent Link: DZone Gets a Facelift" [36]: http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-4 "Permanent Link: Scala for Java Refugees Part 4: Pattern Matching and Exceptions" [37]: http://www.codecommit.com/blog/ruby/jruby-event-proxy "Permanent Link: JRuby Event Proxy" [38]: http://creativecommons.org/licenses/by-sa/3.0/ [39]: http://www.opensource.org/licenses/bsd-license.php