Data structures are one of the most important aspects of a language when it comes to productivity. Java has great
implementations of the common data structures (e.g java.util.ArrayList
and
java.util.HashTable
) but it doesn't help you much when you want to manipulate them: you often end up
writing out the same code over and over again to filter lists, to sort them, etc.
Gosu addresses this problem by adding new methods that leverage Gosu's language features (blocks, in particular) to the core data structures from Java. Gosu also provides additional syntactic support for common collections.
As you may have noticed, a list in Gosu can be declared by using {}
's:
var lstOfStrings = {"This", "is", "a", "list"}You can use Array-style access on lists:
var lstOfStrings = {"This", "is", "a", "list"} print( lstOfStrings[2] ) // prints "a"
Lists in Gosu have a lot of enhancement methods on them, many of which use blocks. Blocks (also called closures or lambda expressions) are a simple way to specify an inline function. They have a lot of uses, but they really shine in data structure manipulation:
var lstOfStrings = {"This", "is", "a", "list"} var longStrings = lstOfStrings.where( \ s -> s.length > 2 ) print( longStrings.join(", ") ) // prints "This, list"The funny
\->
thing is the block. It declares a mini-function that says "Given a String
s
return whether
s.length
is greater than two". You can think of it as an inline version of this function:
function isLongerThanTwo( s : String ) : boolean { return s.length > 2 }Or, if you prefer, this anonymous inner class:
var inner = new com.google.common.base.Predicate<String>() { function apply( s : String ) : boolean { return s.length > 2 } }Blocks allow you to express this logic much more succinctly.
Note that I didn't have to declare a type for the block's parameter, s
, like I did in the function and
anonymous class above. This because the block is defined in a context where Gosu can figure out the argument is a
String. Typically you will not have to define the types of the arguments of blocks.
With blocks you can often remove loads of iterative code you would end up writing in Java. Consider this java code:
List<String> lstOfStrings = Arrays.asList("This", "is", "a", "list"); List<String> longStrings = new ArrayList<String>(); for( String s : lstOfStrings ) { if( s.length() > 2 ) { longStrings.add( s.toUpperCase() ); } } Collections.sort(longStrings, new Comparator<String>() { public int compare( String s, String s2 ) { return s.compareTo( s2 ); } }) StringBuilder sb = new StringBuilder(); for( String s : longStrings ) { if(sb.length() != 0) { sb.append(", "); } sb.append(s); } System.out.println(sb.toString());This code can be written in Gosu like so:
var lstOfStrings = {"This", "is", "a", "list"} var longStrings = lstOfStrings.where( \ s -> s.length > 2 ) .map( \ s -> s.toUpperCase() ) // converts each string to upper case .orderBy( \ s -> s ) // there is a .order() method that could be used here instead print( longStrings.join(", ") ) // prints "LIST, THIS"In Gosu the code more clearly expressed the algorithm, it is less coupled (there is no mix of algorithmic steps in a for-loop, for example) and certainly less verbose.
Blocks have other uses, but, by and large, the big win with them is in Collection manipulation.
Java has many interfaces that contain a single method, which are used as a stand-in for actual closures. In order to facilitate Java interoperability, Gosu blocks and one-method interfaces are automatically converted between one another:
var r : Runnable r = \-> print("This block was converted to a Runnable")This makes some Java APIs very enjoyable to work with in Gosu.
Here are some of the more useful enhancement methods on Lists:
map()
- transforms each element in a list with the given blockorderBy()
- creates a new list ordered by the value returned by the given blockthenBy()
- once you've called orderBy
on a List, you can call thenBy
to add
additional sorting conditions
where()
- creates a new list of all elements that return true
for the given blockwhereTypeIs()
- returns a new list where all elements have the given typefirst()/last()
- return the first and last element of the list, respectivelyfirstWhere()/lastWhere()
- return the first and last element of the list that match the given block,
respectively
partition()
- converts the List to a Map, where the keys are the values returned by the given block,
and the values are Lists of all elements that mapped to that key
toSet()
- converts the List to a Setjoin()
- converts the List to String, using the given string as a delimiter between each elementmaxBy()
- returns the element in the list that has the maximum value returned by the given blocktoTypedArray()
- returns a strongly typed array of the List (e.g. List<String> returns a String[])
Maps can be declared by using {}
's with a right-arrow between keys and values:
var mapOfStringsToStrings = {"This" -> "is", "a" -> "map"}You can also use Array-style access on maps:
var mapOfStringsToStrings = {"This" -> "is", "a" -> "map"} print( mapOfStringsToStrings["This"] ) // prints "is"
There are fewer enhancement methods on Maps than Lists, but there are some:
mapValues()
- transforms each value with the given block and returns the resulting mapwriteToPropertiesFile()
- Calls .toString() on each key and value, and save the result to the
properties file passed in.
Map.readFromPropertiesFile()
- A static method that returns a Map<String, String>
from the given properties file
Map.entrySet()
to modify the Map:
var mapOfStringsToStrings = {"This" -> "is", "a" -> "map"} // removes all entries with a key whose length is less than 2 mapOfStringsToStrings.entrySet().retainWhere( \ p -> p.Key.length > 2 )