Skip to content

Commit

Permalink
Initial edit pass on State, up to "Unreliable Effects" #bruce #time 40m
Browse files Browse the repository at this point in the history
  • Loading branch information
Bruce Eckel committed Jul 23, 2024
1 parent e344737 commit 4f94767
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 29 deletions.
7 changes: 4 additions & 3 deletions Chapters/02_Introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,16 +209,17 @@ Basically, if you have to read the documentation to figure out what happens when

We hope you are thinking, "Isn't that what we've been talking about? Isn't that just an Effect?"

The difference between a Side Effect and an Effect is that a side effect "just happens," while an Effect is something that can be managed.
There's an important difference between a Side Effect and an Effect.
A Side Effect "just happens" but an Effect can be managed.
For example, suppose you write to the console using the standard console library provided by your language.
That library was not designed for Effect Systems, so we don't have any way to put a box around it and control it like we do with Effects.
When you write to the console, the surroundings change (output appears on the console) and there's nothing we can do about it.
When you write to the console, the surroundings change (output appears on the console), and there's nothing we can do.
That's a Side Effect.

For this reason, we must use special libraries from the Effect System when performing some kinds of Effects such as console I/O.
Those libraries are written to keep track of and manage the Effects.

The simple answer is that Side Effects are un-managed and Effects are managed.
The simple answer is that Side Effects are unmanaged and Effects are managed.

### `Unit` and Effects

Expand Down
53 changes: 27 additions & 26 deletions Chapters/08_State.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,28 @@

Functional programmers often sing the praises of immutability.
The advantages are real and numerous.
However, it is easy to find situations that are intrinsically mutable.
However, programmers commonly encounter situations that are intrinsically mutable.
For example:

- How many people are currently inside a building?
- How much fuel is in your car?
- How much money is in your bank account?

Rather than avoiding mutability entirely, we want to avoid unprincipled, unsafe mutability.
If we codify and enumerate everything that we need from Mutability, then we can wield it safely.
Required Operations:
We can't live without mutability, but we want to avoid unprincipled, unsafe mutability.
If we codify and enumerate everything we need from Mutability, we can wield it safely.
When working with a mutable value, we need to:

- Update the value
- Read the current value

Both of these operations are Effects.
In order to confidently use them, we need certain guarantees about the behavior:
Both operations are Effects.
To safely perform these operations, we need two guarantees:

- The underlying value cannot be changed during a read
- Multiple writes cannot happen concurrently, which would result in lost updates
- The underlying value cannot change while it is being read.
- Multiple writes cannot happen concurrently. This would lose updates.

Less obviously, we must also create the Mutable reference itself.
We are changing the world, by creating a space that we can manipulate.
We are changing the world, by creating a space we can manipulate.
To meet our guarantees, we wrap the mutable value inside an Effect.

## Unreliable State

Expand Down Expand Up @@ -55,13 +56,14 @@ def run =
unreliableCounting
```

Due to the unpredictable nature of shared mutable state, we do not know exactly what the final count above is.
Each time we publish a copy of this book, the code is re-executed and a different wrong result is generated.
However, conflicts are extremely likely, so some of our writes get clobbered by others, and we end up with less than the expected 100,000.
Ultimately, we lose information with this approach.
Shared mutable state is unpredictable.
We cannot know the final value of `counter` inside `unreliableCounting`.
Indeed, every time we regenerate this book, the above code runs and produces a different (incorrect) result.
Because conflicts are extremely likely, some of our writes get clobbered by others, yielding less than the expected 100,000.
Unsafe mutability loses information.

Performing our Side Effects inside ZIO's does not magically make them safe.
We must fully embrace the ZIO components, utilizing `Ref` for correct mutation.
Performing Side Effects inside a ZIO does not make them safe.
For safety, we use `Ref` for mutable values.

## Reliable State

Expand All @@ -88,17 +90,16 @@ def run =
reliableCounting
```

Now we can say with full confidence that our final count is 100000.
Additionally, these updates happen _without blocking_.
This is achieved through a strategy called "Compare & Swap", which we will not cover in detail.
With `Ref`, any changes to `counter` are completely reliable, and our final count is always 100000.
Updates happen _without blocking_, using a technique called _compare and swap_ (you can learn about it [here](https://effectorientedprogramming.com/resources/zio/compare-and-swap.md)).

## Unreliable Effects

Although there are significant advantages; a basic `Ref` is not the solution for everything.
A basic `Ref` is not the solution for everything.
We can only pass pure functions into `update`.
The API of the plain Atomic `Ref` steers you in the right direction by not accepting `ZIO`s as parameters to any of its methods.
To demonstrate why this restriction exists, we will deliberately undermine the system by sneaking in a Side Effect.
First, we will create a helper function that imitates a long-running calculation.
The API of the plain Atomic `Ref` steers you in the right direction by preventing `ZIO` method parameters.
To demonstrate why this restriction exists, we deliberately undermine the system by sneaking in a Side Effect.{i: "Side Effect"}
We start with a helper function to imitate a long-running calculation:

```scala 3 mdoc:silent
import zio.*
Expand All @@ -109,15 +110,15 @@ def expensiveCalculation() =
35
```

Our Side Effect will be a mock alert that is sent anytime our count is updated:
The Side Effect is a mock alert, sent when our count is updated:

```scala 3 mdoc:silent
import zio.*
import zio.direct.*

def sendNotification() =
println:
"Alert: updating count!"
"Alert: Updating Count!"
```

```scala 3 mdoc:silent
Expand Down Expand Up @@ -148,7 +149,7 @@ def run =
```

What is going on?!
Previously, we were losing updates because of unsafe mutability.
Previously, we lost updates because of unsafe mutability.
Now, we have the opposite problem!
We are sending far more alerts than intended, even though we can see that our final count is 4.

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ plugins:
'resources/zio/docs.md': 'https://zio.dev/reference/'
'resources/zio/whats-functional-programming-all-about.md': 'https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAbout.html'
'resources/zio/zio-config-docs-index.md': 'https://github.com/zio/zio-config/blob/master/docs/index.md'
'resources/zio/compare-and-swap.md':`https://en.wikipedia.org/wiki/Compare-and-swap`

0 comments on commit 4f94767

Please sign in to comment.