Wednesday, April 28, 2010

Infinite Types for Fun and Profit

I stumbled upon this fun post at Stack Overflow. Let me paraphrase the goal: write a function contraption that, when given another function (call this the operator) and an argument x, invokes operator(x), and returns a function that, when given yet another argument y, invokes operator(y), and returns a function that, when given yet another argument z, invokes operator(z), and returns a function that ...

That's kind of a gross definition. What we're looking to do, in ocaml, is the following:

let printer = contraption (print_endline)
printer "Hello" "World" "How" "Is" "It" "Going?"

Clearly this should print 6 lines, with the relevant text. Cool, right? You (kind of) get a function that takes an arbitrary number of parameters of the same type (sort of, because you have to do the same thing to each one). So, how do we write contraption? As follows:

let rec contraption op x =
    let _ = op x in
    fun y -> contraption op y

So, what type does contraption have? Well, this is where things get loose. Run this through ocaml and you get:

# let rec contraption op x =
      let _ = op x in
      fun y -> contraption op y;;
Characters 60-76:
      fun y -> contraption op y;;
               ^^^^^^^^^^^^^^^^
Error: This expression has type 'a -> 'b but is here used with type 'b

What? 'a -> 'b isn't the same as 'b? I guess not, but why not let 'a -> 'b equal 'b? Sure, it's an infinite type, but it seems like everything would work out. Indeed, if we run ocaml -rectypes instead we get the following type:

val contraption : ('a -> 'b) -> 'a -> ('a -> 'c as 'c)

What does this type mean? It says that, "if we give it a function from foo to bar, and a bar, then we get back something of an infinite type". And this infinite type is "if you give me a foo, I'll give you back an infinite type", indeed the same infinite type. And that's just what we want!

So, if everything works fine with -rectypes, why isn't it always turned on? Apparently leaving it on all of the time can get you even worse error messages than ocaml already gives you. Hard to believe, I know, but nevertheless there is indeed a deeper circle of Hell.

To bring it back to Mix, we might wonder if this craziness will work in Mix, and how it will look if it does. Here you go:

contraption(op, x)
{
  op(x);
  return function(y)
  {
    return contraption(op, y);
  };
};

Now, how do we use it?

Int main(String args)
{
  var printer = function(s){ return contraption(IO.print, s); };
  printer("Hello")("World")("This")("Is")("Bizarre");

  return 0;
};

Note that since we don't have currying in Mix we were obligated to build an "extra" anonymous function when creating printer; if we had the placeholder syntax "_" of Scala we could have written it as follows:

Int main(String args)
{
  var printer = contraption(IO.print, _);
  printer("This")("is")("Cleaner");

  return 0;
};

Or, if you prefer we can tweak the definition of contraption slightly instead:

contraption(op)
{
  return function(x)
  {
    op(x);
    return contraption(op);
  };
};

Int main(String args)
{
  var printer = contraption(IO.print);
  printer("This")("Is")("Cleaner")("Still!");

  return 0;
};

And so Mix is victorious (or at least works, after a fashion) once again! And incidentally, we see how nice currying can be.
PS - I definitely need to shorten the function keyword; perhaps just fn?

No comments:

Post a Comment