Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
8ad2c42
reformat the article.
khajavi Apr 22, 2021
496a8c7
split paragraphs into smaller ones.
khajavi Apr 22, 2021
bb52e97
document succeed constructor.
khajavi Apr 22, 2021
065812e
creation of layers from managed resources.
khajavi Apr 22, 2021
3296e45
remove silent to illustrate better types.
khajavi Apr 22, 2021
1260a44
lifting from zio effects.
khajavi Apr 22, 2021
443ebd6
document creation of zlayer by using another services.
khajavi Apr 22, 2021
d54383e
move has and zlayer to a new category called 'contextual types'.
khajavi Apr 24, 2021
c78bdda
document zio environment.
khajavi Apr 25, 2021
eaa756e
document writing a service in oop style.
khajavi Apr 25, 2021
c6aeca5
introduce zlayer and has data types.
khajavi Apr 26, 2021
45553d6
document module pattern 1.0
khajavi Apr 26, 2021
b169738
add an introduction paragraph for defining service in oop style.
khajavi Apr 26, 2021
c0004a2
document module pattern 2.0
khajavi Apr 27, 2021
4c99a1c
generalize service definition steps.
khajavi Apr 27, 2021
018942a
document accessor methods for module pattern 2.0.
khajavi Apr 27, 2021
0962936
complete the Logging service example.
khajavi Apr 27, 2021
675ed1b
document service dependencies in module patttern 1.0.
khajavi Apr 27, 2021
25eee7a
reordering sections.
khajavi Apr 27, 2021
a06f8e6
move related documentation to defining services in zio.
khajavi Apr 28, 2021
22747d5
organize zio environment documentation.
khajavi Apr 28, 2021
65d36c8
split the first step into two.
khajavi Apr 28, 2021
a4a2ade
introduction to services.
khajavi Apr 28, 2021
a69402f
add more stuff into the has document.
khajavi Apr 28, 2021
fe800c7
introduce zlayer constructors and composing layers.
khajavi Apr 28, 2021
a119a6b
remove extra paragraph for has data type.
khajavi Apr 28, 2021
076bddb
document ZLayer creation.
khajavi Apr 28, 2021
08dee76
layer memoization.
khajavi Apr 28, 2021
1b6dc26
cyclic dependencies.
khajavi Apr 28, 2021
f446a51
introduction paragraph for managed resource.
khajavi Apr 28, 2021
d347c1b
adding note about using explicitly Has wrappers.
khajavi Apr 28, 2021
3c59703
document a new section for dependency injection in zio.
khajavi Apr 28, 2021
649a619
building dependency graph by horizontal and vertical composition.
khajavi Apr 28, 2021
ef1023c
add example for composing services.
khajavi Apr 28, 2021
dc1969c
add dependency propagation section.
khajavi Apr 28, 2021
ce9675a
more introduction for provide method.
khajavi Apr 29, 2021
c5f3991
update provideLayer section with a better example.
khajavi Apr 29, 2021
144dd76
document provideSomeLayer and provideCustomLayer.
khajavi Apr 29, 2021
a3de1c7
using "building dependency graph" term is better.
khajavi Apr 29, 2021
f98e816
updating local dependencies.
khajavi Apr 29, 2021
4f3f4c3
add another example for resourceful layers.
khajavi Apr 29, 2021
793a4ea
improve description on provide method.
khajavi Apr 29, 2021
91d1fd9
add a paragraph about programming to interface.
khajavi Apr 29, 2021
5b86365
describe resourceful layers.
khajavi Apr 29, 2021
9586204
introduce better type parameter when describing zlayer data type.
khajavi Apr 29, 2021
d4a09b7
improve introduction section of zlayer.
khajavi Apr 29, 2021
287db6f
emphasize some important features of zlayer.
khajavi Apr 29, 2021
5dea96a
refine zlayer constructor list.
khajavi Apr 29, 2021
dc53e21
remove extra information in describing zlayer.
khajavi Apr 29, 2021
85898e7
remove extra example.
khajavi Apr 29, 2021
fadec00
move composing layers into the zlayer page.
khajavi Apr 29, 2021
e5962f1
fix duplicate paragraph.
khajavi Apr 29, 2021
9a24bbf
remove extra links from zlayer page.
khajavi Apr 29, 2021
3a9254a
add a note about asynchronous property of zlayer.
khajavi Apr 29, 2021
5604287
improve memoization section.
khajavi Apr 29, 2021
da9778f
hidden vs. pass through dependencies.
khajavi Apr 29, 2021
9c92fab
remove introducing zlayer.
khajavi Apr 29, 2021
f91ced5
move has introduction to its page.
khajavi Apr 29, 2021
be1d17a
remove extra links.
khajavi Apr 29, 2021
7bcd0f7
remove some covered sections.
khajavi Apr 29, 2021
887d8fb
enable mdoc checker for type safety.
khajavi Apr 29, 2021
29cea5c
remove extra module and layers article.
khajavi Apr 29, 2021
ddf2e08
document zlayer's type aliases.
khajavi Apr 29, 2021
f209705
use the latest work on zlayer, which fixes the type inferring problem.
khajavi Apr 29, 2021
e6fc220
remove extra comma from json file.
khajavi Apr 30, 2021
f1599c5
enable mdoc for missing code block.
khajavi Apr 30, 2021
7071d24
introduce usage of has data type in module pattern 1.0.
khajavi Apr 30, 2021
3e083bc
document has data type.
khajavi Apr 30, 2021
51c8bd5
reorganize has data type article.
khajavi Apr 30, 2021
cb9f6c1
remove extra stuff from had data type.
khajavi Apr 30, 2021
212b7ac
fix the zlayer link.
khajavi Apr 30, 2021
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
271 changes: 271 additions & 0 deletions docs/datatypes/contextual/has.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
---
id: has
title: "Has"
---

The trait `Has[A]` is used with the ZIO environment to express an effect's dependency on a service of type `A`.

For example,`RIO[Has[Console.Service], Unit]` is an effect that requires a `Console.Service` service.

## Overview
ZIO Wrap services with `Has` data type to:

1. **Combine** multiple services together.
2. **Bind** services into their implementations.

### Combining Services
Two or more `Has[_]` elements can be combined _horizontally_ using their `++` operator:

```scala mdoc:invisible
import zio._

trait Logging
trait RandomInt
```

```scala mdoc:silent:nest
val logger: Has[Logging] = Has(new Logging{})
val random: Has[RandomInt] = Has(new RandomInt{})

// Note the use of the infix `++` operator on `Has` to combine two `Has` elements:
val combined: Has[Logging] with Has[RandomInt] = logger ++ random
```

### Binding Services

The extra power that is given by `Has` is that the resulting data structure is backed by an _heterogeneous map_. `Has` can be thought of as a `Map[K, V]` which keys are _service types_ and values are _service implementations_. from service type to service implementation, that collects each instance that is mixed in so that the instances can be accessed/extracted/modified individually, all while still guaranteeing supreme type safety.

ZIO internally can ask `combined` using `get` method to determine binding configurations:

```scala mdoc:silent:nest
// get back the Logging and RandomInt services from the combined values:
val logger: Logging = combined.get[Logging]
val random: RandomInt = combined.get[RandomInt]
```

These are implementation details. Usually, we don't create a `Has` directly. Instead, we create a `Has` using `ZLayer`.

## Motivation
Some components in an application might depend upon more than one service, so we might need to combine multiple services and feed them to the ZIO Environment. Services cannot directly be combined, they can be combined if they first wrapped in the `Has` data type.

Let's get into this problem and how the `Has` data type, solves this problem:

### Problem
ZIO environment has a `ZIO#provide` which takes an `R` and returns a `ZIO` effect which doesn't require `R` and ready to be run by the `unsafeRun` operation of `Runtime`.

Assume we have two `Logging` and `RandomInt` services:

```scala mdoc:invisible:reset
import zio._
```

```scala mdoc:silent:nest
trait Logging {
def log(line: String): UIO[Unit]
}

trait RandomInt {
def random: UIO[Int]
}
```

We also provided their accessors to their companion object. We just used `ZIO.accessM` to access environment of each service:

```scala mdoc:silent:nest
object Logging {
def log(line: String): ZIO[Logging, Nothing, Unit] = ZIO.accessM[Logging](_.log(line))
}

object RandomInt {
val random: ZIO[RandomInt, Nothing, Int] = ZIO.accessM[RandomInt](_.random)
}
```

Now, we are ready to write our application using these interfaces. We are going to write a simple program which generates a new random number and feed it into the logger:

```scala mdoc:silent:nest
val myApp: ZIO[Logging with RandomInt, Nothing, Unit] =
for {
_ <- Logging.log("Application Started!")
nextInt <- RandomInt.random
- <- Logging.log(s"Random number generated: ${nextInt.toString}")
} yield ()
```

To run this program, we need to implement a live version of `Logging` and `RandomInt` services. So let's implement each of them:

```scala mdoc:silent:nest
val LoggingLive: Logging = new Logging {
override def log(line: String): UIO[Unit] =
ZIO.effectTotal(println(line))
}

val RandomIntLive: RandomInt = new RandomInt {
override def random: UIO[Int] =
ZIO.effectTotal(scala.util.Random.nextInt())
}
```

Great! Now, we are ready to inject these two dependencies into our application `myApp` through `ZIO.provide` function.

```scala mddoc:silent:nest
val mainApp = myApp.provide(???) //What to provide?
```

As the type of `myApp` effect is `ZIO[Logging with RandomInt, Nothing, Unit]`, we should provide an object with a type of `Logging with RandomInt`. Oh! How can we combine `LoggingLive` and `RandomIntLive` objects together? Unfortunately, we don't have a way to combine these two objects to create a required service (`Logging with RandomInt`).

But, there is a workaround, we can throw away these implementations and write a new implementation for an intersection of these two services:

```scala mdoc:silent:nest
val LoggingWithRandomIntLive = new Logging with RandomInt {
override def log(line: String): UIO[Unit] =
ZIO.effectTotal(println(line))

override def random: UIO[Int] =
ZIO.effectTotal(scala.util.Random.nextInt())
}
```

Now, we can provide this implementation into our application:

```scala mdoc:silent:nest
val mainApp: IO[Nothing, Unit] = myApp.provide(LoggingWithRandomIntLive)
```

The `mainApp` doesn't need any environmental services and can be run by using the ZIO Runtime system:

```scala mdoc:silent:nest
Runtime.default.unsafeRun(mainApp)
```

But this workaround is not perfect, because every time we are writing an application, we need to provide a specific implementation for its requirement. This is overwhelming.

We need to implement each of each service separately and at the end of the day, combine them and provide that to our application. This is where the `Has[_]` wrapper data type comes into play.

### Solution

`Has[_]` data type enables us to combine different services and provide them to the ZIO Environment. Let's solve the previous problem by using the `Has` wrapper.

First, we should change the accessor methods to return us an effect which requires services wrapped into the `Has` data type:

```scala mdoc:silent:nest
object Logging {
def log(line: String): ZIO[Has[Logging], Nothing, Unit] =
ZIO.serviceWith[Logging](_.log(line))
}

object RandomInt {
val random: ZIO[Has[RandomInt], Nothing, Int] =
ZIO.serviceWith[RandomInt](_.random)
}
```

`ZIO.serviceWith` is accessor method like `ZIO.accessM`, it accesses the specified service in the environment of effect, but it returns a ZIO effect which requires a service wrapped in `Has[_]` data type.

We should refactor our application to represent the correct types.

```scala mdoc:silent:nest
val myApp: ZIO[Has[Logging] with Has[RandomInt], Nothing, Unit] =
for {
_ <- Logging.log("Application Started!")
nextInt <- RandomInt.random
- <- Logging.log(s"Random number generated: ${nextInt.toString}")
} yield ()
```

Now, our application is a ZIO effect which requires `Has[Logging] with Has[RandomInt]` services. Let's combine implementation of these two services using Has data type:

```scala mdoc:silent:nest
val combined: Has[Logging] with Has[RandomInt] = Has(LoggingLive) ++ Has(RandomIntLive)
```

Let's feed the combined services into our application:

```scala mdoc:silent:nest
val effect: IO[Nothing, Unit] = myApp.provide(combined)
zio.Runtime.default.unsafeRun(effect)
```

That is how the `Has` data type helps us to combine services. The previous example was just for demonstrating purposes, and we rarely create `Has` data type directly. Instead, we create a `Has` via `ZLayer`.

Whenever we lift a service value into `ZLayer` with the `ZLayer.succeed` constructor or `toLayer`, ZIO will wrap our service with `Has` data type.

Let's implement `Logging` and `RandomInt` services:

```scala mdoc:silent:nest
case class LoggingLive() extends Logging {
override def log(line: String): UIO[Unit] =
ZIO.effectTotal(println(line))
}

case class RandomIntLive() extends RandomInt {
override def random: UIO[Int] =
ZIO.effectTotal(scala.util.Random.nextInt())
}
```

Now, we can lift these two implementations into the `ZLayer`. The `ZLayer` will wrap our services into the `Has[_]` data type:

```scala mdoc:invisible:reset
import zio._
trait Logging {
def log(line: String): UIO[Unit]
}

trait RandomInt {
def random: UIO[Int]
}

object Logging {
def log(line: String): ZIO[Has[Logging], Nothing, Unit] =
ZIO.serviceWith[Logging](_.log(line))
}

object RandomInt {
val random: ZIO[Has[RandomInt], Nothing, Int] =
ZIO.serviceWith[RandomInt](_.random)
}

val myApp: ZIO[Has[Logging] with Has[RandomInt], Nothing, Unit] =
for {
_ <- Logging.log("Application Started!")
nextInt <- RandomInt.random
- <- Logging.log(s"Random number generated: ${nextInt.toString}")
} yield ()

case class LoggingLive() extends Logging {
override def log(line: String): UIO[Unit] =
ZIO.effectTotal(println(line))
}

case class RandomIntLive() extends RandomInt {
override def random: UIO[Int] =
ZIO.effectTotal(scala.util.Random.nextInt())
}
```

```scala mdoc:silent
object LoggingLive {
val layer: URLayer[Any, Has[Logging]] =
(LoggingLive.apply _).toLayer
}

object RandomIntLive {
val layer: URLayer[Any, Has[RandomInt]] =
(RandomIntLive.apply _).toLayer
}
```

Now, when we combine multiple layers together, these services will combined via `with` intersection type:

```scala mdoc:silent:nest
val myLayer: ZLayer[Any, Nothing, Has[Logging] with Has[RandomInt]] =
LoggingLive.layer ++ RandomIntLive.layer
```

Finally, when we provide our layer into the ZIO effect, ZIO can access the binding configuration and extract each service. ZIO does internally these pieces of wiring machinery, we don't care about the implementation detail:

```scala mdoc:nest
val mainApp: ZIO[Any, Nothing, Unit] = myApp.provideLayer(myLayer)
```

Loading