Skip to content

Commit

Permalink
Upgrade all tutorial to latest versions (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
gcanti authored Jul 7, 2023
1 parent f57e8fc commit 44933fa
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 262 deletions.
45 changes: 45 additions & 0 deletions pages/docs/essentials/pipeline.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,51 @@ const program = pipe(
console.log(Effect.runSync(program)) // Output: "Result is: 6"
```

## The pipe method

Effect provides a `pipe` method that works similarly to the `pipe` method found in [`rxjs`](https://rxjs.dev/api/index/function/pipe). This method allows you to chain multiple operations together, making your code more concise and readable.

Here's how the `pipe` **method** works:

```ts
const result = effect.pipe(func1, func2, ..., funcN)
```

This is equivalent to using the `pipe` **function** like this:

```ts
const result = pipe(effect, func1, func2, ..., funcN)
```

The `pipe` method is available on all effects and many other data types, eliminating the need to import the `pipe` function from the `@effect/data/Function` module and saving you some keystrokes.

Let's rewrite the previous example using the `pipe` method:

```ts {16}
import * as Effect from "@effect/io/Effect"

const increment = (x: number) => x + 1

function divide(a: number, b: number): Effect.Effect<never, Error, number> {
if (b === 0) {
return Effect.fail(new Error("Cannot divide by zero"))
}
return Effect.succeed(a / b)
}

const foo = Effect.succeed(10)
const bar = Effect.succeed(2)

// Effect<never, Error, string>
const program = Effect.all(foo, bar).pipe(
Effect.flatMap(([a, b]) => divide(a, b)),
Effect.map((n1) => increment(n1)),
Effect.map((n2) => `Result is: ${n2}`)
)

console.log(Effect.runSync(program)) // Output: "Result is: 6"
```

## Cheatsheet

Let's summarize the transformation functions we have seen so far:
Expand Down
19 changes: 8 additions & 11 deletions pages/docs/tutorial/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,14 @@ Let's start with a simple example of how to read configuration from environment
<Tab>

```ts filename="primitives.ts" showLineNumbers
import { pipe } from "@effect/data/Function"
import * as Effect from "@effect/io/Effect"
import * as Config from "@effect/io/Config"

// Effect<never, ConfigError, void>
const program = pipe(
Effect.all(
Effect.config(Config.string("HOST")),
Effect.config(Config.float("PORT"))
),
const program = Effect.all(
Effect.config(Config.string("HOST")),
Effect.config(Config.float("PORT"))
).pipe(
Effect.flatMap(([host, port]) =>
Effect.sync(() => console.log(`Application started: ${host}:${port}`))
)
Expand All @@ -49,7 +47,7 @@ If we run this application we will get the following output:
```bash filename="Terminal" {3-6}
ts-node primitives.ts

/path/to/primitives.ts:8
/path/to/primitives.ts:6
Effect.config(Config.string("HOST")),
^
(Missing data at HOST: "Expected HOST to exist in the process context")
Expand Down Expand Up @@ -144,7 +142,7 @@ If we use this customized configuration in our application:
<Tabs>
<Tab>

```ts filename="HostPort.ts" {20}
```ts filename="HostPort.ts" {19}
import { pipe } from "@effect/data/Function"
import * as Config from "@effect/io/Config"
import * as Effect from "@effect/io/Effect"
Expand All @@ -163,8 +161,7 @@ export const config: Config.Config<HostPort> = pipe(
)

// Effect<never, ConfigError, void>
export const program = pipe(
Effect.config(config),
export const program = Effect.config(config).pipe(
Effect.flatMap((hostPort) =>
Effect.sync(() => console.log(`Application started: ${hostPort.url}`))
)
Expand Down Expand Up @@ -221,9 +218,9 @@ So far, we have learned how to define configurations in a top-level manner, whet
Let's assume we have a `ServiceConfig` data type that consists of two fields: `hostPort` and `timeout`.

```ts filename="ServiceConfig.ts"
import { pipe } from "@effect/data/Function"
import * as HostPort from "./HostPort"
import * as Config from "@effect/io/Config"
import { pipe } from "@effect/data/Function"

class ServiceConfig {
constructor(readonly hostPort: HostPort.HostPort, readonly timeout: number) {}
Expand Down
12 changes: 7 additions & 5 deletions pages/docs/tutorial/context-management/layers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ we can provide it to our program to satisfy the program's requirements using `Ef
<Tabs>
<Tab>

```ts filename="main.ts" {28}
```ts filename="main.ts" {30}
import { pipe } from "@effect/data/Function"
import * as Layer from "@effect/io/Layer"
import { FlourLive, SugarLive } from "./Ingredients"
Expand All @@ -316,11 +316,13 @@ const MainLive = pipe(
)

// Effect<Recipe, never, void>
const program = pipe(
Recipe,
const program = Recipe.pipe(
Effect.flatMap((recipe) => recipe.steps),
Effect.flatMap((steps) =>
Effect.forEachDiscard(steps, (step) => Effect.logInfo(step))
Effect.forEach(steps, (step) => Effect.log(step), {
concurrency: "unbounded",
discard: true,
})
)
)

Expand Down Expand Up @@ -361,7 +363,7 @@ const program = Effect.gen(function* (_) {
const recipe = yield* _(Recipe)
const steps = yield* _(recipe.steps)
for (const step of steps) {
yield* _(Effect.logInfo(step))
yield* _(Effect.log(step))
}
})

Expand Down
71 changes: 29 additions & 42 deletions pages/docs/tutorial/context-management/services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ Now that we have our service interface defined, let's see how we can use it by b

```ts
// Effect<Random, never, void>
const program = pipe(
Random,
const program = Random.pipe(
Effect.flatMap((random) => random.next()),
Effect.flatMap((randomNumber) => Effect.logInfo(`${randomNumber}`))
Effect.flatMap((randomNumber) => Effect.log(`${randomNumber}`))
)
```

Expand All @@ -87,7 +86,7 @@ We then use the `Effect.logInfo` utility to log the generated random number.
const program = Effect.gen(function* (_) {
const random = yield* _(Random)
const randomNumber = yield* _(random.next())
return yield* _(Effect.logInfo(`${randomNumber}`))
return yield* _(Effect.log(`${randomNumber}`))
})
```

Expand Down Expand Up @@ -129,22 +128,20 @@ In order to provide an actual implementation of the `Random` service, we can uti

```ts
// Effect<never, never, void>
const runnable = pipe(
const runnable = Effect.provideService(
program,
Effect.provideService(
Random,
Random.of({
next: () => Effect.succeed(Math.random()),
})
)
Random,
Random.of({
next: () => Effect.succeed(Math.random()),
})
)

Effect.runSync(runnable)
// Output: ...more infos... message=0.8241872233134417
```

In the code snippet above, we call the `program` that we defined earlier and provide it with an implementation of the `Random` service.
We use the `Effect.provideService(tag, implementation){:ts}` function to associate the `Random` service with its implementation, an object with a `next` method that generates a random number passed to `Random.of(){:ts}` (a convenience method to construct and return the service with the correct type).
We use the `Effect.provideService(effect, tag, implementation){:ts}` function to associate the `Random` service with its implementation, an object with a `next` method that generates a random number passed to `Random.of(){:ts}` (a convenience method to construct and return the service with the correct type).

Notice that the `R` type parameter of the `runnable` effect is now `never`. This indicates that the effect no longer requires any context to be provided. With the implementation of the `Random` service in place, we are able to run the program without any further dependencies.

Expand Down Expand Up @@ -173,20 +170,16 @@ To determine if we can use the service, we can use the `Option.match` function p
import * as Option from "@effect/data/Option"

// Effect<never, never, void>
const program = pipe(
Effect.serviceOption(Random),
const program = Effect.serviceOption(Random).pipe(
Effect.flatMap((maybeRandom) =>
pipe(
maybeRandom,
Option.match(
// the service is not available, return a default value
() => Effect.succeed(-1),
// the service is available
(random) => random.next()
)
)
Option.match(maybeRandom, {
// the service is not available, return a default value
onNone: () => Effect.succeed(-1),
// the service is available
onSome: (random) => random.next(),
})
),
Effect.flatMap((randomNumber) => Effect.logInfo(`${randomNumber}`))
Effect.flatMap((randomNumber) => Effect.log(`${randomNumber}`))
)
```

Expand All @@ -206,7 +199,7 @@ const program = Effect.gen(function* (_) {
yield* _(Effect.succeed(-1))
: // the service is available
yield* _(maybeRandom.value.next())
return yield* _(Effect.logInfo(`${randomNumber}`))
return yield* _(Effect.log(`${randomNumber}`))
})
```

Expand All @@ -228,14 +221,12 @@ However, if we provide the `Random` service implementation:

```ts
Effect.runSync(
pipe(
Effect.provideService(
program,
Effect.provideService(
Random,
Random.of({
next: () => Effect.succeed(Math.random()),
})
)
Random,
Random.of({
next: () => Effect.succeed(Math.random()),
})
)
)
// Output: ...more output... message=0.9957979486841035
Expand All @@ -256,7 +247,6 @@ By passing a tuple of tags, we can access the corresponding tuple of services:
```ts
import * as Effect from "@effect/io/Effect"
import * as Context from "@effect/data/Context"
import { pipe } from "@effect/data/Function"

interface Random {
readonly next: () => Effect.Effect<never, never, number>
Expand All @@ -271,8 +261,7 @@ interface Logger {
const Logger = Context.Tag<Logger>()

// Effect<Random | Logger, never, void>
const program = pipe(
Effect.all(Random, Logger),
const program = Effect.all(Random, Logger).pipe(
Effect.flatMap(([random, logger]) =>
Effect.flatMap(random.next(), (randomNumber) =>
logger.log(String(randomNumber))
Expand Down Expand Up @@ -323,8 +312,7 @@ Effect.Effect<Random | Logger, never, void>
To execute the `program`, we need to provide implementations for both services:

```ts
const runnable = pipe(
program,
const runnable = program.pipe(
Effect.provideService(
Random,
Random.of({
Expand All @@ -334,7 +322,7 @@ const runnable = pipe(
Effect.provideService(
Logger,
Logger.of({
log: Effect.logInfo,
log: Effect.log,
})
)
)
Expand All @@ -344,13 +332,12 @@ Alternatively, instead of calling `provideService` multiple times, we can combin

```ts
// Context<Random | Logger>
const context = pipe(
Context.empty(),
const context = Context.empty().pipe(
Context.add(Random, Random.of({ next: () => Effect.succeed(Math.random()) })),
Context.add(
Logger,
Logger.of({
log: Effect.logInfo,
log: Effect.log,
})
)
)
Expand All @@ -359,7 +346,7 @@ const context = pipe(
and then provide the entire context using the `provideContext` function:

```ts
const runnable = pipe(program, Effect.provideContext(context))
const runnable = program.pipe(Effect.provideContext(context))
```

By providing the necessary implementations for each service, we ensure that the `runnable` effect can access and utilize both services when it is executed.
Expand Down
Loading

0 comments on commit 44933fa

Please sign in to comment.