Skip to content
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

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
263b5b0
Mention ArrowModule in serialization and ktor sections
tKe May 14, 2024
b9c81c0
remove kotlin tag on source blocks to avoid knit
tKe May 17, 2024
116718b
Arrow 2.0 release notes
serras May 29, 2024
c28c5a6
Smaller updates to 2.0
serras May 29, 2024
54dec11
Knit
serras May 30, 2024
36a8df9
AwaitScope
serras May 30, 2024
b267808
Pattern matching DSL
serras May 31, 2024
fbfe9d1
More on pattern matching
serras Jun 4, 2024
183ffe3
Finish pattern matching section
serras Oct 1, 2024
a58845f
Talk about accumulating
serras Oct 1, 2024
de555b1
Add information about new retry
serras Oct 3, 2024
58844fd
Merge branch 'document-arrow-serializer-module' into serras/arrow-2-0…
serras Oct 3, 2024
928d136
Merge branch 'serras/arrow-2-0' into serras/arrow-2-0-everything
serras Oct 3, 2024
ebdee04
Merge branch 'serras/updates-to-2-0' into serras/arrow-2-0-everything
serras Oct 3, 2024
a426fe6
Merge branch 'serras/pattern-matching' into serras/arrow-2-0-everything
serras Oct 3, 2024
d935b65
Links + Knit
serras Oct 3, 2024
0279d12
Update to 2.0.0-alpha
serras Oct 3, 2024
1b12c8a
Merge branch 'serras/2.0-alpha.4' into serras/arrow-2-0-everything
serras Oct 3, 2024
fde0980
Small fix to STM code
serras Oct 3, 2024
b9fc0e6
Use Knit in matching examples
serras Oct 4, 2024
71cc9f0
Accumulation
serras Oct 5, 2024
f45caad
Remove mentions of pattern matching
serras Oct 16, 2024
38e9cf2
Suggestions by @nomisRev
serras Oct 16, 2024
744b11c
Merge branch 'main' into serras/arrow-2-0-everything
serras Oct 16, 2024
6248e7e
Update to 2.0.0-beta.1
serras Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions content/blog/2024-10-04-arrow-2-0.md
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parZip and parMap are used for parallel operations. Did you mean zipOrAccumulate here?


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) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
parZip(
{ checkOneThing() },
{ checkOtherThing() }
) { a, b -> doSomething(a, b) }
zipOrAccumulate(
{ checkOneThing() },
{ checkOtherThing() }
) { a, b -> doSomething(a, b) }

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add the details why this is important for Raise, or how not using this could result in hanging?

Copy link
Member Author

Choose a reason for hiding this comment

The 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.
39 changes: 35 additions & 4 deletions content/docs/learn/coroutines/parallel.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,37 @@ if the amount of elements in the collection is too significant. To fight against
problem, Arrow provides a version of `parMap` with an additional parameter that
tells how many computations should be dispatched in parallel.

### Await-all scopes

Although `parZip` gives the most high-level view of the code, clearly specifying
which tasks are independent of each other, it has the drawback of requiring a particular
style of writing your computations. Arrow provides another tool based on `async`,
where the code is written using the usual `async`/`.await()` idioms.

<!--- INCLUDE
import arrow.fx.coroutines.parZip
import arrow.fx.coroutines.await.awaitAll
typealias UserId = Int
data class User(val name: String, val avatar: String)
suspend fun getUserName(id: UserId): String = "$id-name"
suspend fun getAvatar(id: UserId): String = "$id-avatar"
-->
```kotlin
suspend fun getUser(id: UserId): User = awaitAll {
val name = async { getUserName(id) }
val avatar = async { getAvatar(id) }
User(name.await(), avatar.await())
}
```
<!--- KNIT example-parallel-03.kt -->

As the name suggests, within this `awaitAll` block, every time you call `.await()`
_all_ of the `async` computations that were registered until that point are
awaited. If any of those throws an exception, the whole block is canceled, as
per the rules of structured concurrency. In general, writing a sequence of independent
`async` computations within `awaitAll` is equivalent to giving those computations
as arguments to `parZip`.

### Flows

The [`parMap`](https://apidocs.arrow-kt.io/arrow-fx-coroutines/arrow.fx.coroutines/par-map.html)
Expand Down Expand Up @@ -113,7 +144,7 @@ suspend fun file(server1: String, server2: String) =
{ downloadFrom(server2) }
).merge()
```
<!--- KNIT example-parallel-03.kt -->
<!--- KNIT example-parallel-04.kt -->

The example above shows a typical pattern combined with `raceN`.
The result of the function above is `Either<A, B>`, with each type
Expand Down Expand Up @@ -156,7 +187,7 @@ suspend fun example() {
println(triple)
}
```
<!--- KNIT example-parallel-04.kt -->
<!--- KNIT example-parallel-05.kt -->

```text
Sleeping for 500 milliseconds ...
Expand Down Expand Up @@ -210,7 +241,7 @@ suspend fun example() {
println(res)
}
```
<!--- KNIT example-parallel-05.kt -->
<!--- KNIT example-parallel-06.kt -->

In the output, we can see that tasks `1` and `3` started, but `2` _raised_ an error that triggered the cancellation of the other two tasks.
After tasks `1` and `3` are canceled, we see that the result of `raise` is returned and prints the error message.
Expand Down Expand Up @@ -255,7 +286,7 @@ suspend fun example() {
println(res)
}
```
<!--- KNIT example-parallel-06.kt -->
<!--- KNIT example-parallel-07.kt -->

The example transforms, or maps, every element of an `Iterable` `[1, 2, 3, 4]` in _parallel_ using `parMap` and `failOnEven`.
Since `failOnEven` raises an error when the `Int` is even, it fails for inputs 2 and 4, and the other two coroutines are canceled.
Expand Down
33 changes: 31 additions & 2 deletions content/docs/learn/coroutines/stm.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,35 @@ One additional guarantee of STM is that the _whole_ transaction is executed
atomically. That means we can modify several `TVar`s in one transaction,
and we'll never observe an intermediate state.

:::tip Delegated transactional properties

From version 2.0, you can use [property delegation](https://kotlinlang.org/docs/delegated-properties.html)
to access a `TVar`. That way you don't need explicit `read` or `write`,
they become implicit in the syntax.

<!--- INCLUDE
import arrow.fx.stm.atomically
import arrow.fx.stm.TVar
import arrow.fx.stm.STM
-->

```kotlin
fun STM.deposit(accVar: TVar<Int>, amount: Int): Unit {
var acc by accVar // property delegation
val current = acc // implicit 'read'
acc = current + amount // implicit 'write'
// or simply, acc = acc + amount
}
```

<!--- INCLUDE
suspend fun example() { }
-->
<!--- KNIT example-stm-02.kt -->
<!--- TEST assert -->

:::

### Other STM data structures

The following types are built upon `TVar`s and provided out of the box with Arrow:
Expand Down Expand Up @@ -167,7 +196,7 @@ suspend fun example() = coroutineScope {
acc2.unsafeRead() shouldBe 350
}
```
<!--- KNIT example-stm-02.kt -->
<!--- KNIT example-stm-03.kt -->
<!--- TEST assert -->

`retry` can be used to implement a lot of complex transactions,
Expand Down Expand Up @@ -220,7 +249,7 @@ suspend fun example() {
atomically { transaction(v) } shouldBe 5
}
```
<!--- KNIT example-stm-03.kt -->
<!--- KNIT example-stm-04.kt -->
<!--- TEST assert -->

## Exceptions
Expand Down
Loading