Comparing my Smalltalk and Eclipse Collections Wordle Kata Solutions
Solving a problem with multiple languages and libraries can sometimes bring new insights.
Challenge Yourself and Others to Learn
I accepted a JLDD (Jet Lag Driven Development) challenge recently to implement a Wordle Checker Kata in Java. My friend José Paumard sent me this challenge and I set out to solve it using my favorite Java collections framework — Eclipse Collections. I wrote a blog about the challenge and my Java / Eclipse Collections solution.
José then published his response in a great JEP Café tutorial video which he tweeted about.
I really enjoyed the video and learned an approach to solving this problem using pattern matching that I wouldn’t have thought of on my own.
I had come up with three slight variations on the same solution using Java with Eclipse Collections, and thought it might be interesting to try and build a solution using Pharo Smalltalk. So of course I built one, and tweeted about it.
Smalltalkers will hopefully recognize the emoji picture I used. It is the basic syntax of a lambda in Smalltalk — [|]
. Parameters go on the left side of the pipe, and statements/expressions go on the right.
A few days after posting this, I posted a slight variation on the Smalltalk solution. I have included both Smalltalk solutions in the blog along with three Java / Eclipse Collections solutions.
My Smalltalk Solutions
I used Pharo 8.0 to implement my Smalltalk solution. First, I wrote the set of test assertions in a WordleTest
class. Each assertion creates an instance of a Wordle
class with a hidden
word, and then calls a method named guess:
which will output the expected result of the two words matched.
I created a Wordle
class with an instance variable named hidden
, and then added a method named guess:
which takes a String
parameter.
As you read my Smalltalk solutions, I would note the following. Smalltalk has a very simple and powerful programming paradigm which leads to a very minimal syntax with only five reserved words (true
, false
, nil
, self
, super
) in the language. There are no control statements (if
, for
, while
) in Smalltalk , and everything is accomplished by sending messages to objects. The equivalent of if statements, for loops, while loops, etc. in other languages are accomplished by sending messages to different objects. Much of this is accomplished by passing lambdas around to methods as parameters, or calling appropriate methods on lambdas.
Smalltalk Solution #1
I wrote my first Smalltalk solution for guess:
using with:do:
and with:collect:
methods. This solution passes all of the tests above.
The with:do:
and with:collect:
methods are available on the SequenceableCollection
class in Smalltalk. In Smalltalk, a String
is a SequenceableCollection
. The following is a view of the Smalltalk Collection hierarchy.
The method named with:do:
lets you iterate over two String
instances at the same time. The first argument passed is the the String
that you want to iterate over with this String
, and the second argument is a two argument lambda which doesn’t return anything (equivalent of a Procedure2
or BiConsumer
in Java). This let me compare letters at the same index, exclude all of those that were equal and add the remaining letters to the Bag
named remaining
, which I initialized on the first line of the method. For anyone unfamiliar with the Bag
data structure, a Bag
is an unordered collection that allows duplicates. Nikhil Nanivadekar wrote a blog about the Bag data structure in Eclipse Collections which explains how it is implemented.
The second method, with:collect:
, is similar to with:do:
in that it iterates over both String
instances together. The difference is that the two argument lambda that is passed as the second argument is used as a Function2
(a two argument Function
or BiFunction
in Java) to transform the characters to a new String
. The transformation will result in either an uppercase letter, a lowercase letter or a “.” being returned for each combination of letters.
The ifTrue:ifFalse:
method is a method on the Boolean
class in Smalltalk and is overridden by the True
and False
subclasses. The method takes two lambdas as arguments. Depending on whether the Boolean
is true
or false
, one of the two lambdas will be evaluated. The =
method tests for equality and will return true
or false
. The literal true
is the single instance of the class True
, and false
is an instance of False
. I always thought it was kind of clever how Smalltalk implemented if control flow logic without using statements.
Finally, the remove:ifAbsent:
method is available on most Collection
classes in Smalltalk, and in this instance I am calling this method on a Bag
. The result of the method is to return the object that is removed, or if the object is not in the collection, evaluate and return the result of the lambda.
Smalltalk Solution #2
For the second Smalltalk solution, I had hoped to find a method named with:reject:
, but unfortunately, no such method exists today. I could have added the myself, but instead I decided to use a method that was already implemented. I used withIndexSelect:
as a somewhat more awkward alternative to with:reject:
but at least more terse alternative to using with:do:
. I also formatted the code for with:collect:
using the recommended auto-formatting in Pharo. It should make it easier to read and is a good way to break up the ifTrue:ifFalse:
message send with the nested lambdas.
The withIndexSelect:
method is passed each character in the hidden
String
along with its corresponding index in the String
. Indexes in Smalltalk are one based, so start at 1, not 0. This can’t be seen in this particular code example, but I think it’s an interesting difference between Smalltalk and Java. I look up the character at the same index position in the guess String using the at:
method. Because I am using select:
(inclusive filter
) and not reject:
(exclusive filter
), I have to negate the comparison. I use ~=
to compare the two letters, which translates to “not equal” in Smalltalk. Finally, I convert the resulting String
instance from the call to withIndexSelect:
to a mutable Bag
. The rest of the solution is identical to my first Smalltalk solution.
Update: May 10, 2022
I upgraded my Pharo IDE to version 10.0. I also refactored my test cases to use a shared data set. This reduced a lot of duplication, and made the test cases much more pleasant to read.
I put the shared test data in a method named hiddenGuessExpectedData
.
I renamed the test methods to testGuess
and testGuessWithIndexSelect
.
A neat side effect of renaming the test methods to better match the name of the methods on the class, is the class now displays green spheres next to each method and allow you to run the tests for the methods in the class itself.
My Eclipse Collections Solutions
All of my Java solutions used features of the Eclipse Collections library. All three solutions used Strings.asChars(string)
to provide access to the primitive collection API. In Eclipse Collections, this static method returns a CharAdapter
instance wrapping the String
.
Two of the three Eclipse Collections solutions use methods that end in WithIndex
. The third solution uses a method named zipChar
which allows two primitive collections to be zipped together into char pairs. The basic strategy is pretty much the same between my three solutions, with minor differences in implementation.
- Create a
Bag
of characters that do not have direct positional matches between bothString
instances (hidden
andguess
) - Collect up one of three possible states for each character as a result String. Convert exact positional letter matches to uppercase, letter match in different position to lowercase, and no letter match to a “.”
Eclipse Collections Solution # 1
The first solution I wrote using injectIntoWithIndex
and collectWithIndex
. I wrote the original Wordle JLDD Kata Challenge blog with this solution included. You can read the blog for more details on the solution.
The thing that bothered me most about this solution is that Eclipse Collections did not yet have rejectWithIndex
available on primitive collections. The solution using injectIntoWithIndex
is really implementing the rejectWithIndex
pattern.
Eclipse Collections Solution #2
I have recently contributed selectWithIndex
and rejectWithIndex
methods to primitive collections in Eclipse Collections. The same methods existed on the object collection API in Eclipse Collections since the 11.0 release. I used rejectWithIndex
in my second solution, along with the collectWithIndex
approach used in Eclipse Collections Solution #1.
The rejectWithIndex
method here takes a target collection to store the results in.
Eclipse Collections Solution #3
My third Eclipse Collections solution using zipChar
and collectChar
was inspired by an alternative zipChar
solution written by Vladimir Zakharov.
The method zipChar
creates a List
of character pairs between both String instances. I reject
the exact positional matches between char
pairs and collect
those char
values into a MutableCharBag
. Then I collect
char values into a String
based on the algorithm described above.
From Wordle Kata Solutions to EC Integration Test
The Eclipse Collections Wordle Kata solutions I came up with are good examples of using some of the String
iteration patterns available in the library. Because I am leveraging multiple String
iteration patterns, I thought it made sense to turn the kata solutions into an integration level test for Eclipse Collections. I committed a new WordleTest
class into Eclipse Collections along with my contribution of the primitive versions of selectWithIndex
and rejectWithIndex
. Below is the source code for the test.
Comparing the solutions
Here is a visualization of the intersection and set of differences between the methods available in Smalltalk and Eclipse Collections to solve this particular kata challenge.
I quite liked the with:do:
and with:collect:
methods in Smalltalk. I would have preferred to have a with:reject:
method for this particular use case. While I could have added this method on my own to SequenceableCollection
, it would be nice if it was part of the standard library. However, I doubt that this particular use case will be incentive enough to have it added. The following are the methods using with:
as a prefix that are available on SequenceableCollection
.
It was interesting to see that there is a withIndexSelect:
method in Smalltalk which is a slight difference in name to the equivalent of the selectWithIndex
method in Eclipse Collections. There is no equivalent of rejectWithIndex
, which is my preference in this use case. The methods collectWithIndex:
and withIndexCollect:
both exist in Pharo Smalltalk and are synonyms. It appears the naming preference is now in favor of withIndex
as a prefix for these methods.
The method zipChar
in Eclipse Collections has no equivalent in Pharo Smalltalk today. There is also no equivalent of asLazy
either. Most iteration methods on the Smalltalk Collection classes are eager.
In terms of elegance, I think the with:do
and with:collect:
methods worked the best for this use case. Again, it would have been slightly simpler and clearer if there was a with:reject:
method.
In terms of flexibility, I think the withIndex
solutions are better than both the other solutions. Since you have the index, you can do whatever you need with it. For example, this would allow you to iterate over as many collections simultaneously as you need.
In terms of fluency and ease of writing, I really liked the asLazy
plus zipChar
solution which could then just use reject
, collectChar
and toBag
.
Here’s a link to the tweet where Vladimir Zakharov shared his zipChar
solution using Eclipse Collections.
Summary
Learning multiple programming languages and libraries and using them to solve various coding katas can help you improve as a programmer. I think it’s interesting to see how languages and libraries compare and how they evolve over time. I don’t recall the methods I used in Pharo Smalltalk being available in VisualAge for Smalltalk when I programmed in it in the 1990s. This is a good sign, as it means the language and core libraries are still evolving. Similarly, the Eclipse Collections library continues to evolve as Java does, and new methods like selectWithIndex
and rejectWithIndex
now available on both object and primitive collections are a good example of this.
Thank you for reading! I hope you enjoyed this Wordle JLDD Kata Challenge comparison with Smalltalk and Eclipse Collections.
I am a Project Lead and Committer for the Eclipse Collections OSS project at the Eclipse Foundation. Eclipse Collections is open for contributions. If you like the library, you can let us know by starring it on GitHub.