scoped
Use case
Scoped uses IOLocal to share context through an application as documented here
(from the documentation)
┌────────────┐ ┌────────────┐
fork │ Fiber B │ update(_ + 1) │ Fiber B │
┌─────►│ (local 42) │──────────────►│ (local 43) │
│ └────────────┘ └────────────┘
┌────────────┐─┘ ┌────────────┐
│ Fiber A │ update(_ - 1) │ Fiber A │
│ (local 42) │────────────────────────────────────►│ (local 41) │
└────────────┘─┐ └────────────┘
│ ┌────────────┐ ┌────────────┐
│ fork │ Fiber C │ update(_ + 2) │ Fiber C │
└─────►│ (local 42) │──────────────►│ (local 44) │
└────────────┘ └────────────┘
One example use case for this is passing around transaction ids where you can set them at the start and any further effects are able to access.
Example
Lets start by creating a datatype we want to be shared around
opaque type TransactionId = String
object TransactionId:
def fromString(str: String): TransactionId = str
We can wrap it up and give it some useful name
import is.ashley.scoped.Scoped
type Transactional[F[_]] = Scoped[F, TransactionId]
object Transactional:
def apply[F[_]](using ev: Transactional[F]): Transactional[F] = ev
And now a service that will be using our new datatype, here we can use Transactional as a constraint on F[_] ensuring
that we have the value set in scope.
class MyService[F[_]: Transactional]:
def doSomething: IO[Unit] =
for
txid <- Scoped[F, TransactionId].get // txid = "not-set"
_ <- Transactional[F].scope("123").use(new MyOtherService)
yield ()
Another service using Transactional this service is called by the .use above and has had the TransactionId value changed.
class MyOtherService[F[_]: Transactional]:
def doOtherThing: IO[Unit] =
for
txid <- Transactional[F].get // txid = "123"
...
yield ()
In order to setup the scoping we create a TransactionId here and hand it to Scoped.fromIOLocal we can then use this to meet
our Transactional constraint further down the stack.
Scoped.fromIOLocal(TransactionId.fromString("not-set")).flatMap {
implicit transactionalScope: Transactional[IO] =>
val service = new MyService[IO]
service.doSomething
}