Groovy features in Nice, part 2
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.
