Skip to content

Commit

Permalink
Release 0.5.0
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Oct 7, 2024
1 parent f3f27df commit a1c3e7c
Show file tree
Hide file tree
Showing 31 changed files with 548 additions and 412 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ the project!
To test Ox, use the following dependency, using either [sbt](https://www.scala-sbt.org):

```scala
"com.softwaremill.ox" %% "core" % "0.4.0"
"com.softwaremill.ox" %% "core" % "0.5.0"
```

Or [scala-cli](https://scala-cli.virtuslab.org):

```scala
//> using dep "com.softwaremill.ox::core:0.4.0"
//> using dep "com.softwaremill.ox::core:0.5.0"
```

Documentation is available at [https://ox.softwaremill.com](https://ox.softwaremill.com), ScalaDocs can be browsed at [https://javadoc.io](https://www.javadoc.io/doc/com.softwaremill.ox).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ Actors in Ox enable invoking methods on an object serially, keeping the behavior
invocation. That is, even though invocations may happen from multiple threads, they are guaranteed to happen one after
the other, not concurrently.

Actor invocations are fully type-safe, with minimal overhead. They use [channels](index.md) and
[scopes](../structured-concurrency/fork-join.md) behind the scenes.
Actor invocations are fully type-safe, with minimal overhead. They use [channels](streaming/channels.md) and
[scopes](structured-concurrency/fork-join.md) behind the scenes.

One of the use-cases is integrating with external APIs, which are represented by an object containing mutable state.
Such integrations must be protected and cannot be accessed by multiple threads concurrently.
Expand Down Expand Up @@ -37,12 +37,11 @@ class Stateful:
counter += delta
counter

supervised {
supervised:
val ref = Actor.create(new Stateful)

ref.ask(_.increment(5)) // blocks until the invocation completes
ref.ask(_.increment(4)) // returns 9
}
```

If a non-fatal exception is thrown by the invocation, it's propagated to the caller, and the actor continues processing
Expand Down Expand Up @@ -75,13 +74,12 @@ class Stateful:
def work(howHard: Int): Unit = throw new RuntimeException("boom!")
def close(): Unit = println("Closing")

supervised {
supervised:
val ref = Actor.create(new Stateful, Some(_.close()))

// fire-and-forget, exception causes the scope to close
ref.tell(_.work(5))

// preventing the scope from closing
never
}
```
8 changes: 4 additions & 4 deletions generated-doc/out/basics/direct-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@ of use and performance of imperative programming. This is a departure from a pur
[cats-effect](https://github.com/typelevel/cats-effect) or [ZIO](https://zio.dev), in favor of running effectful
computations imperatively.

Note, however, that in all other aspects direct style Scala remains functional: using immutable data structures,
Note, however, that in all other aspects direct-style Scala remains functional: using immutable data structures,
higher order functions, typeclasses, restricting effects, separating code and data, favoring function composition, etc.

Ox uses the above mentioned virtual threads in Java 21 to implement a safe approach to concurrency, combined with
Go-like channels for inter-thread communication. Moreover, Ox supports and proposes an approach to error handling, along
with multiple utility functions providing safe resiliency, resource management, scheduling and others.

The overarching goal of Ox is enabling safe direct style programming using the power of the Scala 3 language. While
The overarching goal of Ox is enabling safe direct-style programming using the power of the Scala 3 language. While
still in its early days, a lot of functionality is available in ox today!

## Other direct style Scala projects
## Other direct-style Scala projects

The wider goal of direct style Scala is enabling teams to deliver working software quickly and with confidence. Our
The wider goal of direct-style Scala is enabling teams to deliver working software quickly and with confidence. Our
other projects, including [sttp client](https://sttp.softwaremill.com) and [tapir](https://tapir.softwaremill.com),
also include integrations directly tailored towards direct style.
2 changes: 1 addition & 1 deletion generated-doc/out/basics/error-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Ox uses two channels, through which errors can be signalled:

Exceptions are always appropriately handled by computation combinators, such as the high-level concurrency operations
[`par`](../high-level-concurrency/par.md) and [`race`](../high-level-concurrency/race.md), as well as by
[scopes](../structured-concurrency/fork-join.md) and [channels](../channels/index.md).
[scopes](../structured-concurrency/fork-join.md) and [streams](../streaming/index.md).

The general rule for computation combinators is that using them should throw exactly the same exceptions, as if the
provided code was executed without them. That is, no additional exceptions might be thrown, and no exceptions are
Expand Down
20 changes: 10 additions & 10 deletions generated-doc/out/basics/quick-example.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ variants and functionalities!
import ox.*
import ox.either.ok
import ox.channels.*
import ox.flow.Flow
import ox.resilience.*
import ox.scheduling.*
import scala.concurrent.duration.*
Expand All @@ -23,27 +24,26 @@ val result2: Either[Throwable, Int] = either.catching(timeout(1.second)(computat

// structured concurrency & supervision
supervised {
forkUser {
forkUser:
sleep(1.second)
println("Hello!")
}
forkUser {

forkUser:
sleep(500.millis)
throw new RuntimeException("boom!")
}
}
// on exception, ends the scope & re-throws

// retry a computation
def computationR: Int = ???
retry(RetryConfig.backoff(3, 100.millis, 5.minutes, Jitter.Equal))(computationR)

// create channels & transform them using high-level operations
supervised {
Source.iterate(0)(_ + 1) // natural numbers
.transform(_.filter(_ % 2 == 0).map(_ + 1).take(10))
.foreach(n => println(n.toString))
}
// create a flow & transform using high-level operations
Flow.iterate(0)(_ + 1) // natural numbers
.filter(_ % 2 == 0)
.map(_ + 1)
.take(10)
.runForeach(n => println(n.toString))

// select from a number of channels
val c = Channel.rendezvous[Int]
Expand Down
6 changes: 3 additions & 3 deletions generated-doc/out/basics/start-here.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

```scala
// sbt dependency
"com.softwaremill.ox" %% "core" % "0.4.0"
"com.softwaremill.ox" %% "core" % "0.5.0"

// scala-cli dependency
//> using dep com.softwaremill.ox::core:0.4.0
//> using dep com.softwaremill.ox::core:0.5.0
```

## Scope of the Ox project
Expand Down Expand Up @@ -52,7 +52,7 @@ We offer commercial support for Ox and related technologies, as well as developm
* [Two types of futures](https://softwaremill.com/two-types-of-futures/)
* [Supervision, Kafka and Java 21: what’s new in Ox](https://softwaremill.com/supervision-kafka-and-java-21-whats-new-in-ox/)
* [Designing a (yet another) retry API](https://softwaremill.com/designing-a-yet-another-retry-api/)
* [Handling errors in direct style Scala](https://softwaremill.com/handling-errors-in-direct-style-scala/)
* [Handling errors in direct-style Scala](https://softwaremill.com/handling-errors-in-direct-style-scala/)

## Inspiration & building blocks

Expand Down
19 changes: 9 additions & 10 deletions generated-doc/out/best-practices.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Best practices

While working on ox and integrating ox into our other open-source projects, we found a couple of patterns and best
While working on Ox and integrating ox into our other open-source projects, we found a couple of patterns and best
practices which might be useful for anybody starting their journey with direct-style Scala and ox.

## Make scopes as small as possible

If you end up using concurrency scopes such as `supervised`, make sure that their lifetime is as short as possible. In
some cases it might be necessary to start a "global" scope (e.g. for application-wide, long-running tasks), but even
if so, don't let the global scope leak to any other parts of your code, and isolate its usage, e.g. using
[actors](channels/actors.md).
[actors](actors.md).

For all other tasks, create short-lived scopes, which handle a single request, message from a queue or a single job
instance.

## Integrate with callback-based APIs using channels

Callback-based APIs, including "reactive" ones, are by their nature non-structured, and don't play well with
structured concurrency. For such cases, [channels](channels/index.md) are an ideal tool. Sending or receiving to/from
structured concurrency. For such cases, [channels](streaming/channels.md) are an ideal tool. Sending or receiving to/from
a channel doesn't require any context, and can be done from any thread. On the other hand, processing the data that
is on the channel often involves concurrency and creating thread, which can be then done in a structured way.

Expand All @@ -30,15 +30,14 @@ of structured concurrency is to localise thread creation as much as possible, an
thread as an effect. `using Ox` partially circumvents this guarantee, hence use this with caution, and pay attention
not to pass it through several layers of method calls, which might make the code hard to understand.

## Use `mapAsView` instead of `map`
## Use flows instead of channels

The `map` and `filter` channel operations must be run within a concurrency scope, and perform their processing on a
freshly created fork. While this still performs well, as creating virtual threads & channels is cheap, it might incur
an unnecessary overhead. If the mapping/filtering logic doesn't have effects, and isn't blocking, it's often sufficient
to create views of channels.
Transforming channels directly might lead to excessive concurrency, as each transformation typically starts a
background fork, processing the data and sending it to a new channel. While this still performs well, as creating
virtual threads & channels is cheap, it might incur an unnecessary overhead.

In a channel view, the processing logic is run lazily, on the thread that performs the `receive` operation. Channel
views can be created using `.mapAsView`, `.filterAsView`, and `.collectAsView`.
Instead, you can use [flows](streaming/flows.md) and their high-level API, which allows inserting asynchronous
boundaries when necessary, but otherwise runs the subsequent processing stages on the same thread.

## Avoid returning `Fork`

Expand Down
4 changes: 0 additions & 4 deletions generated-doc/out/channels/backpressure.md

This file was deleted.

36 changes: 0 additions & 36 deletions generated-doc/out/channels/channel-closed.md

This file was deleted.

30 changes: 0 additions & 30 deletions generated-doc/out/channels/discharging.md

This file was deleted.

9 changes: 0 additions & 9 deletions generated-doc/out/channels/errors.md

This file was deleted.

30 changes: 0 additions & 30 deletions generated-doc/out/channels/index.md

This file was deleted.

Loading

0 comments on commit a1c3e7c

Please sign in to comment.