Groovy Operator Overloading Does Not Work In The ColdFusion Context
Disclaimer: I just started looking into Groovy so a lot of this might be dead wrong.
When I watched Barney Boisvert talk about Groovy, one of the first things that I wanted to try was operator overloading. It's been a really long time since I've played around with a programming langauge that even allowed it, so the prospect of implicitly handling the (+) operator with underlying logic was extremely titilating. To experiment with this concept, I took my Class instantiation demo from yesterday, paired it down, and added the method, Person::enterRelationship() (NOTE: I have moved my Groovy Factory code into a CFInclude as it had no immediate value in these demos):
<!--- Import the CFGroovy tag library. --->
<cfimport prefix="g" taglib="../cfgroovy/" />
<g:script>
<!---
Include the groovy factory. This will put an instance
of the "GroovyFactory" into the variables scope:
varibles.groovyFactory = new GroovyFactory();
--->
<cfinclude template="./groovyfactory.groovy.cfm" />
<!--- ------------------------------------------------- --->
<!--- Groovy Class Definitions ------------------------ --->
<!--- ------------------------------------------------- --->
class Person {
private def name = "";
public Person( String name ){
this.name = name;
}
public String getName(){
return( this.name );
}
<!---
This method allows a relationship to be created as a
behavior made available from a given person instance.
--->
public Object enterRelationship( person ){
return(
new Relationship( this, person )
);
}
};
<!--- ------------------------------------------------- --->
<!--- ------------------------------------------------- --->
class Relationship {
def private Person person1;
def private Person person2;
public Relationship(
Person person1,
Person person2
){
this.person1 = person1;
this.person2 = person2;
}
public String toString(){
return(
(
person1.getName() +
" is dating " +
person2.getName() +
" ... awesome!"
).toString()
);
}
};
</g:script>
<!--- Create a girl. --->
<cfset sarah = groovyFactory.get( "Person" ).init( "Sarah" ) />
<!--- Create a boy (using a slightly different syntax). --->
<cfset ben = groovyFactory.get( "Person" ).init( "Ben" ) />
<!--- Create a relationship. --->
<cfset relationship = ben.enterRelationship( sarah ) />
<!--- Output the relationship string. --->
<cfoutput>
#relationship.toString()#<br />
</cfoutput>
As you can see in the Groovy classes above, the call to Person::enterRelationship() is a proxy call to [new Relationship()], using the target person and the given person instances as the two people being used to form the new Relationship instance. We haven't done any operator overloading yet, but we've set up the context for it. And, when we run the code, we get the appropriate Relationship output:
Ben is dating Sarah ... awesome!
Now that we have our Person::enterRelationship() method, let's overload the plus (+) operator. Since I view (+) as more of a "gesture" and short-hand for forming a relationship between two peopl, what I'm gonna hve it do is have it (the plus operator) turn around and call the enterRelationship() method. This way, we centralize the logic for forming relationships. You'll notice that I am overloading the plus (+) operator by providing the Person class with a plus() method:
<!--- Import the CFGroovy tag library. --->
<cfimport prefix="g" taglib="../cfgroovy/" />
<g:script>
<!---
Include the groovy factory. This will put an instance
of the "GroovyFactory" into the variables scope:
varibles.groovyFactory = new GroovyFactory();
--->
<cfinclude template="./groovyfactory.groovy.cfm" />
<!--- ------------------------------------------------- --->
<!--- Groovy Class Definitions ------------------------ --->
<!--- ------------------------------------------------- --->
class Person {
private def name = "";
public Person( String name ){
this.name = name;
}
public String getName(){
return( this.name );
}
<!---
This method allows a relationship to be created as a
behavior made available from a given person instance.
--->
public Object enterRelationship( person ){
return(
new Relationship( this, person )
);
}
<!---
This plus() method is an operator overload method
that will handle allow us to implicitly handles calls
like (person + person).
--->
public Object plus( Person person ){
return(
this.enterRelationship( person )
);
}
};
<!--- ------------------------------------------------- --->
<!--- ------------------------------------------------- --->
class Relationship {
def private Person person1;
def private Person person2;
public Relationship(
Person person1,
Person person2
){
this.person1 = person1;
this.person2 = person2;
}
public String toString(){
return(
(
person1.getName() +
" is dating " +
person2.getName() +
" ... awesome!"
).toString()
);
}
};
</g:script>
<!--- Create a girl. --->
<cfset sarah = groovyFactory.get( "Person" ).init( "Sarah" ) />
<!--- Create a boy (using a slightly different syntax). --->
<cfset ben = groovyFactory.get( "Person" ).init( "Ben" ) />
<!--- Create a relationship using PLUS operator overloading. --->
<cfset relationship = (ben + sarah) />
<!--- Output the relationship string. --->
<cfoutput>
#relationship.toString()#<br />
</cfoutput>
As I was writing this code, I could barely contain my excitement; which, is why I was completely heart broken when running the above code threw the following ColdFusion error:
The value Person cannot be converted to a number.
coldfusion.runtime.Cast$NumberConversionException: The value Person cannot be converted to a number. at coldfusion.runtime.Cast._double()
To make sure I wasn't doing something wrong, I re-ran the code, this time putting the "meat" of it back into the Groovy context (I have removed the redudandant code for clarity):
<!--- Import the CFGroovy tag library. --->
<cfimport prefix="g" taglib="../cfgroovy/" />
<g:script>
<!--- ... PREVIOUS CODE ... --->
<!--- Create two people. --->
def ben = new Person( "Ben" );
def sarah = new Person( "Sarah" );
<!---
Create a relationship between these two people using
the Groovy operator overloading (plus operator).
--->
def relationship = (ben + sarah);
<!--- Output the relationship as a string. --->
println( relationship );
</g:script>
Notice that this time, since we are in the Groovy context, I don't have to explicitly call toString() on the resultant Relationship object. That is because Groovy will access the existing toString() method when it needs to implicitly convert a given object to a string (such as is required by the println() function). This time, when we run the above code, the plus operator (+) overloading successfully calls the Person::enterRelationship() method and we get the following output:
Ben is dating Sarah ... awesome!
Operator overloading is definitely an awesome concept; but, unfortunately, it looks like the operator overloading functionality provided by the Groovy language does not extend beyond the Groovy context. Once we move out of Groovy and back into the ColdFusion context, ColdFusion tries to perform math with the plus (+) operator, which is why it threw an exception above, trying to convert the Person instance into a numeric value.
Want to use code from this post? Check out the license.
Reader Comments
I don't actually know this for fact, but my suspicion is that the method overloading in Groovy is implemented as compiler trickery, much the same way varargs are implemented in both Groovy and Java. Since your CFML doesn't run through the Groovy compiler, it doesn't get that syntactic sugar. However, you can still use the 'plus' method (which is what the Groovy compiler is converting the '+' into):
[cfset relationship = ben.plus(sarah) /]
You lose some of the elegance of an operator, but you still retain the 'addition' semantic.
@Barney,
Ah, ok that makes sense. It would have been cool though :)
Yeah, it'd be awesome if it worked, but oh well. Groovy does stuff at both compile time and runtime, and it's not always obvious where a given feature is implemented until it fails in the CFML context (and therefore is a compile-time feature).
The question is - what to play with next :)
Slightly off-topic, but the operator overloading stories are some of my favorites at thedailywtf.com :) Definitely worth reading if you have some free time.
@Roland,
Ha ha, I guess anything can get out of hand.
Hi Ben, Thanks for this informative post. Now I am reading ur old posts too