From a5139405bec15071e522310e6030301b7896dbc5 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Mon, 26 Aug 2024 23:31:31 +0700 Subject: [PATCH 1/3] Add section "The flow method" --- .../100-essentials/600-pipeline.mdx | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/content/docs/400-guides/100-essentials/600-pipeline.mdx b/content/docs/400-guides/100-essentials/600-pipeline.mdx index 45dc23fdf..f19b3b479 100644 --- a/content/docs/400-guides/100-essentials/600-pipeline.mdx +++ b/content/docs/400-guides/100-essentials/600-pipeline.mdx @@ -531,6 +531,34 @@ const program = Effect.all([fetchTransactionAmount, fetchDiscountRate]).pipe( ) ``` +## The flow method + +Sometimes, you may need to create a reusable pipeline of operations. In such +cases, you can use the `flow` method. + +```ts twoslash +import { Effect, Duration, flow } from "effect" + +const myRetryLogic = flow( + Effect.timeout(Duration.seconds(3)), + Effect.tapError((error) => Effect.log(`Failed: ${error}`)), + Effect.retry({ times: 2 }), + Effect.tapError((error) => Effect.log(`Abandon as retry failed: ${error}`)), +) + +const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) +const fetchDiscountRate = Effect.promise(() => Promise.resolve(5)) + +const fetchTransactionAmountWithRetry = fetchTransactionAmount.pipe(myRetryLogic) +const fetchDiscountRateWithRetry = fetchDiscountRate.pipe(myRetryLogic) +``` + + + `flow` and `pipe` are very similar. The main difference is that `flow` returns *a function that returns an Effect* + (`() => Effect`), while `pipe` returns an `Effect`. It suggests that `pipe` should be used when you want a result, + while `flow` should be used for further composition. + + ## Cheatsheet Let's summarize the transformation functions we have seen so far: From 5103d7c0a216f408ea6c6cf0f866a09ca5865acb Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Tue, 27 Aug 2024 00:54:22 +0700 Subject: [PATCH 2/3] Rearrange the whole document --- .../100-essentials/600-pipeline.mdx | 101 +++++++++--------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/content/docs/400-guides/100-essentials/600-pipeline.mdx b/content/docs/400-guides/100-essentials/600-pipeline.mdx index f19b3b479..cd3ceccdd 100644 --- a/content/docs/400-guides/100-essentials/600-pipeline.mdx +++ b/content/docs/400-guides/100-essentials/600-pipeline.mdx @@ -60,6 +60,27 @@ In the above example, we start with an input value of `5`. The `increment` funct The result is equivalent to `subtractTen(double(increment(5)))`, but using `pipe` makes the code more readable because the operations are sequenced from left to right, rather than nesting them inside out. +## flow + +`flow` is a "lazier" version of `pipe`. While `pipe` returns a concrete value, `flow` returns a function that returns a +result. It allows us to reuse the same composed functions in different contexts. + +```ts twoslash +import { pipe, flow } from "effect" + +const increment = (x: number) => x + 1 +const double = (x: number) => x * 2 +const subtractTen = (x: number) => x - 10 + +const operation = flow(increment, double, subtractTen) + +const result1 = pipe(5, operation) +const result2 = pipe(10, operation) + +console.log(result1) // Output: 2 +console.log(result2) // Output: 12 +``` + ## Functions vs Methods In the Effect ecosystem, libraries often expose functions rather than methods. This design choice is important for two key reasons: tree shakeability and extensibility. @@ -447,62 +468,44 @@ Configuration: {"dbConnection":"localhost","port":8080}, DB Status: Connected to ## Build your first pipeline -Now, let's combine `pipe`, `Effect.all` and `Effect.andThen` to build a pipeline that performs a series of transformations: - -```ts twoslash -import { Effect, pipe } from "effect" - -// Function to add a small service charge to a transaction amount -const addServiceCharge = (amount: number) => amount + 1 - -// Function to apply a discount safely to a transaction amount -const applyDiscount = ( - total: number, - discountRate: number -): Effect.Effect => - discountRate === 0 - ? Effect.fail(new Error("Discount rate cannot be zero")) - : Effect.succeed(total - (total * discountRate) / 100) - -// Simulated asynchronous task to fetch a transaction amount from a database -const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) +Now, let's combine `pipe`, `Effect.all` and `Effect.andThen` to build a pipeline that performs a series of +transformations. -// Simulated asynchronous task to fetch a discount rate from a configuration file -const fetchDiscountRate = Effect.promise(() => Promise.resolve(5)) +### Using pipe to chain operations -// Assembling the program using a pipeline of effects -const program = pipe( - Effect.all([fetchTransactionAmount, fetchDiscountRate]), - Effect.flatMap(([transactionAmount, discountRate]) => - applyDiscount(transactionAmount, discountRate) - ), - Effect.map(addServiceCharge), - Effect.map((finalAmount) => `Final amount to charge: ${finalAmount}`) -) +At the beginning of this section, we saw how to use the `pipe` function to compose functions. Let us take a closer look +on how to apply it to Effects. We should differentiate between -// Execute the program and log the result -Effect.runPromise(program).then(console.log) // Output: "Final amount to charge: 96" -``` - -## The pipe method +- the `pipe` **method**, and +- the `pipe` **function** -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. +by understanding that one comes from an Effect itself, while the other is a function that receives an input and applies +other functions to it. Here's how the `pipe` **method** works: ```ts +import { Effect } from "effect" + +// ---cut--- +const effect = Effect.succeed(5) const result = effect.pipe(func1, func2, ..., funcN) ``` -This is equivalent to using the `pipe` **function** like this: +It is equivalent to using the `pipe` **function** like this: ```ts +import { Effect, pipe } from "effect" + +// ---cut--- +const effect = Effect.succeed(5) 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 `Function` module and saving you some keystrokes. +The `pipe` method is available on all effects and many other data types, eliminating the need to import the `pipe` +function. It might save you some keystrokes occasionally. -Let's rewrite the previous example using the `pipe` method: +Let's finish the previous example using the `pipe` method: ```ts twoslash import { Effect } from "effect" @@ -521,7 +524,6 @@ const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) const fetchDiscountRate = Effect.promise(() => Promise.resolve(5)) -// ---cut--- const program = Effect.all([fetchTransactionAmount, fetchDiscountRate]).pipe( Effect.flatMap(([transactionAmount, discountRate]) => applyDiscount(transactionAmount, discountRate) @@ -529,17 +531,18 @@ const program = Effect.all([fetchTransactionAmount, fetchDiscountRate]).pipe( Effect.map(addServiceCharge), Effect.map((finalAmount) => `Final amount to charge: ${finalAmount}`) ) + +Effect.runPromise(program).then(console.log) // Output: "Final amount to charge: 96" ``` -## The flow method +### Using flow to create reusable pipelines -Sometimes, you may need to create a reusable pipeline of operations. In such -cases, you can use the `flow` method. +Sometimes, you may need to create a reusable pipeline of operations. In such cases, you can use the `flow` method. ```ts twoslash import { Effect, Duration, flow } from "effect" -const myRetryLogic = flow( +const customRetryLogic = flow( Effect.timeout(Duration.seconds(3)), Effect.tapError((error) => Effect.log(`Failed: ${error}`)), Effect.retry({ times: 2 }), @@ -549,14 +552,14 @@ const myRetryLogic = flow( const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100)) const fetchDiscountRate = Effect.promise(() => Promise.resolve(5)) -const fetchTransactionAmountWithRetry = fetchTransactionAmount.pipe(myRetryLogic) -const fetchDiscountRateWithRetry = fetchDiscountRate.pipe(myRetryLogic) +const fetchTransactionAmountWithRetry = fetchTransactionAmount.pipe(customRetryLogic) +const fetchDiscountRateWithRetry = fetchDiscountRate.pipe(customRetryLogic) ``` - `flow` and `pipe` are very similar. The main difference is that `flow` returns *a function that returns an Effect* - (`() => Effect`), while `pipe` returns an `Effect`. It suggests that `pipe` should be used when you want a result, - while `flow` should be used for further composition. + `flow` and `pipe` are very similar. The main difference is that `flow` returns *a function that returns a result* + (in this case, it is `() => Effect`), while `pipe` returns a result (an `Effect`). It suggests that `pipe` should be + used when you want a result, while `flow` should be used for further composition. ## Cheatsheet From 579fc68455a7272d7e7135ad1dadaff47411bdbf Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Tue, 27 Aug 2024 01:38:29 +0700 Subject: [PATCH 3/3] Remove redundant cut --- content/docs/400-guides/100-essentials/600-pipeline.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/docs/400-guides/100-essentials/600-pipeline.mdx b/content/docs/400-guides/100-essentials/600-pipeline.mdx index cd3ceccdd..5db3e4b6f 100644 --- a/content/docs/400-guides/100-essentials/600-pipeline.mdx +++ b/content/docs/400-guides/100-essentials/600-pipeline.mdx @@ -487,7 +487,6 @@ Here's how the `pipe` **method** works: ```ts import { Effect } from "effect" -// ---cut--- const effect = Effect.succeed(5) const result = effect.pipe(func1, func2, ..., funcN) ``` @@ -497,7 +496,6 @@ It is equivalent to using the `pipe` **function** like this: ```ts import { Effect, pipe } from "effect" -// ---cut--- const effect = Effect.succeed(5) const result = pipe(effect, func1, func2, ..., funcN) ```