Etherplex

Rick Dillon's home on the net…

Static Typing and Functional Languages

with 13 comments

With a title like that, it’s not really clear where I intend to go with this post, but bear with me for a moment.

A couple of years ago, I got into functional programming, initially with Common Lisp (only a tiny bit), and then Scheme and Scala, a pinch of OCaml, and finally Clojure.  One thing I noticed about all the Lisps was that they weren’t statically typed in the way I was used to in Java.  Then I noticed that all the functional languages that were statically typed (OCaml, Scala) were also more modern than the Lisps.

Anyway, I continued working in functional languages for months, growing more accustomed to the power they offered, and changing how I thought about solving problems as I went.  Design patterns seemed much less useful, and languages that didn’t support higher order functions, TCO, and the basic map, reduce, and lambda functionality started to feel increasingly awkward.

So, as I worked in Java for much of my professional work, I was naturally drawn to investigate Functional Java, a set of libraries that offered at least a partial veneer of functional programming on top of the Java language.  Part of that library was a testing framework inspired by QuickCheck called Reductio Test.  Reductio was cool, and the examples looked simple and powerful.  Then I tried to write my own generator for Reductio.

The code was awful.  It was an explosion of objects attempting to replicate higher order functions, but constrained by Java’s type system.  As such, the types of the higher order functions were generified on types of lower-arity higher order functions, and so on, until finally all the parameters had been consumed.  I thought I must be doing it wrong, so I browsed the Functional Java source tree over on the Google code site where it is hosted.  In Function.java, I saw the same problem emerge:

 /**
   * Curry a function of arity-5.
   *
   * @param f The function to curry.
   * @return A curried form of the given function.
   */
  public static <A, B, C, D, E, F$> F<A, F<B, F<C, F<D, F<E, F$>>>>> curry(final F5<A, B, C, D, E, F$> f) {
    return new F<A, F<B, F<C, F<D, F<E, F$>>>>>() {
      public F<B, F<C, F<D, F<E, F$>>>> f(final A a) {
        return new F<B, F<C, F<D, F<E, F$>>>>() {
          public F<C, F<D, F<E, F$>>> f(final B b) {
            return new F<C, F<D, F<E, F$>>>() {
              public F<D, F<E, F$>> f(final C c) {
                return new F<D, F<E, F$>>() {
                  public F<E, F$> f(final D d) {
                    return new F<E, F$>() {
                      public F$ f(final E e) {
                        return f.f(a, b, c, d, e);
                      }
                    };
                  }
                };
              }
            };
          }
        };
      }
    };
  }

I was stunned.  What had gone wrong?  After a few minutes or hours, I realized the reason Lisp had used dynamic typing: having to explicity specify all the types for each of the higher order functions destroys the readability of the code!  So, languages like Javascript, Lisps, Python and Ruby get too avoid all that.  So how do OCaml and Scala work?

Well, a lot of work had to be done to support static typing with functional programming, and it required the development of a type inferencer to make code readable.  The end result is a sophisticated compiler that has the ability to determine the types in the code without the programmer specifying them exhaustively.  This functionality is advanced, however, and it took a long time to develop.  That is why OCaml and Scala are much newer that Lisp, for example (though it is true that OCaml and Python are about the same age).

So, suddenly it all came together for me.  I finally had a glimpse as to how the type systems interacted with the programming paradigms, and why languages made the decisions they did.  Type inferencers can be tricky to write well, so there is a trade space language designers have to navigate to get the result they’re looking for.  Dynamic typing offers simplicity, but also comes at the cost of rigor and the ability to catch entire classes of bugs at compile time, rather than runtime.  Sometimes that cost is worth it, sometimes not.  That is why, even today, there is room for dynamic languages like Python and Ruby alongside static languages like Scala and OCaml.

Written by Rick

August 29th, 2009 at 1:36 am

Posted in Programming

13 Responses to 'Static Typing and Functional Languages'

Subscribe to comments with RSS or TrackBack to 'Static Typing and Functional Languages'.

  1. While type inference is only starting to creep into mainstream languages now, ML type inference and the algorithm (Hinley-Milner) that is used have been around for decades.

    Anonymous

    2 Sep 09 at 14:41

  2. I suspect a more important motivation was how to deal with change in systems (like the earlier Lisp and Smalltalk integrated OS/devolopment envirnoment systems) where things can change at runtime. The assumptions of static type systems get broken on non static systems.

    Steve Dekorte

    2 Sep 09 at 14:44

  3. Haskell’s type annotations aren’t overly verbose at all.

    John Bender

    2 Sep 09 at 14:45

  4. It is interesting to note that type inference appeared in the 1970s, well before Java existed. Functional Java ‘looks’ so ugly, because Java is not naturally suited to expressing these abstractions.

    I suspect the statement that functional programming implies that static typing is difficult, is an error. Type system research has been motivated by a number of factors over the years, including compiler optimisations for program performance and theorem proving.

    Brad Clow

    2 Sep 09 at 15:10

  5. Forgive me if I’m wrong, but I was under the impression that it ran on the JVM, and in fact would compile down to Java files…

    Paul

    2 Sep 09 at 15:44

  6. Whoops, by “it” I meant “Scala”

    Paul

    2 Sep 09 at 15:44

  7. Hindley-Milner type inference is not advanced, and it did not take a long time to develop!

    The original algorithm was first published in 1969, and the first ML implementation was done by Luca Cardelli in 1973 (the same year as K&R C!). I’m pretty sure he was an undergraduate at the time!

    Fred Blasdel

    2 Sep 09 at 16:22

  8. What do you think of Haskell? How would that change your conclusion?

    Robin Bate Boerop

    2 Sep 09 at 17:26

  9. http://en.wikipedia.org/wiki/Type_inference#Hindley.E2.80.93Milner_type_inference_algorithm

    Type inference is a very old idea. The Damas-Hindley-Milner algorithm was a big step in that it supported a stylized form of System F, which allows it to account for precisely the example you give.

    If you’re interested in learning more, check out Benjamin Pierce’s book, Types and Programming Languages.

    Michael Greenberg

    2 Sep 09 at 18:53

  10. Link messed up for some reason..

    The magic is called Hindley-Milner type inference. It freakin’ saves the day.

    Jason Kikel

    3 Sep 09 at 04:43

  11. Have you tried C# or F#? C# has the type inference and makes it a lot simpler to do the kind of tricks you were talking about. I think it is a good balance between both functional world and traditional OOP.
    I think that is needed for transition from structural and OOP to functional. Very much the same way that C++ was a transition from structural to OOP.

    Mehrdad

    14 Sep 09 at 18:54

  12. The problems in Functional Java are not because of static typing. They are because of Java. I recommend taking a look at a practical static typing implementation some time.

    Tony Morris

    20 Mar 10 at 04:15

  13. Mehrdad “C# has the type inference” <- It is not true. You must mean F#

    Olle Kullberg

    11 Jun 10 at 02:26

Leave a Reply