Permalink

Groovy features in Nice, part 2

09 AUG 2004

In my last post I started comparing Nice and Groovy on the basis of Groovy's features as explained on the Groovy homepage. I did this not to denigrate the fine work the Groovy folks have done, but to point out capabilities of Nice that have perhaps not been publicized very well. Here's Part 2.

I should mention that some of the features I mention here (namely list slicing and using someList[someFunction] to mean someList.filter(function)) are not yet available for release, but they're hardly deep magic, either. For instance, here's the implementation of the filter syntax above:


 List get(List list, T->boolean function) = 

	list.filter(function);

Anyway, I'm not going to mention anything that isn't already working on my machine, all of which will be available by or before version 1.0.

Native Syntax for Lists and Maps

Groovy

myTuple = 1, 2, 3;

print myTuple[2];



anotherTuple = (1, (2, 3), 4);

print anotherTuple[2:3];



list = { 1, 2, 3 };

list.add(4);

print list[2:4];



block = { print 'hello'; };

block.call();



block = { x : print x; }

block.call('hello');



list = [ 'a', 'b', 'c' ];

list.each { i : print i; }



map = [ 1 : 'bob', 2 : 'james' ];

print map[1];

map[4] = 'jon';

Nice

let myTuple = (1, 2, 3);

let (a0, a1, a2) = myTuple;

println(a2);



let anotherTuple = (1, (2, 3), 4);

let (b0, b1, b2) = anotherTuple;

println(b1, b2);



let list = [ 1, 2, 3 ];

list.add(4);

println(list[2..3]);



let block = () => { println("hello"); };

block();



let list = [ 'a', 'b', 'c' ];

list.foreach(int i => println(i); }

// or, equivalently:

list.foreach(println);

// or, again:

for(i:list) { println(i); }



let map = [(1,"bob"), (2,"james")].listToMap();

println(map[1]);

map[4] = "jon";

Groovy SQL

Groovy has some handy syntax for SQL. It's the same handy syntax it uses for other things. Nice is in the same boat. I've not yet had reason to write a database library for Nice, but let's just write a quick sketch here and see how it goes. Of course, we could just use JDBC as-is, but we can do much better.

package nice.sql;

import nice.functional;

import java.sql.*;



Connection connect() { throw new Exception("Not implemented"); }



//Generic query. Not sure what this ought to look like, but this will work

//for our examples here.

var ?Connection conn = null;

Iterator<ResultSet> query(String sqlQuery, ?List<Object> binders = null) {

    if (conn == null) {

        conn = connect();

    }

    let c = conn;

    if (c != null) {

    let stmt = c.createStatement();

    try {

        if (binders != null) {

            //Replace all the ? chars with our given binders.

            let tokens = binders.map(sql).iterator();

            while(sqlQuery.indexOf("?") > -1 && tokens.hasNext())

                sqlQuery = sqlQuery.replace('?', tokens.next());

                }

            return iterator(stmt.executeQuery(sqlQuery));

    } finally {

       stmt.close();

    }

   }

   throw new Exception("Couldn't connect");

}



//Wrap up ResultSets as Iterators, so we can use all the built-in

//methods for iterators that Nice already supports.

Iterator<ResultSet> iterator(ResultSet res) {

    return iterator(() => {

    if (res.next())

        return res;

    return stop();

    });

}



//Support [] operator for indexes, dynamic typing here!

<T> T get(ResultSet r, int i) = cast(r.get(i));



//Support [] operator for column names, dynamic typing here!

<T> T get(ResultSet r, String col) = cast(r.get(col));



//Handy SQL formatting

String sql(?Object obj) = obj == null ? "null" : obj.toString();

sql(String s) = "'" + s + "'";

//Need one for dates...





One interesting thing to notice here is how well Nice handles dynamic typing when that's what's really necessary. See the implementation for the get method? The <T> bit at the front introduces a type variable, exactly the same sort of type variable we use for writing things like List<T>. Notice however, that though the function returns type T, there are no Ts in the arguments to the method, nor any other constraints on T. That means, T can be any type at all. Voila, dynamic typing. You promise the compiler you know the type you're expecting, and you get a runtime exception if you get it wrong, same as if you used casting or a dynamically typed language. You don't have to use a cast each time you call it, though, which is a lot nicer than Java's approach.

Because we're implementing a simple database library for freestyle data access, we don't have types available for the tables we're working with. If we did, we could talk about how to build a statically typed interface to the database.

Now, let's compare:

Groovy

import groovy.sql.Sql

import groovy.sql.TestHelper



foo = 'cheese'

sql = TestHelper.makeSql()



sql.eachRow("select * from FOOD where type=${foo}") { 

    println "Gromit likes ${it.name}" 

}
Nice

import nice.sql;



void main(String[] args) {

	let foo = "cheese";

	for(r : query("select * from FOOD where type = ?", [foo])) {

		println("Gromit likes " r["name"]);

	}

}

Groovy

import groovy.sql.Sql

import groovy.sql.TestHelper



foo = 'cheese'

sql = TestHelper.makeSql()



answer = 0

sql.eachRow("select count(*) from FOOD where type=${foo}") { row |

	answer = row[0]

}

assert answer > 0

Nice

import nice.sql;



void main(String[] args) {

	let foo = 'cheese'

	int answer;

	for(row : query("select count(*) from FOOD where type= ?", [foo]))

		answer = row[0];

	assert answer > 0;

}

Groovy

import groovy.sql.Sql

import groovy.sql.TestHelper



sql = TestHelper.makeSql()



food = sql.dataSet('FOOD')

cheese = food.findAll { it.type == 'cheese' }

cheese.each { println "Eat ${it.name}" }

Nice

import nice.sql;



void main(String[] args) {

	let food = query("FOOD");

	let cheese = food[ResultSet r => r["type"] == "cheese"];

	for(r:cheese)

		println("Eat " r["name"]);

}

More later.