This is the final article in our eight part series. We have explored advanced Functional Programming for the curious, but practically minded, Kotlin developer. I hope you have found it worthwhile.
Here’s a review of everything covered to date:
- Part 1: The Traverse operation
- Part 2: Using Applicatives
- Part 3: Higher Kinded Types
- Part 4: The Kleisli Type
- Part 5: Using Typeclasses
- Part 6: Using Optics
- Part 7: The State Type
In this final article I would like to:
- Show you some extra FP operations from Arrow
- Offer some insight into new features in Arrow 1
- Provide some links to resources I have found useful
Part 1: Additional Operations from Arrow
We have seen that Arrow can do an awful lot to simplify your day to day coding. But our survey was far from comprehensive. Here are some additional operations that I find useful in everyday development.
Making a Start
Most of our examples will begin with code similar to the below. We iterate over the range of integers between one and six and map them into either a Right or a Left:
[Left(1), Right(2), Left(3), Right(4), Left(5), Right(6)]
From Map to Bimap
We have already seen that map with Either is right-biased. Values inside a Left are ignored. But if you want to apply a transformation against both Left and Right then you can use
In the example below we multiply the odd numbers (contained within a Left) by ten and the even numbers (contained within a Right) by one hundred:
[Left(10), Right(200), Left(30), Right(400), Left(50), Right(600)]
In previous articles we used
fold as a terminal operation. We supplied two side-effecting functions, in order to process Left and Right results. This could be as simple as
However this is not the only possible use of fold. Unlike
bimap the fold operation returns a value instead of a container. So whereas applying
bimap to an Either would produce another Either, applying
fold produces a value.
Here is the same example, rewritten to use
fold. As you can see the output is a list of numbers, rather than a list of Either:
[10, 200, 30, 400, 50, 600]
Fold comes in a number of interesting variations. For example
bifoldLeft allows us to inject an additional value into the function.
Let’s use ten as our additional value. We add this value to the odd numbers whilst multiplying the even ones:
[11, 20, 13, 40, 15, 60]
In containers with two outcomes, it is possible to alternate between options via the
swap operation. In this case all the Right items become Left, and vice versa:
[Left(1), Right(2), Left(3), Right(4), Left(5), Right(6)]
[Right(1), Left(2), Right(3), Left(4), Right(5), Left(6)]
In previous articles we saw that a Semigroup is a Container with a
combine method. Most functional types are Semigroups, including Either.
maybeCombine method combines two Either instances, provided they are both Left or Right. Here we will add one hundred to every Right instance in the list, but the Left instances will be unaffected:
[Left(1), Right(102), Left(3), Right(104), Left(5), Right(106)]
The second article in our series examined Applicatives in detail. Lists count as Applicatives and hence provide an
If you provide a list containing a single function, then that function is applied to each item in the original list, as shown in result1 below. If you provide multiple functions then every function is allied to every item, as shown in result2:
[10, 20, 30, 40]
[100, 1000, 200, 2000, 300, 3000, 400, 4000]
Part 2: New Features in Arrow 1
When Arrow was first created, it was very much a volunteer driven effort to see if a library similar to Cats or the Haskell base library could be made to work in Kotlin. Happily the answer was yes! But some adjustments had to be made, given that Higher Kinded Types and Typeclasses were not intrinsic to Kotlin. See article three and article five in this series for more details.
As Arrow approaches the all important 1.0 release, the focus has shifted to maximising it’s usability and making the most of language features unique to Kotlin. This has resulted in some major changes. The most significant is that Higher Kinded Types will be dropped, for the foreseeable future until the Kotlin IDEA plugin is aware of community Kotlin compiler plugins.
As we have seen HKT’s require calls to
fix, which are an impediment to learning. The only way to avoid these calls would have been via a compiler plugin and proper IDEA support. This could have inserted downcasts where required, without any manual intervention. Unfortunately, whilst Kotlin 1.4 provides a universal backend for all compilers, a standard API for compiler plug-ins has yet to emerge.
The good news is that a better alternative is already available. When you declare a function with
suspend the compiler implements a continuation based state machine to handle the pausing and resumption of the function. My colleague Eamonn and myself wrote an article on this last year.
This mechanism can be used for more than concurrency with co-routines. Research has shown that suspending functions can be used to implement all the computation blocks over the types we have discussed in this series. So Either, Validated, State etc… relevant effect operators can be implemented as suspending functions, without HKT’s or Typeclasses.
This redesign, with a focus on suspension, will apply not just to Arrow Core, but also to Optics and Fx which by now provide inline versions of most popular FP operators to be used alongside suspension. With this change the IO type will be deprecated, given suspend functions cover all use cases of IO without the allocation and ADT costs.
You can read about this redesign in depth on the Arrow site. It offers substantial improvements in performance, due to the reduced need to allocate instances associated with Typeclasses, ADTs and abuse of the heap for these patterns to become stack-safe. The core types will also become easier to use - for example when calling
traverse you will no longer need to pass in the associated Applicative instance.
All this new functionality will be arriving in two upcoming releases. Arrow 0.12 (already available in preview) will deprecate HKT’s and Typeclasses, whilst Arrow 0.13 will remove them entirely. Both of them will come out in the next few weeks, at the same time.
Users that are not using kinds, or don’t mind breaking changes, can go straight to 0.13. This is the release which will form the basis for Arrow 1.0, which is expected to be released this summer. It will be supported for the long run and easier industry adoption.
Part 3: Additional Resources
Here are some books, papers and talks which I particularly recommend. I’ve listed them in ascending order, based of the amount of FP knowledge the author(s) assume:
- A superb talk by Kevlin Henney, covering how Lambdas and Objects are not adversarial. In reality they are overlapping and substitutable.
- A keynote by Rúnar Bjarnason (author of the ‘red Scala book’) on how pure functional coding enables the composition of large systems.
- The upcoming Kotlin / Arrow version of the book mentioned above.
- A talk by Eric Torreborre on how features from Haskell have been incrementally adopted into mainstream languages.
- Scott Wlaschin on how Functional Patterns can be used to facilitate Railway Oriented Programming.
- An introduction to Arrow by Raúl Raja Martínez at KotlinConf.
- Philip Wadler introducing Category Theory, and it’s relevance to IT.
That’s it folks! I really hope you have found this series useful, and that it has helped you enhance your knowledge of FP concepts. Feedback is always welcome — both here, on Twitter and at work. Happy coding!
My thanks to Richard Gibson and the Instil training team for reviews, comments and encouragement on this series of articles. Special thanks on this occasion to Raul Raja from 47 Degrees, for chatting with Richard and myself about their vision for Arrow 1. All errors are of course my own.