Helper code to mimic Clojure fns in Scala

I’ve finished my 3.5 year stint writing Scala, and I haven’t stopped missing writing Clojure. The knowledge of Clojure continues to heighten and inform my programmer sensibilities. One thing that I appreciated about Scala is that it was as good of a medium as you might practically find to allow writing Clojure without writing Clojure. I liked to think of Scala as the canvas on which I painted my Clojure ideas. Because Scala makes itself amenable to many styles of programming at once (at least, FP and OOP), it was possible to write code by imagining what the Clojure code would look like, and then writing that in Scala syntax. Interestingly, the more I did this, and the more faithfully I did so, the more people implicitly (no pun intended!) acknowledged the code as “good Scala code”. Because, you know, most Scala programmers agree that good Scala code puts “val”s at the top of a function body, uses immutable collections exclusively, prefers functions over (object) methods, and makes functions small, stateless, and composable. More on that later. Here, I want to simply release some of the code that I wrote in Scala to fill in a few perceived gaps in Scala’s Seq abstraction, where the perception is based on what I was accustomed to using in Clojure.

Code snippets for implementing Clojure fns / functionality in Scala

Note: the following code snippets are Apache 2 licensed, so go ahead and use them wherever you would like as you see fit!

The first code snippet is perhaps the more interesting of the two. I provide my implementations in Scala of Clojure’s merge-with and partition-by:

The code originated in the fact that something as simple as partition-by didn’t exist in Scala, and there was really no way to cleanly finish the task I was working on without going off and implementing it. Soon after, merge-with followed, and then “mergeLeftWith” was created to offer a version that starts with an initial value. The analogy is if merge-with is like reduce with no initial value argument, then “mergeLeftWith” is like using Clojure’s reduce with an initial value argument (aka Scala’s foldLeft).

The second code snippet was useful for reducing all the boilerplate that inevitably surrounds the use of Options in Scala. I also added some pretty-printing functions that I used in testing:

On the topic of whether all the ceremonial code required for Scala’s liberal use of Option (now partially present in Java 8+ due to Java’s careful embrace of FP), you should really see Effective Programs – 10 Years of Clojure by Rich Hickey. It articulates well the inherent tradeoffs that we make in our choice of programming languages, which are merely tools to a means. But it brilliantly articulates an opinion/perspective that is practical and speaks to my sensibilities of why I found the boilerplate code in Scala slowing me down more than I would like for the amount of perceived benefit I got in return (not much). Most of the benefit in terms of confidence in my code came from my various unit and integration tests.

And speaking of tests, don’t underestimate the utility of the pretty-printing functions. The reason why I created them was because I had to convert Clojure code that I wrote that used the expectations testing library. That library is amazing, especially when your logic requires data structures. The library isn’t radically different to other “fluent” testing libraries, nor is that where most of the benefit lies. The real benefit occurs when you spend the most time using it — when your tests fail! And you don’t necessarily look back at your test code, but rather, you look at the test output to gather clues of what failed and how. Expectations does the following in its error output:

  • re-prints the test code causing the failure, with the provided values plugged into the code if necessary
  • instead of printing “actual value [A] is not equal to expected value [E]“, it neatly prints (using line breaks and horizontal spacing) the values so that they line up. (I can’t tell you how many times I’ve seen the test error output that reproduces default Java object printing of 2 large, detailed objects side by side without even line breaking
  • more importantly and awesome-ly, it only shows you the portion of expected and actual values you need to see
  • and in a terminal with colors, you get different colors for the re-printing of the original test code, the expected value, and the actual value

I wasn’t about to do all that because I couldn’t possibly do so. I don’t think Scala has data structure diff’ing libraries because it doesn’t share Clojure’s proud focus on data-oriented programming. So the best I could do to recreate expectations in Scala was create helper test functions, one for comparing sets, one for comparing maps, one for comparing seqs, etc. where the differing values would be print on separate lines so that they would line up and you could more easily see where they diverge. For seqs, my testing fns would additionally iterate through the seqs and print the indices of the last congruent & first divergent elements in the seqs. Ultimately, the less time you spend sifting through the error messages, the faster your unit/integration tests are, and the faster you get back to doing the interesting, productive work that you intended to.

Parting thoughts about Scala

I found it strange that when I stepped in my previous role, which used Scala heavily, I basically just started writing my Scala code as if it were Clojure and didn’t really punished for it. I created a couple of semi-stateful OOP-y classes in my very first Scala program just to make it not look obvious that I was doing so, but then I got critiqued for how it made my code confusing. After that, I decided to just write Clojure code in Scala syntax from then on. As a result, at a high-level, my code:

  • used Scala object classes in lieu of Clojure namespaces
  • wrote functions statelessly, with all the vals and block-local function definitions occurring before any other code in the function
  • used Scala case classes to represent Clojure heterogenous maps / Clojure records
  • avoided any typical OOP “plain” Java classes
  • used Java interfaces as substitutes for Clojure protocols

With all that, and combined with my helper fns/code in main code and test code, I felt pretty comfortable in Scala. And arguably, I may have had a quicker pace in getting code written than many Scala programmers around me who inevitably got caught in some compiler error or type representation riddle because they felt they were trying to do Scala the “right way” (for whatever value of “right way” they imbibed).

“Hey, (at least) it’s better than Java!” — that’s the most popular selling point that I’ve heard for Scala through the years. And I agree. There’s nothing more that I wish to say further on the topic that Rich doesn’t say much more insightfully in his his talk Effective Programs – 10 Years of Clojure. I don’t know what the future holds for programming languages. But I’m still optimistic in declaring that Clojure is a language for the ages.

  • Eric

    About your operations on Option: do you know about the Applicative typeclass in cats or scalaz?

    • Elango Cheran

      No, I don’t, although I did definitely hear of scalaz and cats sounds very vaguely familiar. I remember that teammates who knew more about scalaz and shapeless said to steer clear because their benefit was not worth the complexity that they introduced. I could understand their perspective because it is a more localized “instance” of the point about language tradeoffs that Rich explained in his talk. So I never bothered to try for that reason, for better or for worse.

      If you’re alluding to the idea that maybe those optionAnd and optionOr functions are duplicates of logic elsewhere, then I totally agree. I had the feeling that it was true as I wrote them, but I was going for fast and simple over the complexity of finding & learning a new dep for those 2 functions. Kind of like how lots of Clojure programmers probably re-invent mapping over map values instead of using the pre-existing fmap function (