Saturday, July 19, 2008

Awesome new Groovy Mixin Syntax (and F#'s alternative)

Groovy's MetaClass recently acquired some new features...

Check out how easy it is to add multiple new methods to an existing class:

class ListUtils {
static foo(List list) { "foo called" }
static bar(List list) { "bar called" }
}
List.metaClass.mixin ListUtils

assert "foo called" == [1, 2, 3].foo()
assert "bar called" == [1, 2, 3].bar()
From the DefaultGroovyMethods source, it looks like you can currently call .mixin on any Class or MetaClass object, passing a new Class (here ListUtils) or list of classes. Awesome.

Paulk made a few comments regarding the Maximum Segment Sum implementation in Groovy from my last post. Looking at both the F# and Groovy original implementations, it was easy to see the value that F# brought for functional programming. But the new versions can be seen here, and the results are not so different.

Open classes are still available in F#, despite it being a statically typed, compiled language. I still find the F# open classes syntax simple and appealing:
open List

type List with
member x.foo = "foo called"
member x.bar = "bar called"

if "foo called" <> [1;2;3].foo then failwith "error"
if "bar called" <> [1;2;3].bar then failwith "error"
I especially like how the 'this' reference can be named anything at all (here 'x').

Let the golf begin.

16 comments:

Hamlet D'Arcy said...

Jesse @ GUM asked for a little more clarification on why the Groovy mixin is cool. It should allow you to mixin your existing Java *Util classes, like StringUtils, which everybody has. And mixing them in will not require some funky adapter class or dispatch table setup:

import com.mycompany.StringUtils

String.metaClass.mixin StringUtils

Jon Harrop said...

Apples and oranges here, I suspect. The F# is not dynamically retrofitting new methods onto an existing class as the Groovy probably is. For example, I believe the scope of the new member in F# is limited to the remainder of the current compilation unit, which seriously limits the utility of this approach.

However, this technique is often considered to be extremely poor coding style anyway.

Charles Oliver Nutter said...

Why do you have to specify the target type as a parameter to the mixed-in method? Say I want to create an Enumerable in Groovy that works against arbitrary types. Can I do that?

Also, why specify the "this" object at all? In Ruby, within th emixed-in method self IS the object you're calling against, and you have access to its instance vars and private methods.

Robby O'Connor said...

That's been there for awhile now...here's a post from before the 1.6 beta was released.

http://robbyoconnor.blogspot.com/2008/04/new-groovy-mixins-syntax.html

paulk_asert said...

Re: Why do you have to specify the target type as a parameter to the mixed-in method?

The parameter is the class containing the methods to be mixed into a type (as shown) or object (if you prefer). By adding to List, you add these methods to all classes which implement the List interface including any List types returned by other method calls in this example.

Re: Say I want to create an Enumerable in Groovy that works against arbitrary types. Can I do that?

Not exactly sure what you are after. I can create a method on Object that will be applicable to all objects. There is already an iterator() method on Object defined this way. Other classes or objects can override the default definition for iterator() if they so choose.

Ricky Clarkson said...

The dynamic languages always seem to make this a class-level construct, which has some convenience (you don't need to repeatedly import it in each source file), but seems a bad idea, as it doesn't work when you use two libraries that both define the same methods in different ways.

Static languages tend to have it scoped. Here's a C# extension method, but with [] for generics because I don't like reading or writing HTML escape codes:

public static int FirstOddNumber(this IEnumerable[int] self) {
foreach (var i in self)
if (i % 2 != 0)
return i;
throw null; }

If you import (use) the class that FirstOddNumber is defined on you can then do things like:

Console.WriteLine(new[]{1,2,3}.FirstOddNumber());

If two libraries define FirstOddNumber the order of imports (using clauses) will decide which is used, on a per-file basis - extension methods are a purely static facility.

Scala's implicit conversions are quite similar, but a step up in power - you can call methods from a type that your type can be implicitly converted to.

F#'s feature seems equivalent to C#'s and Scala's, and Groovy's seems to promote runtime errors, which seems to be the entire reason for Groovy's existence - there'd be no other reason to choose an ugly syntax like Java's as your base for a dynamic language.

paulk_asert said...

@Ricky: I guess beauty is in the eye of the beholder. I find the Groovy solution here much more appealing than the F# one. What do the Scala and C# solutions look like?

Ricky Clarkson said...

paulk,

I showed the C# version in the last comment. :)

The Scala one:

class RichList[T](wrapped: List[T])
{
. . def foo="foo called"
. . def bar="bar called"
}

implicit def richList[T](wrapped: List[T]) = new RichList[T](wrapped)

Then you can write List(1,2,3).foo and List(1,2,3).bar.

The above can be made shorter, but I think you should find out how when you're actually using the language. The shorter version is easier to misunderstand as a casual observer.

paulk_asert said...

@Ricky: I was referring to the MSS example but no problems if you don't have time.

Hamlet D'Arcy said...

I'll offer a cup of coffee payable via pay pal to the author of the first Scala version.

Ricky Clarkson said...

paulk,

I don't know what MSS is. I've scrolled up and down this blog post a few times and haven't found out what it is.

Hamlet,

Do I get that coffee?

Hamlet D'Arcy said...

MSS is maximum segment sum. See the previous post called "Groovy vs. F# Showdown - Side by Side Comparison" (in which you were quoted).

http://tinyurl.com/6bf2fy

James Iry said...

Sorry for the retro post, but it seems nobody ever claimed the Scala prize. I've put it in a paste so any magic characters don't get eaten.

http://paste.pocoo.org/show/96201/

It pretty much corresponds to the F# solution but suffers a bit from a weaker type inference system and the need to denote unapplied parameters with _. I did not make a version with implicit conversions as Ricky suggested.

For the sake of completeness a Haskell version is at

http://paste.pocoo.org/show/96202/

I played a little golf with the Haskell version, but not much. :-)

Hamlet D'Arcy said...

Cool, thanks. I have a sense that Scala's type inference is not as good as F#. Seeing all those type annotations in Scala code was what made me try F# and in general I've been really impressed. I /think/ F# uses the Hindley-Milner type inference which seems to be the gold standard. Any idea what Scala uses?

James Iry said...

HM is a type system that has the nice property that every expression has a single principle type and it can be inferred.

Unfortunately, HM lacks any notion subtyping which means it doesn't fit in the OO world at all well.

F# doesn't have an HM type system since it needs to interface with .Net well. I don't know the details of F#'s inference algorithm, but I do know it doesn't promise complete type inference, it just tries its best. The plus side is a lot less type annotation, the down side is it can be surprising when type inference fails.

Scala takes a very conservative approach to type inference and uses a purely local type inference algorithm. Scala also has a richer type and module system. So we Scalars put in a bunch more annotation (although still a fraction of what the poor Java bastards deal with).

More extensive type inference is definitely on the Scala team's agenda. Don't be surprised if they steal some ideas from F#. The two teams know each other well and there's already been plenty of cross pollination.

Jon Harrop said...

@James

OCaml and F# both have a HM core. So you can write ML code in both languages and it works as expected: all types are inferred. The inevitable deviations occur when you start using subtyping or System F types and have to annotate in both languages.

Scala does not have a HM core and, consequently, it cannot compile ML code without a lot more help from the programmer. Specifically, Scala does not do automatic generalization of function types so the programmer has to infer the type variables of all functions by hand and write them explicitly in the source code.

ML's productivity is rooted in the ability to write and compose large numbers of tiny functions and that is heavily reliant upon automatic generalization. Hence, I say that Scala has failed to capture ML's productivity benefits.

The other major problem with Scala is, of course, lack of tail calls. These two reasons are why I chose to pull out of Scala and not write Scala for Scientists.

Finally, what are the chances of Sun implementing core features like tail calls in the JVM in the face of their ongoing financial troubles?