{"id":13496142,"url":"https://github.com/antoine-coulon/effect-introduction","last_synced_at":"2025-04-07T10:23:06.204Z","repository":{"id":168304230,"uuid":"638482704","full_name":"antoine-coulon/effect-introduction","owner":"antoine-coulon","description":"Effect introduction about the whys, helping transitioning from raw TypeScript to Effect TypeScript ","archived":false,"fork":false,"pushed_at":"2024-04-22T08:55:24.000Z","size":168,"stargazers_count":270,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-09T10:06:25.907Z","etag":null,"topics":["dsl","effect-systems","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/antoine-coulon.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-09T13:06:48.000Z","updated_at":"2024-10-08T19:31:03.000Z","dependencies_parsed_at":"2023-06-09T06:45:39.971Z","dependency_job_id":"a650e4f8-88e2-4be9-a5b5-032629a36ce2","html_url":"https://github.com/antoine-coulon/effect-introduction","commit_stats":{"total_commits":21,"total_committers":3,"mean_commits":7.0,"dds":0.09523809523809523,"last_synced_commit":"3675f143f0001af1a25f89c80ffa3993ead5c401"},"previous_names":["antoine-coulon/effect-introduction"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoine-coulon%2Feffect-introduction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoine-coulon%2Feffect-introduction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoine-coulon%2Feffect-introduction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/antoine-coulon%2Feffect-introduction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/antoine-coulon","download_url":"https://codeload.github.com/antoine-coulon/effect-introduction/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247632453,"owners_count":20970164,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dsl","effect-systems","typescript"],"created_at":"2024-07-31T19:01:42.956Z","updated_at":"2025-04-07T10:23:05.775Z","avatar_url":"https://github.com/antoine-coulon.png","language":"TypeScript","funding_links":[],"categories":["Introductions and crash courses","TypeScript"],"sub_categories":[],"readme":"\u003cp align=\"center\" width=\"100%\"\u003e\n  \u003cimg src=\"https://github.com/antoine-coulon/effect-introduction/assets/43391199/eafb4846-0d9c-466d-a50e-d5bc782bff93\" alt=\"Effect, next generation TypeScript\"\u003e\n\u003c/p\u003e\n\n**Important note: This is still in the making but as this was requested, I'm open-sourcing the first version**.\n\n# A practical introduction to the whys of Effect\n\nThis introduction comes from Effect workshops I gave in which the main objective was to explain in few hours the whys of Effect coming from raw JavaScript/TypeScript.\n\nThis introduction **is not about how to write Effect code** but rather focuses on why Effect might be an interesting pick for writing softwares using TypeScript as of today taking into account all the common problems we face as developers. As a rule of thumb, each developer should be aware of the problems a tool is solving before even trying to take a look at the implementation details. Hopefully with that short introduction you will **first become aware of the existing problems** and then **understand how elegantly and efficiently Effect solves them**.\n\n## **Effect is a well-rounded tool solving a lot of well-known software engineering problems. Let's first talk about problems before solutions.**\n\n**Inspiration**\n\nThis is highly inspired by both excellent talks from [Michael Arnaldi (@mikearnaldi) at the WorkerConf](https://www.youtube.com/watch?v=zrNr3JVUc8I) and [Mattia Manzati at React Alicante](https://www.youtube.com/watch?v=uwALExyq4NY).\n\nN.B: If you're already comfortable with the problems Effect tries to solve and wish to jump straight into the **hows of Effect**, I suggest you to take a look at the official [Effect documentation](https://effect.website/) (still in the works) and the [excellent crashcourse from Stefano Pigozzi (@pigoz)](https://github.com/pigoz/effect-crashcourse).\n\n## Samples and source code\n\nIn the `src/` folder you will be able to find some samples used alongside the introduction. There is also a TypeScript version if you prefer to both read the content and have the ready-to-be-run and type-checking samples.\n\nNote: TypeScript files are still in the making so they might be incomplete/outdated as of now.  \n\n## Outcomes you can expect from the introduction\n\n- **Understanding most commons problems we're facing as developers**\n- **Understanding limits we're facing as JavaScript/TypeScript developers**\n- **Basic understanding of Effect**\n- **Basic understanding of an \"Effect System\"**\n \nBefore diving into Effect, let's take a step back talking about what problems we commonly face as developers.\nEffect is a tool in the same way as TypeScript is a tool. Our responsibility is to first understand the problems as it would help us finding the good solutions. \n\nWhat are the most common challenges we are facing when developing softwares?\n\n- ### [**Explicitness**](#1-explicitness)\n- ### [**Testing**](#2-testing)\n- ### [**Resilience**](#3-resilience)\n- ### [**Composability**](#4-composability)\n- ### [**Concurrency**](#5-concurrency)\n- ### [**Efficiency \u0026 Performance**](#6-efficiency--performance)\n- ### [**Tracing \u0026 Logging**](#7-tracing--logging)\n \n\nWe'll show examples using TypeScript, but this is not only related to JavaScript/TypeScript concern. It **concerns every ecosystem, language**, for instance Effect was initially heavily inspired by [ZIO](https://zio.dev/), its Scala counterpart, because most of the problems also apply to Scala.\n\nHopefully, you'll realise that Effect is just a **tool that addresses hard problems** that we will always face, regardless the underlying ecosystem/language.\n\nBefore diving into these problems and the solutions Effect brings, let me just do a pretty\nquick prelude that will help you understand right away the approach.\n\n## 0. Prelude\n\nLet's talk a little bit about the Effect datatype in itself with a bit of background history.\n\n`Effect` is the core datatype of the ecosystem, but what if I tell you that it\ncould have been called `Program` instead? The reason for that is that Effect\ntries to model exactly what a program is, that is something that requires\nan environment to run, that can fail with an error or succeed with a value.\n\n\u003e You can see the original conversation started by @mikearnaldi [just there in Effect's Discord](https://discord.com/channels/795981131316985866/795983589644304396/948653981863923813)\n \nConsequently, Effect is a generic datatype with 3 type parameters:\n`A`: represents the **value** that can be produced by the program\n`E`: represents the **error** that can be produced by the program\n`R`: represents the **environment** required to run the program\n\nResulting in: `Effect\u003cA, E, R\u003e`.\n\n```ts\nimport type { Effect } from \"effect\";\n\ntype Program\u003cEnvironment, Error, Success\u003e = Effect.Effect\u003c\n  Success\n  Error,\n  Environment,\n\u003e;\n```\n\nLet's just model a simple command-line interface program (with a very high\nlevel of abstraction). We can say that our command line program requires\na process to run, that will be granted by the OS. It's represented by the first\ngeneric type parameter `R`. Then, our program can fail with an error of type\n`E` (Standard Error) or succeed with a value of type `A` (Standard Output).\n\n```ts\ntype Stdout = any;\ntype Stderr = any;\ntype Process = any;\n\ntype CommandLineProgram = Program\u003cStdout, Stderr, Process\u003e;\n```\n\nHaving these 3 generic parameters explicitly defined in the type signature\nof our program allows us to have a very precise understanding of what our\nprogram is doing and what it can produce as a result. In addition to explicitness,\nEffect provides us a very strong type safety guarantee that the constraints of the\ngeneric type parameters will be respected by the implementation of the program.\n\n## 1. Explicitness \n\n\u003cp align=\"left\"\u003e\n    \u003ca href=\"https://github.com/antoine-coulon/effect-introduction/blob/main/src/01-explicitness.ts\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://skillicons.dev/icons?i=ts\" width=\"25\" /\u003e\n      Go to source file (01-explicitness.ts)\n     \u003c/a\u003e\n\u003c/p\u003e\n\nThe ability of making a program self-describing, allowing to have a clear vision and understanding what outcomes the program can produce without having to run it.\n\nIdeally, what we want is:\n\n- explicit errors\n- explicit dependencies\n- explicit outcomes\n\nLet's see few examples using TypeScript first, then with Effect\n\n\n## Synchronous computations\n\n```ts\nfunction multiplyNumber() {\n  const generatedNumber = NumberGeneratorLibrary.generateRandomNumber();\n  //    ^ number\n  return number * 2;\n}\n```\n\nUnfortunately when running the code our program crashes: `Error at \u003canonymous\u003e`\nWithout taking a look at the implementation of the `generateRandomNumber()`, we don't even know that this thing might throw an error. The consequence of that is having runtime defect makes the process just die. Think of that in a wider scope of a program, where this can be very hard to properly handle. \n\n```ts\nexport function generateRandomNumber(): number {\n    const randomNumber = Math.random();\n\n    if (randomNumber \u003e 0.9) {\n      // RIP\n      throw new Error();\n    }\n\n    return randomNumber;\n}\n```\n\nThis behavior can be the root cause of many problems including defensive coding, for instance:\n\n```ts\nfunction defensiveMultiplyNumber() {\n  try {\n    const number = NumberGeneratorLibrary.generateRandomNumber();\n    return number * 2;\n  } catch {\n    // Just in case\n  }\n}\n```\n\nOr we need to deal with runtime errors the hard way:\n\n```ts\nfunction blindlyCatch() {\n  try {\n    const random = Math.random();\n\n    if (random \u003e 0.9) {\n      throw new SomeError();\n    }\n\n    if (random \u003e 0.8) {\n      throw new SomeOtherError();\n    }\n\n    return random;\n  } catch (exception: unknown) {\n    if (isSomeErrorException(exception)) {\n      // do something\n    } else if (isSomeOtherErrorException(exception)) {\n      // do something else\n    }\n  }\n}\n```\n\nThe solution that we just found is not ideal and even if there is only thirty lines of code, compromises must already be done because we simply lack of explicitness.\n\n\n## Asynchronous operations\n\nOne way to model an async computation with JavaScript is using a Promise whose results is always delivered asynchronously.\n\n```ts\nfunction doSomething(): Promise\u003cnumber\u003e {\n  return new Promise((resolve) =\u003e {\n    setTimeout(() =\u003e {\n      resolve(3)\n    });\n  });\n}\n\ndoSomething().then(\n  // Callback will be executed at some point in time (generally as soon as possible)\n  () =\u003e {\n\n  }\n);\n```\n\nHowever, Promises are both conceptually limited and lacking a lot of important features to deal with common problems that we face.\n\n## Drawbacks of a Promise 😥\n\n- **Eagerly executed, hence is impure, referentially-opaque and is running computation (already a value). Consequently, can't be used around for writing functional programs.**\n\nYou might already know this eager nature of a Promise, but you might not know that it prevents many interesting rules to be applied. \n\nPurity and referential transparency are important concepts in Functional Programming because they allow you to make assumptions about the behavior of your program levaraging mathematical laws (compositions and substitutions of expressions, etc). Moreover, it helps reasoning about the behavior of your program by just looking at the types, which is what we also target with explicitness. By leveraging compilers, in our case TypeScript, we will be constrained to a set of well-behaved types and principles, allowing us to eliminate whole classes  of bugs and unexpected behaviors.\n\nA more detailed version of the explanation is available in the **[01-explicitness.ts](https://github.com/antoine-coulon/effect-introduction/blob/main/src/01-explicitness.ts) source file**\n\n- **Implicit memoization of the result (either success or failure).**\n\n As we already said, a Promise is eagerly executed. It means that as soon as you create a Promise, the computation is already running and might have already completed with a value. That value produced by the Promise is implicitly memoized meaning that when the Promise is settled, the internal state of the Promise is frozen and can't be changed anymore, whether the Promise is fulfilled or rejected. Consequently if you want to run the same computation again, you'll need to recreate the Promise from scratch. Altough this is convenient because it allows subscribers to receive the value even when registering for it after the Promise produced its value, this makes the behavior of a Promise non-reusable and does not favor retries and compositions.\n  \n- **Has only one generic parameter: `Promise\u003cA\u003e`. The error is non-generic/non-polymorphic.**\n\nPromise has only one generic parameter, which is the type of the value produced. This is not really convenient because it means that the error is not reflected by default in the type of the Promise. This highly restricts the type-level expressiveness and forces us to deal with untyped and unknown failures. We could say that only generic parameter can be used to represent the error using Either/Result representations, but this model has its own limitations when it comes to combining many operations together and when trying to inferthe type of the errors of the whole chain. \n\n- **Can't depend on any contextual information.**\n\nA Promise can't explicitely encode the fact of depending on some contextual information. It means that if you want to run a Promise that depends on some input context, dependencies can not be explicitely modeled hence it is impossible to statically constrain the Promise to only be run in a valid context i.e. with all the requirements satisfied.\n\nThis is a problem because this means that Promises can implicitely rely on hidden dependencies and does not offer any flexibility when it comes to composition and dependency injection. By nesting Promises, that implicit layer of dependencies will grow and it will be harder to reason about the behavior and the requirements of the program.  \n\nA more detailed version of the explanation is available in the **[01-explicitness.ts](https://github.com/antoine-coulon/effect-introduction/blob/main/src/01-explicitness.ts) source file**\n\n- **No control over concurrency.**\n\nNatively, a Promise does not offer any control execution over concurrency \nso when composing many Promises together, you can't control how many Promises\ncan be spawned and run in parallel (unbounded concurrency). \nThis is a problem because in most cases you will end up either spawning too many \nPromises and overloading the system or constrain Promises to run sequentially \nand not taking advantage of the asynchronous nature of the platform.\n\nThis is talked in more details in the  [**Concurrency**](#5-concurrency) section of the introduction.\n\n- **Not much built-in combinators (then, catch, finally) and static methods (all, allSettled, race, any, resolve, reject).**\n\nBy default, Promises don't have much combinators to work with nor Promise constructors and are lacking some important features, for instance `all` and `allSettled` are _unbounded_ concurrency-wise, `race` and `any` are working as expected but are unsafe because \"race losers\" are not cleanly interrupted hence underlying resources can not be released (it's also the case for `Promise.all`).\n  \n- **No builtin interruption model.**\n\nFollowing what was said just before, Promises unfortunately don't have a built-in interruption model. There was one attempt to [introduce cancellation to Promises that was withdrawn](https://github.com/tc39/proposal-cancelable-promises) for some [unclear reasons](https://github.com/tc39/proposal-cancelable-promises/issues/70). My 2 cents is that it was because adding cancellation into Promises would have introduced too many changes, and the initial design constrained the evolution of the builtin features around Promises.\n\nIn any case as of now, we are not able to cancel a Promise using the standard API.\n\n- **No builtin retry logic.**\n\nAnother feature which won't never see the light of day is the built-in retry policies. Given that a Promise already represents a running computation in itself, it can not be easily retried without being constructed again. We can work around these limitations by introducing lazy promises which are nothing but Promises wrapped in functions over which we have the control, but this approach reduce flexibility, composability and introduce quickly some avoidable complexity.\n\nA more detailed version of the explanation is available in the **[03-resilience.ts](https://github.com/antoine-coulon/effect-introduction/blob/main/src/03-resilience.ts) source file**\n\n\n---\n\nPromises are everywhere and are part of most codebases when dealing with asynchronous programming, so you might wonder what could be a solid alternative to that. Let's jump right into it.\n\n## Alternatives 1/2\n\n## fp-ts\n\n![width:600px height:300px](https://user-images.githubusercontent.com/43391199/231682115-a9e9cf3c-e310-4eed-b7f8-2f67ccf96cde.png)\n\n[fp-ts](https://github.com/gcanti/fp-ts) created by [Giulio Canti (@gcanti)](https://github.com/gcanti) is the most popular functional programming library in the TypeScript ecosystem and provides developers with popular patterns and reliable abstractions from typed functional languages.\n\nfp-ts introduced primitives that allow to model such things:\n\n**Synchronous**\n\n- `IO\u003cA\u003e`: Represents a lazy and synchronous computation that are not expected to fail, meaning that executing the thunk produces a value `A`. \n  \n```ts\ntype IO\u003cA\u003e = () =\u003e A  \n```\n\nBecause errors can't explicitely be represented using `IO\u003cA\u003e`, it means that conventionally the side effect must not throw any unexpected errors.\n\n- `IOEither\u003cE, A\u003e`: When it comes to explicitely representing a typed error that can be produced by the execution of a synchronous computation, **fp-ts** provides us `IOEither\u003cE, A\u003e`. It represents a synchronous computation that can fail with an error `E`.\n\n**Asynchronous**\n\n- `Task\u003cA\u003e`: It is essentially the same as `IO\u003cA\u003e` except that it describes an asynchronous computation that is not expected to fail.\n- `TaskEither\u003cE, A\u003e`: It is essentially the same as `IOEither\u003cE, A\u003e` except that it describes an asynchronous computation that is expected to fail with an error `E`.\n\nCool, we already found a solution to favor explicitness and model both the success or failure an operation can produce. It's a great step towards a stronger primitives, but still requires us to make a difference between asynchronous or synchronous computations. Why should we care about whether it's async or sync? We don't care! Ideally, we would like to be able to represent a computation that can both fail with an error `E` or succeed with a value `A` for all types of computations and always describe it the same way. There are already many difference between how to deal with asynchronous and synchronous error propagation, and there are even many ways to deal with asynchronous error handling (callbacks vs promises), we want to simplify that both at the type-level and at runtime.\n\nMoreover, there is still:\n- no builtin control over concurrency \n- no builtin interruption\n- no builtin retry\n- composing/combining multiple Tasks gets quickly hard to read\n- semantic differences between synchronous and asynchronous operations\n \n## Alternatives 2/2\n\n**Effect**\n\nThe new kid in town\n\n![width:600px height:300px](https://user-images.githubusercontent.com/43391199/231682137-3658c039-df03-4b56-ad88-b854f4de2454.png)\n\n```ts\n/**\n * An Effect is modeled with the datatype Effect\u003cA, E, R\u003e\n * (A) represents the successful outcome a computation can produce\n * (E) represents the failure a computation can produce\n * (R) represents requirements a computation needs in order to be run\n */ \ninterface Effect\u003cA, E, R\u003e {}\n```\n\nIn the context of explicit outcomes, `Effect` is a data type that can be used to model everything at the same time:\n- no distinction between synchronous/asynchronous computations, everything is just a computation\n- can be used to model computations that are expected to fail or not fail, the `Either` datatype is embedded in the Effect data type\n\nBut also Effect is:\n\n- lazy by nature\n- highly composable\n- highly type-safe\n- explicit errors and dependencies management (dependencies and `R` are discussed right after)\n- builtin concurrency control\n- builtin interruption \n- builtin retry\n- builtin resource management (acquire/release)\n\nThe primary goal of an Effect is to act as a representation of a computation or more generally a program whose outcome (error or success) and dependencies are explicitely modeled.\n\n## How we can improve that way of handling errors?\n\nNow that everyone is up-to-date with challenges we are facing dealing with synchronous and asynchronous (promise-based) computations, it's time to go back on our dear _explicitness_ and see how Effect solves that.\n\nTo improve the way of handling errors, we can improve the way they are described, and finally make them part of the type signature as well as the value. One universal solution that you might already now is `Either` (Result-like) implemented natively in Rust, Kotlin, Haskell... Can be implemented in TypeScript as well.\n\n```ts\ninterface Either\u003cA, B\u003e {\n  readonly left: A;\n  readonly right: B;\n}\n```\n\nAn `Either\u003cA, B\u003e` at its root has nothing to do with errors, it's simply a datatype that aims to represent a values with two possibilities, either \"A\" (left) or \"B\" (right). The Either type is sometimes used to represent a value which is either correct or an error; by convention, the Left constructor is used to hold an error value and the Right constructor is used to hold a correct value. This Either specialization is what most people now know as a `Result\u003cE, A\u003e`.\n\n```ts\ninterface Result\u003cError, Success\u003e extends Either\u003cError, Success\u003e {}\n```\n\nEffect integrates an `Either\u003cE, A\u003e` under the hood of each computation, making it both easy and explicit to deal with.\n\n\n```ts\ntype _ = Effect\u003cA, E, R\u003e\n//              ^__^ -\u003e Either-like\n```\n\nDo you remember our first raw TypeScript samples? Let's rewrite it with Effect. Let's consider some code:\n\n```ts\n\nimport { pipe } from \"effect/Function\";\nimport { Effect } from \"effect\";\n\nnamespace EffectNumberGeneratorLibrary {\n  export function generateRandomNumber(): Effect.Effect\u003cnumber, Error, never\u003e {\n    return pipe(\n      Effect.sync(() =\u003e Math.random()),\n      Effect.flatMap((randomNumber) =\u003e {\n        if (randomNumber \u003e 0.9) {\n          return Effect.fail(new Error());\n        }\n\n        return Effect.succeed(randomNumber);\n      })\n    );\n  }\n}\n\n```\n\nIf you take a close look at the above `generateRandomNumber()` signature, you can see that we have the error typed as `Error`. Consequently if you describe an Effect that should not produce any known failure that is having the error channel typed as `never` (`Effect\u003cnever, never, number\u003e`) and try to directly consume an effect that has a typed failure (in that case typed `Error`), it won't compile. It's great, because we are forced by the compiler to be rigorous and to deal with the error.\n\nLet's see that in action, with `multiplyNumberWithoutDealingWithError` that is not supposed to produce failures.\n\n```ts\nfunction multiplyNumberWithoutDealingWithError(): Effect.Effect\u003c\n  number,\n  never, // E is typed as 'never', meaning that this Effect is not expected to produce failures (in the same way as IO\u003cA\u003e or Task\u003cA\u003e). \n  never\n\u003e {\n  return EffectNumberGeneratorLibrary.generateRandomNumber();\n  // ^ Type 'Effect\u003cnumber, Error, never\u003e' is not assignable to type 'Effect\u003cnumber, never, never\u003e'\n}\n```\n\nSo now that we are aware of the constraint, how do we come from a description of a computation that will eventually produce a failure to a computation that does not produce failures? Dealing with errors in a recoverable fashion is pretty straightforward.\n\n```ts\nfunction multiplyNumberWhenDealingWithError(): Effect.Effect\u003c\n  number,\n  never,\n  never\n\u003e {\n  return pipe(\n    EffectNumberGeneratorLibrary.generateRandomNumber(),\n    Effect.flatMap((number) =\u003e Effect.succeed(number * 2)),\n    // Recovering from the error and producing a successful result value instead\n    Effect.catchAll(() =\u003e Effect.succeed(0))\n  );\n}\n```\n\nAfter having described our recovery logic, the error channel is immediately being changed from `Error` to `never` meaning that `multiplyNumberWhenDealingWithError` computation can benefit from the description of a computation that won't produce expected failures. Consequently we can just deal nicely with that outcome by relying on the typings and be confident about the outcome of the computation.\n\nIn that case it's still a very simple example, but keep in mind that Effect leverages pretty well inference in a way that it can keep track and compose the error channel of dozens of chains of effects.\n\nAnother benefit of having a dedicated error channel is that we can also model multiple failures using tagged unions.\nIn the following example, by just using TypeScript tagged classes, we are able to make Effect infer a union of typed errors.\n\n```ts\n\nexport class NumberIsTooBigError {\n  readonly _tag = \"NumberIsTooBigError\";\n}\n\nexport class NumberIsTooSmallError {\n  readonly _tag = \"NumberIsTooSmallError\";\n}\n\nnamespace EffectNumberGeneratorLibrary {\n  export function generateRandomNumber(): Effect\u003c\n    number,\n    NumberIsTooBigError | NumberIsTooSmallError,\n    never\n  \u003e {\n    return pipe(\n      Effect.sync(() =\u003e Math.random()),\n      Effect.filterOrFail(\n        (randomNumber) =\u003e randomNumber \u003e 0.9,\n        () =\u003e new NumberIsTooBigError()\n      ),\n      Effect.filterOrFail(\n        (randomNumber) =\u003e randomNumber \u003c 0.2,\n        () =\u003e new NumberIsTooSmallError()\n      )\n    );\n  }\n}\n```\n\nNote: there might be some cases where TypeScript isn't able to unify union types properly, but thankfully using Effect combinators (here: `filterOrFail`) we are able to bypass these limitations.\n\nI'm not going to dive into this subject there, but you can read more either in the [01-explicitness.ts](https://github.com/antoine-coulon/effect-introduction/blob/main/src/01-explicitness.ts) section or in the [following Discord thread](https://discord.com/channels/795981131316985866/1115294739382669312).\n\n\nNow that we have failures represented as a union, it allows us to pattern match and recover from either specific failures or all failures. Depending on that choice, pattern matched failures will be erased from the error channel and other ones will just remain until some recovery logic is defined at some point. \n\n```ts\n\nfunction multiplyNumberWithExhaustivePatternMatching(): Effect\u003cnumber, never, never\u003e {\n  // Note how the error channel becomes \"never\" now that we exhaustive pattern match                                           \n  return pipe(\n    EffectNumberGeneratorLibrary.generateRandomNumber(),\n    // If there is no failure\n    Effect.flatMap((number) =\u003e Effect.succeed(number * 2)),\n    // If there are failures, pattern match.\n    Effect.catchTags({\n      NumberIsTooBigError: () =\u003e Effect.succeed(0),\n      NumberIsTooSmallError: () =\u003e Effect.succeed(1),\n    })\n  );\n}\n\n```\n\nIn the above case the pattern matching is exhaustive, but if it's not the case, the members of the union not being covered by the matching will be still reflected in the error channel.\n\n```ts\n\nfunction multiplyNumberWithPartialPatternMatching(): Effect.Effect\u003c\n  number,\n  NumberIsTooBigError,\n  // ^ partial pattern matching does not erase all errors\n  never\n\u003e {\n  return pipe(\n    Effect2NumberGeneratorLibrary.generateRandomNumber(),\n    Effect.flatMap((number) =\u003e Effect.succeed(number * 2)),\n    Effect.catchTags({\n      NumberIsTooSmallError: () =\u003e Effect.succeed(1),\n    })\n  );\n}\n\n```\n \n**Explicit dependencies**\n\n\u003cp align=\"left\"\u003e\n    \u003ca href=\"https://github.com/antoine-coulon/effect-introduction/blob/main/src/01-explicitness.ts\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://skillicons.dev/icons?i=ts\" width=\"25\" /\u003e\n      Go to source file, section \"Explicit dependencies\" (01-explicitness.ts)\n     \u003c/a\u003e\n\u003c/p\u003e\n\nEffect can embed contextual information in the same way as the `Reader` data type from `fp-ts` was describing it.\n\nIt makes the dependencies required for the computation to be run also explicit. Let's say we have a very simple use case whose purpose is to register a new user on a given platform. \nThe use case is meant to be agnostic of implementation details, that means that it ignores how the user registration is indeed persisted, whether it is in a database or some other storage service. \nThe only thing the use case is responsible for is to orchestrate correctly all the business requirements. \nIn our very simple example below, it's only registering the user to a given storage (in a real world application it could be dispatching a domain event, and putting both the user registration and the even dispatch in the same transaction, a la [Transactional Outbox](https://microservices.io/patterns/data/transactional-outbox.html) for instance).\n\n```ts\n\nimport { Effect } from \"effect\";\nimport * as Context from \"effect/Context\";\n\ninterface UserRepository {\n  createUser: () =\u003e Effect.Effect\u003cCreatedUser, UserAlreadyExistsError, never\u003e;\n}\n\nconst UserRepository = Context.GenericTag\u003cUserRepository\u003e(\"UserRepository\");\n\n// Use case depending on an abstract User Repository\nfunction registerUser(): Effect.Effect\u003cCreatedUser, UserAlreadyExistsError, UserRepository\u003e {\n                                                                            // ^ explicit dependency\n  return pipe(\n    UserRepository,\n    Effect.flatMap((userRepository) =\u003e userRepository.createUser()),\n    // ... do something more as part of the use case, sending domain events, etc.\n  );\n}\n\n```\n\nWhat it means is that `registerUser` needs an instance of some service that implements the interface `UserRepository`. Until the requirements are satisfied, the program won't compile:\n\n```ts\nconst mainProgram = Effect.runPromise(registerUser());\n      // ^ Type 'UserRepository' is not assignable to type 'never': ts(2345)\n```\n\nWe **can't compile the program because we didn't satisfy the dependencies**. \n\nHow does it work? Theorically speaking, it's simple. The runtime interpreter checks that the Effect we're trying to run has all the dependencies satisfied. Statically at the type-level, we're able to determine that by checking\nthe `R` type parameter of the Effect. If the `R` type parameter is `never`, it means that all dependencies of the Effect are satisfied. Otherwise, it means that some dependencies are missing (the ones still visible in the `R` type).\n\n```ts\nconst _program = useCases.registerUser();\n//    ^ The type here is Effect\u003cCreatedUser, UserAlreadyExistsError, UserRepository\u003e\n```\n\nBecause the `R` still has `UserRepository`, it means that the dependency needs to be provided in order for the effect to be run.\n\nOne benefit of having explicit dependencies is that conceptually the requirements are very clear and dependencies are not hidden/implicit. \nDependencies appearing in the `R` generic type parameter is only refering to interfaces not any real implementations, this has for consequence to let room for the **Dependency Inversion Principle to easily spread everywhere in a effortless way**.\n\nThis example shows the use of one dependency, but it's important to note that Effect is able to **deeply infer the dependencies required as a TypeScript Union type**, wherever the dependencies come from in the Effect tree:\n\n```ts\nconst effect1: Effect\u003cnumber, never, DependencyA\u003e = {};\n\nconst effect2: Effect\u003cnumber, never, DependencyB\u003e = {};\n\nconst program: Effect\u003cnumber, never, DependencyA | DependencyB\u003e = Effect.gen(function* ($) {\n                      // ^ See how both respective dependencies from \"effect1\" and \"effect2\"\n                      // now were propagated in the dependencies of our main program, represented as a typed union.\n  const result1 = yield* $(effect1);\n  const result2 = yield* $(effect2);\n\n  return result1 + result2;\n});\n```\n\nIn that case, there is no deep Effect nesting but the principle remains the same.\n\nIf you're interested in the story behind the representation of the dependencies as a Union Type, [here is the section explaining that in the official Effect documentation](https://effect.website/docs/faq/coming-from-zio).\n\n### Type-safe dependency injection\n\nJust before, we mentioned the fact that until a required dependency is satisfied, the program won't compile. Effect provides us a type-safe dependency injection mechanism helping us satisfy the dependency graph.\n\n```ts\n\npipe(\n  registerUser(),\n  // Dependency injection\n  Effect.provideService(UserRepository, {\n    createUser: () =\u003e\n      // We don't care about the implementation, it could be anything, as soon\n      // as it implements the interface contract.\n      // Here we just satisfy the interface by producing the expected failure\n      Effect.fail(new UserAlreadyExistsError(\"User already exists\")),\n  }),\n  Effect.runPromise\n);\n```\n\nUntil we provide the service implementation, the program won't compile because all the computation requirements are not satisfied.\n\n```ts\nEffect.runPromise(something as Effect\u003cvoid, never, SomethingService\u003e)\n// ^ This won't compile, a computation for which all the requirements are not satisfied (SomethingService) can't be executed.\n\nEffect.runPromise(\n  pipe(\n    something, // now becomes Effect\u003cnever, never, void\u003e, because an implementation matching the interface was injected.\n    Effect.provideService(SomethingService, {})\n  )\n);\n```\n\n**Service composition**\n\nIn real-world application scenarios, services would also depend on a set of other services quickly creating a complex dependency graph to satisfy. \n\n```mermaid\n  graph TD;\n      ServiceA--\u003eServiceB;\n      ServiceA--\u003eServiceC;\n      ServiceC--\u003eServiceD;\n      ServiceC--\u003eServiceE;\n      ServiceC--\u003eServiceF;\n```\n\nThankfully to manage dependency injection at scale, Effect embeds `Layers` which are **recipes for creating services in a composable, effectul, resourceful and asynchronous way**. \nTheir goal is to overcome standard constructor limitations and offer more powerful and safe service construction primitives.\n\nLayers describe a set of required dependencies (In) and produces a set of composed dependencies (Out). During Layer construction, errors can occur, hence the Layer signature:\n\n```ts\nexport interface Layer\u003cROut, E, RIn\u003e {}\n```\n\nIn the same spirit as for Effects, using Layers for which dependencies are not satisfied will result in compilation errors. \n\nAs part of the introduction, this `Layer` section ends up there. If you want to know more about Layers, here is a list of advanced resources:\n- [Effect API reference](https://effect-ts.github.io/io/modules/Layer.ts.html)\n- [Layer chapter from pigoz's crashcourse](https://stackblitz.com/github/pigoz/effect-crashcourse?file=006-layer.ts)\n- [ZIO ZLayer documentation](https://zio.dev/reference/di/zlayer-constructor-as-a-value)\n\n## 2. Testing\n\n\u003cp align=\"left\"\u003e\n    \u003ca href=\"https://github.com/antoine-coulon/effect-introduction/blob/main/src/02-testing.ts\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://skillicons.dev/icons?i=ts\" width=\"25\" /\u003e\n      Go to source file (02-testing.ts)\n     \u003c/a\u003e\n\u003c/p\u003e\n\nTesting is the ability of asserting that a system behaves as expected. As obvious as it may seem, testing can be very tricky if the program is coupled to implementation details and has implicit (hidden dependencies) that we can't control.\n\nThankfully, Effect is explicit towards dependencies and favors the use of the Dependency Inversion Principle (DIP) by forcing each computation to depend on an abstraction (interface) rather than on an implementation. \n\nLet's come back to our previous section example:\n\n```ts\ninterface UserRepository {\n  createUser: () =\u003e Effect.Effect\u003cCreatedUser, UserAlreadyExistsError, never\u003e;\n}\n\nconst UserRepository = Context.Tag\u003cUserRepository\u003e();\n\nfunction someUseCase(): Effect.Effect\u003cCreatedUser, UserAlreadyExistsError, UserService\u003e {\n  return pipe(\n    UserRepository,\n    // ^ Just a Tag linked to an interface, there is no implementation yet.\n    // The use case completely ignores the implementation of the repository,\n    // it just relies on its interface.\n    Effect.flatMap((userRepository) =\u003e userRepository.createUser()),\n  );\n}\n```\n\nHaving that Dependency Inversion Principle applied together with the builtin dependency injection mechanism, we can easily test programs:\n\n```ts\n\nclass InMemoryUserRepository implements UserRepository {\n  createUser() {\n    // \n  }\n}\n\nit(\"Should do something\", async () =\u003e {\n  const user = await Effect.runPromise(\n    pipe(\n      createUser(), \n      Effect.provideService(UserRepository, new InMemoryUserRepository())\n    )\n  );\n  expect(user).toEqual(\"something\");\n});\n\n```\n\nAs we can see, testing is very easy with Effect and it was thought from the ground up to allow an effect description to be decoupled from its implementation details.\n\nNote that this is also beneficial for many other use cases other than testing, for instance changing very easily implementations of a service without breaking code depending on the contract.\n\n## 3. Resilience\n\n\u003cp align=\"left\"\u003e\n    \u003ca href=\"https://github.com/antoine-coulon/effect-introduction/blob/main/src/03-resilience.ts\" target=\"_blank\"\u003e\n      \u003cimg src=\"https://skillicons.dev/icons?i=ts\" width=\"25\" /\u003e\n      Go to source file (03-resilience.ts)\n     \u003c/a\u003e\n\u003c/p\u003e\n\nResilience is the art of designing and implementing software systems that can gracefully and efficiently recover from most types of failures.\n\nWe saw that explicitness and type-safety offered by Effect allow us to erase a whole set of bugs and cleanly deal with errors. \n\n`If it compiles, it works` - slogan in the making. I can confirm that from my Effect experience since one year and a half.\n\nConsequently, Effect is a very powerful datatype, with a deep inference mechanism making Effect programs highly type-safe. It brings the type-safety to a whole new level by using TypeScript in a excellent way.\n\nAs we saw from the [**Explicitness**](#1-explicitness) part, Effect forces us to deal with errors case and forces us to describe computations that are both mathematically correct and make sense from a computer science perspective.\n\nBut most of the time, we don't only want to catch error, we also want to retry with some custom policy, that mostly depend on the system we are targeting and its own constraints.\n\nUsing vanilla TypeScript, we know and saw before how to deal with an error happening, but how can we simply write a retry mechanism for a given computation?\n\nLet's start by writing a dummy use case which is unluckily always failing. Then, we can start writing a little `retry` function that is able to retry the computation X times, nothing fancy there.\n\n```ts\nasync function businessUseCase() {\n  throw new Error();\n}\n\nasync function retry(fn: () =\u003e Promise\u003cvoid\u003e, times = 1): Promise\u003cvoid\u003e {\n  try {\n    await fn();\n  } catch {\n    if (times === 0) {\n      return;\n    }\n    return retry(fn, times - 1);\n  }\n}\n\nretry(businessUseCase, 5);\n```\n\n\nGreat, we are able to retry the computation as many times as we want! But what if we want to retry both X times but also on a specific condition?\n\n\n```ts\nasync function businessUseCase() {\n  const random = Math.random();\n  if (random \u003e 0.9) {\n    throw new Error(\"error_1\");\n  }\n  throw new Error(\"error_2\");\n}\n\nasync function retry(\n  computation: () =\u003e Promise\u003cvoid\u003e,\n  times = 1,\n  shouldRetry: (e: unknown) =\u003e boolean\n): Promise\u003cvoid\u003e {\n  try {\n    await computation();\n  } catch (error) {\n    if (times === 0 || !shouldRetry(error)) {\n      return;\n    }\n    return retry(computation, times - 1, shouldRetry);\n  }\n}\n\nretry(\n  businessUseCase,\n  5,\n  (error) =\u003e error instanceof Error \u0026\u0026 error.message === \"error_2\"\n)\n```\n\nAs we can see, the complexity grows very quickly for simple cases. In most real-world scenarios, we would want to add time delays between retries, bound the retrying with a maximum duration, etc. Writing it all in that fashion would be very tedious and error-prone.\n\nIf we want to combine multiple rules, that is adding a specific debounce of exponential backoff, this would become nearly unmaintainable. \n\nConsequently as the `retry` function gets more specific, we:\n- lose flexibility\n- lose composability \n- lose the ability of having an error specialization\n- increase the complexity\n  \nThankfully, Effect also comes in with a rich set of builtin ways to deal with retry policies, allowing us to combine a lot of different and human readable strategies.\n\nLet's rewrite the code examples with Effect.\n\n```ts\nimport { Effect, Duration, Schedule, pipe } from \"effect\";\n\nconst computationWithFiveRetries = pipe(\n  Effect.fail(new Error(\"Some error\")),\n  // Number of retries\n  Effect.retry({ times: 5 })\n);\n\nconst computationWithRetryUntil = pipe(\n  Effect.sync(() =\u003e Math.random()),\n  Effect.flatMap((random) =\u003e\n    Effect.fail(\n      random \u003e 0.5 ? new Error(\"Forbidden\") : new Error(\"Unauthorized\")\n    )\n  ),\n  // Retry until the condition is met\n  Effect.retry({ until: (error) =\u003e error.message !== \"Forbidden\" })\n);\n```\n \nAnd even more complex ones, combining multiple policies to create one composed policy that:\n- Always recurs, but will wait a certain amount between repetitions using exponential backoff, up until the point where repetitions are bounded to 1 second \n- Recurs while the time elapsed during the whole policy is less than or equal to 30 seconds\n\n```ts\nimport { Duration, Schedule, pipe } from \"effect\";\n\nexport const retrySchedule = pipe(\n  Schedule.exponential(Duration.millis(10), 2.0),\n  Schedule.either(Schedule.spaced(Duration.seconds(1))),\n  Schedule.compose(Schedule.elapsed),\n  Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.seconds(30)))\n);\n```\n\nWe are able to describe a complex retry policy in few lines of code, in a very explicit, elegant and composable way.\n\nNote: one more example is available in the associated TypeScript file (`src/03-resilience.ts`).\n\n**Interruption**\n\nStill in the context of **Resilience**, Effect also allows to deal with interruptions thanks to its concurrency model based on **Fibers**. \n\nI'm not going to expand on what Fibers are in this section because it will be done in the [**Concurrency**](#5-concurrency) section, but very briefly a Fiber is a lightweight concurrency primitive able to run computations in a type-safe, composable and resource-safe way. Fibers are said resource-safe, meaning that they allow us to model **safe resource acquisition and release** in case of interruptions, avoiding memory leaks and letting room for many graceful shutdown/clean up mechanisms.\n\nBeing able to interrupt computations is a very common pattern, let's take for instance `Promise.race`:\n\n```ts\nimport { setTimeout } from \"node:timers/promises\";\n\nPromise.race([\n  setTimeout(1000),\n  setTimeout(10000),\n]);\n```\n\nYou might use `Promise.race` as a convenient way to schedule two asynchronous tasks with the objective of cancelling the loser after the winner settled. Conceptually, racing is a great way of achieving that, the problem being that both computations will be settled letting you think that we are fully done with the tasks, but the truth is that the loser will indeed keep running in the background, without being properly released. \n\nOne better way of doing that would be to use the `AbortController` Web API:\n\n```ts\nimport { setTimeout } from \"node:timers/promises\";\n\nfunction makeRace() {\n  const abortController1 = new AbortController();\n  const abortController2 = new AbortController();\n\n  async function cancellableTimeout1() {\n    await setTimeout(1000, undefined, { signal: abortController1.signal });\n    console.log(\"Aborting timeout 2\");\n    abortController2.abort();\n  }\n\n  async function cancellableTimeout2() {\n    await setTimeout(10000, undefined, { signal: abortController2.signal });\n    console.log(\"Aborting timeout 1\");\n    abortController1.abort();\n  }\n\n  return Promise.race([cancellableTimeout1(), cancellableTimeout2()]);\n}\n```\n\nIn that case, the Abort Controller API provide us a way to ask some asynchronous computation to abort its current execution. One drawback of this approach is that it can be very tedious to implement correctly and the control flow becomes very quickly error-prone. Implementing support for the API can even become leaky itself, we'll see that just after our new example.\n\nLet's take a new example of a simple job running in the background, using a `setInterval` processing literally nothing. \n\nFor that we are going to use API that everyone is most likely used to (`setInterval`), that acquires a resource (a timer) and needs to release it at some point (`clearInterval`) when we finished processing the job after 10 seconds.\n\n```ts\nimport { setTimeout } from \"node:timers/promises\";\n\nasync function backgroundJob() {\n  const processTime = 10_000;\n  const interval = setInterval(() =\u003e {\n    console.log(\"process something...\");\n  }, 500);\n\n  try { \n    await setTimeout(processTime);\n  } finally {\n    clearInterval(interval);\n  }\n}\n```\n\nInternally, we are able to use the classic `try/finally` control flow allowing to model the `use` and `release` actions. Let's see how we are able to cancel the job in itself from the outside world, that is being able to cancel at any point in time the `setInterval`.\n\nIt becomes a bit more tricky when trying to implement support for the Abort Controller API as we must involve it everywhere in the control flow, it can become tedious and error-prone.\n\n```ts\nimport { setTimeout } from \"node:timers/promises\";\n\nasync function backgroundJobWithCancellation(signal: AbortSignal) {\n  const processTime = 10_000;\n\n  if (signal.aborted) {\n    return;\n  }\n\n  let interval: NodeJS.Timer;\n\n  signal.addEventListener(\"abort\", () =\u003e {\n    console.log(\"aborting job, releasing timer resource...\");\n    clearInterval(interval);\n  }, {\n    once: true\n  });\n\n  interval = setInterval(() =\u003e {\n    // process something...\n  }, 1000);\n\n  try {\n    // inherits from the same signal to cancel this timer as well\n    await setTimeout(processTime, undefined, { signal });\n  } finally {\n    clearInterval(interval);\n  }\n}\n```\n\nYou might have noticed it, but if we stop there, we're still leaking memory. In most scenarios, either when receiving a cancellation request through the signal or when the `processTime` requests the end of task, the `setInterval` timer is correctly released. However in the later case, the task just ends without using the provided signal, meaning that the listener callback (on \"abort\" event) will never be called. In that specific case, we are wastefully keeping in memory that listener. Thankfully, `addEventListener` also natively supports the Abort Controller API. \n\n```ts\nimport { setTimeout } from \"node:timers/promises\";\n\nasync function backgroundJobWithCancellation(signal: AbortSignal) {\n  const processTime = 10_000;\n\n  if (signal.aborted) {\n    return;\n  }\n\n  let interval: NodeJS.Timer;\n  const abortController = new AbortController();\n\n  signal.addEventListener(\"abort\", () =\u003e {\n    clearInterval(interval);\n  }, {\n    once: true,\n    // added that\n    signal: abortController.signal,\n  });\n\n  interval = setInterval(() =\u003e {\n    // process something...\n  }, 1000);\n\n  try {\n    await setTimeout(processTime, undefined, { signal });\n  } finally {\n    // abort to release the listener\n    abortController.abort();\n    clearInterval(interval);\n  }\n}\n```\n\nAlso, note that in the case were we receive an \"abort\" signal, we clear the interval two times, ideally we would want avoid releasing something already released as it might produce failures, but in that case clearing a timeout that was already destroyed [does literally nothing](https://github.com/nodejs/node/blob/a40a6c890afe0d1d0ca78db015146178c25af079/lib/timers.js#L184).\n\nFrom the external world if we want to interrupt the computation, we also have to manipulate our own instance of the Abort Controller we then provide in the `backgroundJobWithCancellation` function arguments:\n\n```ts\nasync function main() {\n  const controller = new AbortController();\n\n  // later in time\n  setTimeoutCb(() =\u003e {\n    controller.abort();\n  }, 1000);\n\n  await backgroundJobWithCancellation(controller.signal);\n}\n```\n\nAs we can see with that example, it's very easy to miss some important details in the control flow as adding listeners to manage cancellation is also adding a resource that can originate memory leaks. When nesting and having child computations, you must ensure to **propagate the parent signal in all the computation tree, making it tedious and very hard to maintain properly**.\n\nLet's see now the difference using Effect and how it deals with interruptions. Effect embeds its own way of scheduling timers, the prefered way to do that would be to use `Effect.repeat` (an example is available in the [03-resilience.ts](https://github.com/antoine-coulon/effect-introduction/blob/main/src/03-resilience.ts) source file). \n\nJust so that the example remains simple with the same timer API, let's keep using `setInterval`.\n\n```ts\nconst backgroundJob = pipe(\n  Effect.asyncInterrupt(() =\u003e {\n    const timer = setInterval(() =\u003e {\n      console.log(\"processing job...\");\n    }, 500);\n\n    return Effect.sync(() =\u003e {\n      console.log(\"releasing resources...\");\n      clearInterval(timer);\n    });\n  })\n);\n```\n\n`asyncInterrupt` allows us to describe an asynchronous side-effect while offering us the control over its interruption. The Effect returned in the `asyncInterrupt` will be executed in case `backgroundJob` gets interrupted.\n\nUsing Effect, we have a guarantee that the release Effect returned by the `asyncInterrupt` method will be executed. The interruption model allows us to have a straightforward but also a fine-grained control over interruptibility.\n\nTo simulate an interruption happening, we can manually interrupt the Fiber currently running the background job.\n\n\u003e An Effect is always run in a Fiber. The runtime has always atleast one root Fiber to run Effects. In the case where you want to model concurrent operations, you should favor high-level operators such as `zipPar, forEachPar` because Fibers are low-level constructs so you don't usually need to manipulate them directly. See more in the [Concurrency](#5-concurrency) part.\n\n```ts\npipe(\n  backgroundJob,\n  // Fork the execution of the job in a child Fiber\n  Effect.fork,\n  // Forking gives us a reference to the child Fiber\n  Effect.flatMap((fiberId) =\u003e\n    pipe(\n      // After 2 seconds, we arbitrarily interrupt the child Fiber \n      Fiber.interrupt(fiberId),\n      Effect.delay(Duration.seconds(2))\n    )\n  ),\n  // Run the program\n  Effect.runFork\n);\n```\n\nThis case is trivial, but what's great is that Effect simplifies a lot the way interruptibility is managed through chains of computations with a nice control flow and very rich semantics. As it was shown when using the Abort Controller API, things can become quickly verbose, messy and error-prone as signals must be handled, propagated, \"abort\" event handlers must be cleaned up, etc. On the contrary, the Effect runtime manages all that for us so we just have to manage our cancellation logic. Moreover, that powerful interruptibility management also applies to Layers where our application dependencies live.\n\nNote: more interrupt examples are available in the `src/03-resilience.ts` TypeScript file, involving interruptions of nested computations and different ways of reacting following interruptions.\n\n## 4. Composability\n\nThe art of having a set of reusable software components that can be easily combined, extended, specialized and in a scalable, maintainable and understable way. \n\nEffect `does exactly that`. Thanks to all its primitives and very rich standard library, it allows us to model everything we need on a daily basis.\n\nHopefully at this point you already read everything before that, do I still need to justify the fact that Effect leverages incredibly well composability? :yawning_face:\n\n```ts\nconst ids = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];\n\nconst schedulePolicy = pipe(\n  Schedule.exponential(Duration.seconds(1), 0.5),\n  Schedule.compose(Schedule.elapsed),\n  Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.seconds(5)))\n);\n\nconst listTodos = pipe(\n  TodosRepository,\n  Effect.flatMap((todosRepository) =\u003e\n    pipe(\n      ids,\n      Effect.forEach(\n        (id) =\u003e pipe(todosRepository.fetchTodo(id), Effect.retry(schedulePolicy)),\n        {\n          concurrency: 5\n        }\n      )\n    )\n  ),\n  Effect.tapError(({ id }) =\u003e \n    pipe(`Error fetching ${id}`, Effect.log({ level: \"Error\" }))\n  )\n);\n```\n\nThis little example takes just a bit more than 20 lines of code but it combines:\n\n- requesting access to the `TodosRepository`\n- using that repository to combine a set of 10 operations bounded by a limit of 5 simultaneous operations\n- each operation uses a retry schedule with exponential backoff in case of failure, bounded by a maximum period of 5 seconds after what the retry stops\n\nWhat's great is that the same composition principles smoothly apply on all modules because Effect implements the hard Functional Programming laws under the hood for us. Effect provides us a very rich set of building blocks that we can compose as much as we want, we can reduce or increase abstractions as we want, without having to generalize at the cost of a maintenance burden. See how much I was able to define a specific schedule without having to write any piece of that logic myself. I'm just composing Schedule blocks, providing that in a computation context.\n\nFor instance, the [@effect/schema](https://github.com/Effect-TS/schema) library created by [Giulio Canti](https://github.com/gcanti) integrates super nicely, let's take a look at a simple `TodosRepository` implementation:\n\n```ts\nimport { Effect, Layer, Context, pipe } from \"effect\";\nimport * as S from \"@effect/schema/Schema\";\n\nconst Todo = S.struct({\n  id: S.number,\n  completed: S.boolean,\n});\n\ntype Todo = S.Schema.To\u003ctypeof Todo\u003e;\n\nclass FetchError {\n  readonly _tag = \"FetchError\";\n  constructor(readonly id: number) {}\n}\n\nclass DecodeError {\n  readonly _tag = \"DecodeError\";\n  constructor(readonly id: number) {}\n}\n\ninterface TodosRepository {\n  fetchTodo: (id: number) =\u003e Effect.Effect\u003cTodosRepository, FetchError, Todo\u003e;\n}\n\nconst TodosRepository = Context.Tag\u003cTodosRepository\u003e();\n\nconst TodosRepositoryLive = Layer.succeed(TodosRepository, {\n  fetchTodo: (id) =\u003e\n    pipe(\n      Effect.tryPromise({\n        try: () =\u003e\n          fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(\n            (response) =\u003e response.json()\n          ),\n        catch: () =\u003e new FetchError(id),\n      }),\n      Effect.flatMap(S.decode(Todo)),\n      Effect.mapError(() =\u003e new DecodeError(id))\n    ),\n});\n```\n\nSee how everything integrates well including async operation, schema validation, multiple error mapping in 10 explicit lines of code.\n\nNote also that Effect code can be written with various styles. In most examples here, I'm using the pipeable API, but at some places I'm also using the dual API and the imperative-like syntax with Generators. And you know what? You can even combine these three styles combined altogether (I'll let you settle if it's a good practice on your own).\n  \n## 5. Concurrency\n\nConcurrency is the art of running multiple computations cooperatively to improve the overall speed of the program execution.\n\n`\"concurrency is about dealing with lots of things at once\"`, Rob Pike\n\nNode.js is an example of a runtime leveraging concurrency on a single thread using an Event Loop to cooperatively execute asynchronous task.\n\n**Reminder:** Doing a synchronous operation is faster than doing an asynchronous operation. But when combining multiple operations this is where concurrency becomes interesting.\n\n\nConcurrency is very hard to do right\n\n**Issues with Concurrency**\n\n- Hard to get a deterministic execution model\n- Shared resource problems \n- Deadlocks, resource starvation can occur\n- Memory/CPU efficiency\n- ... many more\n\n\n**The Dining philosophers problem**, introduced in 1965 by Edsger Dijkstra\n\nhttps://en.wikipedia.org/wiki/Dining_philosophers_problem\n\n![bg left](https://user-images.githubusercontent.com/43391199/231962010-90d6020f-f18e-4e9f-83e5-fe33f14532d0.png)\n\n\nEarlier with JavaScript we talked about Promises that could be used to model asynchronous computations.\n\nThe problem: `Promises don't have any builtin way of having a fine-grained control over concurrency`\n\n\nWe can handle concurrency very easily!\n\n:otter: `Promise.all`, `Promise.allSettled`\n\n`Promise.all` and `Promise.allSettled` both allow you to run concurrently X operations but:\n\n- no easy way of having a **bounded concurrency** \n- no resource safety, all other Promises keep being executed in the background in case of failures (even in case of success for `Promise.any` or `Promise.race`)  \n- no easy way of handling interruptions \n\n\n**Bounded vs Unbounded concurrency**\n\n`Bounded` can be used to qualify a limited resource in terms of memory space, memory usage, cpu usage, anything that should be limited (bounded).\n\n- Bounded concurrency is the art of controlling how much operations can run concurrently.\n- Unbounded concurrency is the opposite, that is using `Promise.all` :smile:\n\n\nExample of an `Unbounded concurrency case`\n\n```ts\nconst userIds = Array.from({ length: 1000 }, (_, idx) =\u003e idx);\n\nfunction fetchUser(id: number): Promise\u003cUser\u003e {\n  // \n}\n\n\nfunction retrieveAllUsers() {\n  return Promise.all(\n    userIds.map((id) =\u003e fetchUser(id))\n  );\n}\n```\n\n\nAll Promises were spawned at the same time, blowing up both the Event Loop and the CPU.\n\n![bg left](https://user-images.githubusercontent.com/43391199/231975528-84b45f52-5007-4c39-95c4-6986e1873b6b.jpeg)\n\n\nEffect allows us to control the number of concurrent operations very easily:\n\n```ts\npipe(\n  userIds,\n  Effect.forEach((id) =\u003e Effect.promise(() =\u003e fetchUser(id)), {\n    concurrency: 30\n  })\n);\n```\n\n\nAnd also allows to deal more advanced patterns with built in modules:\n\n- STM (Software Transactional Memory): Transactional Data Structures \u0026 Coordination\n- Semaphore: Concurrency Control\n\n\nPromises: no resource safety/management. Even when the Promise fulfills, the other ones keep running in the background.\nThis can become a problem if the scheduling of leaking Promises is done a lot. It will blow up the CPU and load the Event Loop with unecessary work.\n\n```ts\nfunction quickRunningPromise() {\n  return new Promise((resolve) =\u003e {\n    setTimeout(resolve, 0);\n  });\n}\n\nfunction longRunningPromise() {\n  return new Promise((resolve) =\u003e {\n    setTimeout(resolve, 5000);\n  });\n}\n\nPromise.race([quickRunningPromise(), longRunningPromise()]);\n```\n\n\nEffect are by nature interruptible, meaning that all these operations are resource-safe.\n\n```ts\nconst quickRunningEffect = pipe(\n  Effect.delay(Duration.seconds(1))(Effect.unit)\n);\n\nconst longRunningEffect = pipe(\n  Effect.delay(Duration.seconds(5))(Effect.unit),\n  Effect.onInterrupt(() =\u003e {\n    console.log(\"interrupted!\");\n    return Effect.unit;\n  })\n);\n\nEffect.runCallback(\n  Effect.race(quickRunningEffect, longRunningEffect),\n  () =\u003e {\n    console.log(\"done\");\n  }\n);\n```\n\nOne great thing is the Effect runtime will automatically perform cleanup/release of the underlying tasks once a computation is interrupted.\n\nRemember the previous example?\n\n```ts\nconst interruptibleEffectWithAutoCleanup = Effect.asyncInterrupt(() =\u003e {\n  const timer = setInterval(() =\u003e {}, 1000);\n  // Cleanup/Release function\n  return Effect.sync(() =\u003e {\n    console.log(\"clear interval\");\n    clearInterval(timer);\n  });\n});\n\n```\n\nIf we race something with that Effect and it loses, the cleanup function will be automatically called, by default in the background (asynchronously) or can be a blocking operation.\n\nThere are a lot of features around that, that are out of the scope of the introduction.\n\n\n## 6. Efficiency \u0026 Performance\n\nEfficiency and Performance are both related and unrelated at the same time.\n\n- Performance: refers to how well a task is completed within a given time frame or how quickly a system can complete a task\n- Efficiency: refers to the ratio of the output or result to the resources used to produce it\n\nAchieving both requires careful consideration of tradeoffs and goals.\n\nFor instance, it's easy to nearly blow up the stack while the program is very performant.\n\nRemember the `Promise.all` example? It will most likely execute faster than the Effect version, but the overall Performance of the program will be impacted (other tasks will take longer time) and Efficiency-wise, it's not ideal.\n\n\n**Effect in essence**\n\nBefore going into that subject of `Efficiency \u0026 Performance`, it's important to understand the foundations of Effect.\n\nEffect is in the first place an **Embedded Domain Specific Language (DSL)**. It uses TypeScript (Embedded) to describe a specific set of instructions that will be interpreted by a runtime (Effect Runtime). We call that DSL encoding **initial**.\n\nEffect is _simply_ an `Embedded Domain Specific Language with Initial encoding`! \n\n\nHere is an example of a simple React DSL that helps us build Tables.\n\n```tsx\n  \u003cTable\u003cBrandPerformanceTurnover\u003e\u003e\n    \u003cRow\u003e\n      \u003cCell\u003cBrandPerformanceTurnover\u003e\n        title={'something'}\n        sort=\"enabled\"\n      /\u003e\n    \u003c/Row\u003e\n  \u003c/Table\u003e\n```\n\nUnlike Effect, this Table DSL is using a **Final** encoding meaning that the description is defined in terms of it's direct interpretation. In that case it means that there is a parent component that aims as an interpreter and will introspect all the children. The description is tighted to it's interpretation, not letting any room for multiple interpretations, optimizations and can be unsafe (Tables are not really concerned by that).\n\nIf you want to know more about TypeScript DSLs, [here is an excellent blog post](https://dev.to/effect-ts/building-custom-dsls-in-typescript-29el) from [Michael Arnaldi, the creator of Effect](https://github.com/mikearnaldi)\n\n![bg left 50%](https://user-images.githubusercontent.com/43391199/232013993-da5f9e7e-7317-46eb-bd28-79802890ea99.png)\n\n\nLet's demystify Effect\n\n\n## Effect Systems\n\nEffect is just a description! All data types are used to model a set of computations that represent our program.\n\nOne of the biggest strengths of Effect is that without even executing anything, by just leveraging mathematical concepts and the TypeScript compiler, it is already a proof of whether the program is correct or not.\n\nThe whole purpose of **Effect Systems** that aim to represent side-effectful operations that a program might process at some point. The objective is to have a full control and defer at the most end the execution of all those side effects, when the program was understood by both the compiler and the runtime.\n\nIt lets room for a lot of performance, composability, substitutions, optimizations, type-safety, stack-safety, concurrency...\n\nSee this brilliant article from [John A. De Goes, the creator of ZIO](https://degoes.net/about/): [no-effect-tracking](https://degoes.net/articles/no-effect-tracking)\n\n**Effect Systems**\n\n\n`Functional effect systems like ZIO (and Haskell’s IO data type) let us take side-effects and make them more useful, by turning them into values, which we can transform and compose, solving complex problems with easy and type-safe combinators that simply can’t exist for side-effecting statements`\n\n**John A. De Goes, (creator of ZIO, Effect ancestor)**\n\nNow that we have described our program using the Effect DSL, how to execute the underlying computation?\n\nWe need an **Interpreter**!\n\nLet's do that.\n\n**Effect Runtime**\n\nEffect comes in with a builtin **fiber-based Runtime** that interprets the description. A Fiber is a **lightweight primitive** that deals efficiently with concurrency, scheduling, resource management, interruption etc. It is often refered as a virtual thread / green thread, meaning that it leverages cooperative multitasking using its own computational context that runs independently but that can easily be joined/forked/resumed/stopped/interrupted...\n\nThousands and even millions of Fibers can be spawned and run in a single thread. They are much more lightweight and efficient than operating system threads. Fibers can also be dispatched to be executed within many operating system threads (ZIO uses a threadpool).\n\nAlso:\nGo implements its own native version of green threads using goroutines \nKotlin, Java have their own green threads implementation for instance\nErlang...\nC#...\n\n\nLet's see how to use the built in Effect runtime! **(src/06-runtime.ts)**\n\n\nOther interesting facts about the fiber-based runtime:\n\n- It's stack safe, because it controls the execution of operations, it's able to determine how much operations it can execute on the current tick of the Event Loop. \n- It's memory efficient because after X operations (currently 2048), the fiber yields, letting the Event Loop breath and letting other tasks run.  \n- It's overall faster, because the runtime can combine/batch/eliminate operations and tries to leverage synchronous operations as much as it can. \n\nA Fiber can be thought of as a virtual thread that emulate the same behavior as OS threads with nicer abstractions and without the platform constraints, for instance thousands of virtual threads can be run efficiently in a single-thread allocated to run application code, like Node.js by default (putting worker threads aside). In the context of ZIO, Fibers are scheduled within an OS thread pool running on the JVM. \n\nLike threads, Fibers are low-level constructs so you don't usually need to manipulate them directly. Instead, you can use a very rich set of concurrent primitives directly using Effects, these can be used through options provided to Effect combinators e.g. `Effect.forEach(() =\u003e {}, { concurrency: 30 })`.\n\n\n## 7. Tracing \u0026 Logging\n\nEffect also directly embeds primitives for Logging, Tracing and even Metrics.\n\nIt can be integrated with OpenTelemetry, Prometheus, etc.\n\n\nSummary\n\n- Explicitness :white_check_mark: \n- Testing :white_check_mark: \n- Resilience :white_check_mark: \n- Composability :white_check_mark: \n- Concurrency :white_check_mark: \n- Efficiency \u0026 Performance :white_check_mark: \n- Tracing \u0026 Logging :white_check_mark: \n\nAll that in just one tool, with TypeScript.\n\nThe standard library is very rich and we didn't cover everything there! In addition to the standard library, there is also a set of useful additional packages that you can find in the \"API Reference\" section of the [Effect documentation](https://effect.website)\n\nHere is an overview and non-exhaustive list:\n\n- [@effect/schema](https://github.com/Effect-TS/schema)\n- [@effect/cli](https://github.com/Effect-TS/cli)\n- [@effect/platform](https://github.com/Effect-TS/platform)\n- [@effect/opentelemetry](https://github.com/Effect-TS/opentelemetry)\n- [@effect/printer](https://github.com/Effect-TS/printer)\n  \nAnd many more to come!\n\nOther resources: \n\n- **[Official Effect documentation](https://effect.website/)**\n- **[effect documentation](https://effect-ts.github.io/effect/)**\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantoine-coulon%2Feffect-introduction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fantoine-coulon%2Feffect-introduction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fantoine-coulon%2Feffect-introduction/lists"}