Haiku Kata using String transform, Text Blocks, and Switch Expressions
I solved the Haiku Kata again using more features added since Java 12
Finding String transform
I found a method on the Java String
class I hadn’t seen before. I discovered it while running the String
class through my Java class kaleidoscope. There is a method that was added in Java 12 named transform
.
The transform
method makes it possible to pass a Function
to a String
and have any type returned. In Java, the more popular name for this method is map
on Stream
and Optional
.
I couldn’t understand why this method was added to the String
class. This is the only method on String
that takes a Functional Interface like Function
.
What is the purpose of this method on String
?
Instead of scratching my head wondering, I decided to see what I could do with this method.
Haiku Kata
I created a kata for a set of eleven Haiku I wrote during a self-enforced writing detox period I went through in September 2021. I turned my haiku into a Java kata and blogged about it here. José Paumard turned my Haiku Kata into an amazing YouTube tutorial in JEP Café #9.
José’s JEP Café #9 is currently one of the Top 10 most popular videos on the Java YouTube channel based on views. Congrats José!
Paul King also implemented the Haiku Kata in Groovy last year.
The Haiku Kata was intended to be a way for me to experiment with Java’s Text Block feature. Text Blocks were added as a standard feature in Java 15, and were in preview in Java 13 and 14.
In the Haiku Kata, I adapt a String
stored as a Text Block into a CharAdapter
class from Eclipse Collections. The code I write to do this looks like this.
CharAdapter chars = Strings.asChars("Hello!");
The equivalent using the Java chars
method looks like this.
IntStream chars = "Hello!".chars();
With the new transform
method on the String
class, it is possible to flip this code.
CharAdapter chars = "Hello!".transform(Strings::asChars);
There is something subtle that happens with this inversion of behavior, other than requiring a few more characters. To see the effect, it helps to have bigger String
instances than “Hello!”
. This is why I went back to the Haiku Kata to try the transform
method.
Distinct Letters
One of the first tests to implement in the Haiku Kata is finding the distinct letters in the Tex Block. Using the String
transform
method, it is possible to inline all of the code to look as follows.
@Test
public void distinctLettersEclipseCollections()
{
String distinctLetters = """
Breaking Through Pavement Wakin' with Bacon Homeward Found
---------------- -------- ----------------- --------------
The wall disappears Beautiful pavement! Wakin' with Bacon House is where I am
As soon as you break through the Imperfect path before me On a Saturday morning Home is where I want to be
Intimidation Thank you for the ride Life’s little pleasures Both may be the same
Winter Slip and Slide Simple Nothings With Deepest Regrets
--------------------- --------------- --------------------
Run up the ladder A simple flower With deepest regrets
Swoosh down the slide in the snow Petals shine vibrant and pure That which you have yet to write
Winter slip and slide Stares into the void At death, won't be wrote
Caffeinated Coding Rituals Finding Solace Curious Cat Eleven
-------------------------- -------------- ----------- ------
I arrange my desk, Floating marshmallows I see something move This is how many
refactor some ugly code, Cocoa brewed hot underneath What it is, I am not sure Haiku I write before I
and drink my coffee. Comfort in a cup Should I pounce or not? Write a new tech blog.
"""
.transform(Strings::asChars)
.select(Character::isAlphabetic)
.collectChar(Character::toLowerCase)
.distinct()
.toString();
Assertions.assertEquals("breakingthoupvmwcdflsy", distinctLetters);
}
The transform
method works better here than the original form which would require wrapping the entire text block in a method call to Strings.asChars()
.
This is what it would have looked like without the transform
method.
@Test
public void distinctLettersEclipseCollectionsWithoutTransform()
{
String distinctLetters = Strings.asChars("""
Breaking Through Pavement Wakin' with Bacon Homeward Found
---------------- -------- ----------------- --------------
The wall disappears Beautiful pavement! Wakin' with Bacon House is where I am
As soon as you break through the Imperfect path before me On a Saturday morning Home is where I want to be
Intimidation Thank you for the ride Life’s little pleasures Both may be the same
Winter Slip and Slide Simple Nothings With Deepest Regrets
--------------------- --------------- --------------------
Run up the ladder A simple flower With deepest regrets
Swoosh down the slide in the snow Petals shine vibrant and pure That which you have yet to write
Winter slip and slide Stares into the void At death, won't be wrote
Caffeinated Coding Rituals Finding Solace Curious Cat Eleven
-------------------------- -------------- ----------- ------
I arrange my desk, Floating marshmallows I see something move This is how many
refactor some ugly code, Cocoa brewed hot underneath What it is, I am not sure Haiku I write before I
and drink my coffee. Comfort in a cup Should I pounce or not? Write a new tech blog.
""")
.select(Character::isAlphabetic)
.collectChar(Character::toLowerCase)
.distinct()
.toString();
Assertions.assertEquals("breakingthoupvmwcdflsy", distinctLetters);
}
It is harder to determine what type the select
method is being applied to as you have to scan up to the top of the text block to see the String.asChars(
opening.
Top Three Letters
The test to find the top three letters in the haiku can be rewritten using a single fluent line of code, by using the Switch Expressions feature of Java to make the test Assertions
part of a call to forEachWithindex
.
@Test
public void topLettersEclipseCollections()
{
"""
Breaking Through Pavement Wakin' with Bacon Homeward Found
---------------- -------- ----------------- --------------
The wall disappears Beautiful pavement! Wakin' with Bacon House is where I am
As soon as you break through the Imperfect path before me On a Saturday morning Home is where I want to be
Intimidation Thank you for the ride Life’s little pleasures Both may be the same
Winter Slip and Slide Simple Nothings With Deepest Regrets
--------------------- --------------- --------------------
Run up the ladder A simple flower With deepest regrets
Swoosh down the slide in the snow Petals shine vibrant and pure That which you have yet to write
Winter slip and slide Stares into the void At death, won't be wrote
Caffeinated Coding Rituals Finding Solace Curious Cat Eleven
-------------------------- -------------- ----------- ------
I arrange my desk, Floating marshmallows I see something move This is how many
refactor some ugly code, Cocoa brewed hot underneath What it is, I am not sure Haiku I write before I
and drink my coffee. Comfort in a cup Should I pounce or not? Write a new tech blog.
"""
.transform(Strings::asChars)
.select(Character::isAlphabetic)
.collectChar(Character::toLowerCase)
.toBag()
.topOccurrences(3)
.forEachWithIndex((each, index) -> Assertions.assertEquals(switch (index)
{
case 0 -> PrimitiveTuples.pair('e', 94);
case 1 -> PrimitiveTuples.pair('t', 65);
case 2 -> PrimitiveTuples.pair('i', 62);
default -> null;
}, index < 3 ? each : null));
}
The method forEachWithIndex
is one of the lesser known methods available in Eclipse Collections. The equivalent Assertion code before required calling Assertions.assertEquals three separate times with an indexed lookup into the resulting ListIterable
returned from topOccurrences
.
Duplicates and Uniques
There wasn’t much to change in the duplicates and uniques code, other than inlining Text Block in the test and using the String
transform
method.
@Test
public void duplicatesAndUniqueEclipseCollections()
{
MutableCharBag chars = """
Breaking Through Pavement Wakin' with Bacon Homeward Found
---------------- -------- ----------------- --------------
The wall disappears Beautiful pavement! Wakin' with Bacon House is where I am
As soon as you break through the Imperfect path before me On a Saturday morning Home is where I want to be
Intimidation Thank you for the ride Life’s little pleasures Both may be the same
Winter Slip and Slide Simple Nothings With Deepest Regrets
--------------------- --------------- --------------------
Run up the ladder A simple flower With deepest regrets
Swoosh down the slide in the snow Petals shine vibrant and pure That which you have yet to write
Winter slip and slide Stares into the void At death, won't be wrote
Caffeinated Coding Rituals Finding Solace Curious Cat Eleven
-------------------------- -------------- ----------- ------
I arrange my desk, Floating marshmallows I see something move This is how many
refactor some ugly code, Cocoa brewed hot underneath What it is, I am not sure Haiku I write before I
and drink my coffee. Comfort in a cup Should I pounce or not? Write a new tech blog.
"""
.transform(Strings::asChars)
.select(Character::isAlphabetic)
.collectChar(Character::toLowerCase)
.toBag();
CharBag duplicates = chars.selectDuplicates();
CharSet unique = chars.selectUnique();
Assertions.assertEquals(chars, duplicates);
Assertions.assertEquals(CharSets.immutable.empty(), unique);
}
Final Thoughts
I can’t tell yet if the String
transform
method has interesting uses that I haven’t thought of yet, but I was happy to see how it worked with the the Haiku Kata. In the original version of the kata, I stored the text block into a field called haiku
so as to keep the huge text block out of the methods, taking away from the method calls. The transform
method created a nice separation and progression between the inlined String
Text Block and the transformation code that found the distinct, top three, duplicate and unique characters.
Thanks for reading, and I hope you enjoyed learning about my recent discovery of the String
transform
method.
I am the creator of and committer for the Eclipse Collections OSS project, which is managed at the Eclipse Foundation. Eclipse Collections is open for contributions.