-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Release for 2.0 #333
base: main
Are you sure you want to change the base?
Release for 2.0 #333
Changes from 21 commits
263b5b0
b9c81c0
116718b
c28c5a6
54dec11
36a8df9
b267808
fbfe9d1
183ffe3
a58845f
de555b1
58844fd
928d136
ebdee04
a426fe6
d935b65
0279d12
1b12c8a
fde0980
b9fc0e6
71cc9f0
f45caad
38e9cf2
744b11c
6248e7e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,182 @@ | ||||||||||||||||||
--- | ||||||||||||||||||
title: Arrow 2.0 release | ||||||||||||||||||
category: articles | ||||||||||||||||||
tags: [core, articles] | ||||||||||||||||||
--- | ||||||||||||||||||
|
||||||||||||||||||
# Arrow 2.0 release | ||||||||||||||||||
|
||||||||||||||||||
We are happy to announce the next major release of Arrow, version 2.0! | ||||||||||||||||||
As previously announced, migrating your projects to this release should be hassle-free | ||||||||||||||||||
if your code compiled in 1.2.x without any deprecation warnings | ||||||||||||||||||
(except for the breaking change in optics generation discussed below). | ||||||||||||||||||
|
||||||||||||||||||
This release is built with the new K2 compiler, and this gives us the ability | ||||||||||||||||||
to support a wider range of platforms, including WebAssembly. From now on, we shall | ||||||||||||||||||
provide artifacts for every platform supported by Kotlin. | ||||||||||||||||||
|
||||||||||||||||||
Apart from stabilization and general bug fixing, the theme of this release | ||||||||||||||||||
is improving the different DSLs provided by Arrow libraries. Our goal is to | ||||||||||||||||||
empower developers to write more succinct and readable code. | ||||||||||||||||||
|
||||||||||||||||||
* [Simple accumulation in Raise](#simple-accumulation-in-raise) (experimental) | ||||||||||||||||||
* [Additions to Fx](#additions-to-fx) | ||||||||||||||||||
* [Clearer retries for particular exceptions](#clearer-retries-for-particular-exceptions) | ||||||||||||||||||
* [Improved optics](#improved-optics) (breaking changes) | ||||||||||||||||||
* [Pattern matching](#pattern-matching) | ||||||||||||||||||
* [Better support for kotlinx.serialization](#better-support-for-kotlinxserialization) | ||||||||||||||||||
|
||||||||||||||||||
## Simple accumulation in Raise | ||||||||||||||||||
|
||||||||||||||||||
One of the core concepts when working with typed errors is the distinction | ||||||||||||||||||
between fail-first and [accumulation of errors](/learn/typed-errors/working-with-typed-errors/#accumulating-different-computations). Until now, the latter mode | ||||||||||||||||||
required using `parZip` and `parMap`, which sometimes obscure the actual | ||||||||||||||||||
flow of the computation. | ||||||||||||||||||
|
||||||||||||||||||
In Arrow 2.0 we have sprinkled some DSL dust over `Raise`, and now you can | ||||||||||||||||||
write your code in a more linear way. Inside an `accumulate` block (or in | ||||||||||||||||||
general, any `RaiseAccumulate`) you use `by accumulating` to execute some | ||||||||||||||||||
computation keeping all the errors. | ||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
// version with parZip | ||||||||||||||||||
parZip( | ||||||||||||||||||
{ checkOneThing() }, | ||||||||||||||||||
{ checkOtherThing() } | ||||||||||||||||||
) { a, b -> doSomething(a, b) } | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
These examples are using context receivers, and/or omitting quite some details. Is it important for us the examples are real-life, and copy-paste able? |
||||||||||||||||||
|
||||||||||||||||||
// version with accumulate | ||||||||||||||||||
accumulate { | ||||||||||||||||||
val a by accumulating { checkOneThing() } | ||||||||||||||||||
val b by accumulating { checkOtherThing() } | ||||||||||||||||||
doSomething(a, b) | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
This DSL also includes shortcuts for the most common operations, like | ||||||||||||||||||
`bind`ing and accumulating any problem, or checking a single property | ||||||||||||||||||
of some data. | ||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
accumulate { | ||||||||||||||||||
val name by Name(rawName).bindOrAccumulate() | ||||||||||||||||||
ensureOrAccumulate(age >= 18) { UnderAge } | ||||||||||||||||||
Person(name, age) | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
Note that the API may still undergo some change. At this point you need `@OptIn(ExperimentalRaiseAccumulateApi::class)` to allow their usage in your code. | ||||||||||||||||||
|
||||||||||||||||||
## Additions to Fx | ||||||||||||||||||
|
||||||||||||||||||
Writing coroutine-heavy code may become cumbersome over time, especially if | ||||||||||||||||||
one intends to use as much concurrency as possible. Arrow Fx includes a `parZip` | ||||||||||||||||||
function, but not everybody enjoys having so many brackets. | ||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
parZip( | ||||||||||||||||||
{ downloadFile() }, | ||||||||||||||||||
{ loadDataFromDatabase() } | ||||||||||||||||||
) { file, data -> Result(file, data) } | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
The new [`awaitAll` scope](/learn/coroutines/parallel/#await-all-scopes) tries to improve the situation by tweaking the | ||||||||||||||||||
usual `async` mechanism, ensuring that all `Deferred` values are `await`ed | ||||||||||||||||||
once the first one is requested. That means that the previous code behaves | ||||||||||||||||||
identically to the following, that is, the call `file.await()` implicitly | ||||||||||||||||||
awaits every `async` defined up to that point. | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to add the details why this is important for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you have a 1-sentence summary of this I could use? |
||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
awaitAll { | ||||||||||||||||||
val file = async { downloadFile() } | ||||||||||||||||||
val data = async { loadDataFromDatabase() } | ||||||||||||||||||
Result(file.await(), data.await()) | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
We've also improved the STM block by [allowing delegation](/learn/coroutines/stm/#reading-and-writing-concurrent-state) as a means to | ||||||||||||||||||
read or change the value of a `TVar`. | ||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
fun STM.deposit(accVar: TVar<Int>, amount: Int): Unit { | ||||||||||||||||||
val acc by accVar // delegation here | ||||||||||||||||||
val current = acc // implicit 'read' | ||||||||||||||||||
acc = current + amount // implicit 'write' | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
## Clearer retries for particular exceptions | ||||||||||||||||||
|
||||||||||||||||||
Until now, the [`retry` operation](/learn/resilience/retry-and-repeat/) in the Resilience module would capture | ||||||||||||||||||
any `Throwable` exception. From version 2.0 on you can specify a subclass | ||||||||||||||||||
of `Throwable` to be the target for retrying, whereas the rest of | ||||||||||||||||||
exceptions will bubble as usual. | ||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
Schedule.recurs<Throwable>(2) | ||||||||||||||||||
.retry<IllegalArgumentException, _> { ... } | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
The subclass of exceptions must be given as a type argument. | ||||||||||||||||||
Alas, Kotlin does not allow giving only a subset of those, and `retry` | ||||||||||||||||||
has two type parameters (the second one represents the output type of | ||||||||||||||||||
the `Schedule`). Fortunately, you can ask the compiler to infer the | ||||||||||||||||||
second one using `_`. | ||||||||||||||||||
|
||||||||||||||||||
## Improved optics | ||||||||||||||||||
|
||||||||||||||||||
The two **breaking changes** in Arrow 2.0 relate to optics. | ||||||||||||||||||
First of all, the [optics hierarchy](/learn/immutable-data/intro/#many-optics-to-rule-them-all) has been greatly simplified: | ||||||||||||||||||
now we have traversals, optionals, lenses, prisms, and isos, and no more | ||||||||||||||||||
intermediate types. This smaller amount of types means that the type of | ||||||||||||||||||
optic compositions become easier to understand. | ||||||||||||||||||
|
||||||||||||||||||
We have also changed the generation of optics via the compiler plug-in | ||||||||||||||||||
(that is, the `@optics` annotation) with respect to nullable fields. | ||||||||||||||||||
In the 1.x series, a value of type `String?` would be presented as | ||||||||||||||||||
`Optional<T, String>`; this makes impossible to change the value from | ||||||||||||||||||
`null` to an actual `String` using only optics operations. From version | ||||||||||||||||||
2.0, that field is represented as `Lens<T, String?>`. To get the 1.x | ||||||||||||||||||
behavior you should apply `.notNull` after the optic corresponding to | ||||||||||||||||||
the field. | ||||||||||||||||||
|
||||||||||||||||||
One pain point when building [traversals](/learn/immutable-data/traversal/) was the need to provide an | ||||||||||||||||||
argument to `.every`, like `.every(Every.list())`. This new version | ||||||||||||||||||
brings an improved variant that requires no arguments if the type | ||||||||||||||||||
of the `Iterable` is known. Similar improvements have been applied | ||||||||||||||||||
to `.at` and `.index`. | ||||||||||||||||||
|
||||||||||||||||||
## Pattern matching | ||||||||||||||||||
|
||||||||||||||||||
One completely new feature in Arrow 2.0 is the [_pattern matching_ DSL](/learn/immutable-data/matching/). | ||||||||||||||||||
By combining prisms and lenses one can specify a complex shape, and | ||||||||||||||||||
then check whether a value fits into that shape, extracting some | ||||||||||||||||||
pieces of information on the go. The DSL gets quite close to pattern | ||||||||||||||||||
matching found in functional languages like Haskell or Scala. | ||||||||||||||||||
|
||||||||||||||||||
We do not intend this package to be a replacement for `when` expressions, | ||||||||||||||||||
smart casts, and guards already provided by the language. On the other | ||||||||||||||||||
hand, we acknowledge that pattern matching offers advantages when the | ||||||||||||||||||
code needs to inspect very nested data. | ||||||||||||||||||
|
||||||||||||||||||
```kotlin | ||||||||||||||||||
val User.name: String get() = this.matchOrThrow { | ||||||||||||||||||
// Company(name = nm, director = Name(lastName = d)) | ||||||||||||||||||
User.company(Company.name, Company.director(Name.lastName)) then { (nm, d) -> "$nm, att. $d" } | ||||||||||||||||||
// Person(Name(firstName = fn), age if it < 18) | ||||||||||||||||||
User.person(Person.name(Name.firstName), Person.age.suchThat { it < 18 }) then { (fn, _) -> fn } | ||||||||||||||||||
// Person(Name(firstName = fn, lastName = ln)) -> "Sir/Madam $fn $ln" | ||||||||||||||||||
User.person(Person.name(Name.firstName, Name.lastName)) then { (fn, ln) -> "Sir/Madam $fn $ln" } | ||||||||||||||||||
} | ||||||||||||||||||
``` | ||||||||||||||||||
|
||||||||||||||||||
We also provide a new package, [`arrow-match`](/learn/immutable-data/matching/#matching-without-optics), which provides the same | ||||||||||||||||||
pattern matching DSL, but using Kotlin's reflection instead of optics. | ||||||||||||||||||
|
||||||||||||||||||
## Better support for kotlinx.serialization | ||||||||||||||||||
|
||||||||||||||||||
Using Arrow Core data types as part of serialized data requires additional integration. | ||||||||||||||||||
In 1.2.x we started providing compile-time [support for `kotlinx.serialization`](/learn/quickstart/serialization/#kotlinxserialization). | ||||||||||||||||||
From 2.0 on we also provide `ArrowModule` for | ||||||||||||||||||
[contextual serialization](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#contextual-serialization). This is needed, among others, when the data is processed | ||||||||||||||||||
by Ktor. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parZip
andparMap
are used for parallel operations. Did you meanzipOrAccumulate
here?