Giter Club home page Giter Club logo

io's Introduction

Effect

Welcome to Effect, a powerful TypeScript framework that provides a fully-fledged functional effect system with a rich standard library.

Requirements

  • TypeScript 5.4 or newer
  • The strict flag enabled in your tsconfig.json file
{
  // ...
  "compilerOptions": {
    // ...
    "strict": true,
  },
}

Documentation

For detailed information and usage examples, please visit the Effect website.

Introduction to Effect

To get started with Effect, watch our introductory video on YouTube. This video provides an overview of Effect and its key features, making it a great starting point for newcomers:

Introduction to Effect

Connect with Our Community

Join our vibrant community on Discord to interact with fellow developers, ask questions, and share your experiences. Here's the invite link to our Discord server: Join Effect's Discord Community.

API Reference

For detailed information on the Effect API, please refer to our API Reference.

Contributing via Pull Requests

We welcome contributions via pull requests! Here are some guidelines to help you get started:

Setting Up Your Environment

Begin by forking the repository and clone it to your local machine.

Navigate into the cloned repository and create a new branch for your changes:

git checkout -b my-branch

Ensure all required dependencies are installed by running:

pnpm install  # Requires pnpm version 9.0.4

Making Changes

Implement Your Changes

Make the changes you propose to the codebase. If your changes impact functionality, please add corresponding tests to validate your updates.

Validate Your Changes

Run the following commands to ensure your changes do not introduce any issues:

  • pnpm codegen (optional): Re-generate the package entrypoints in case you have changed the structure of a package or introduced a new module.
  • pnpm check: Confirm that the code compiles without errors.
  • pnpm test: Execute all unit tests to ensure your changes haven't broken existing functionality.
  • pnpm circular: Check for any circular dependencies in imports.
  • pnpm lint: Ensure the code adheres to our coding standards.
    • If you encounter style issues, use pnpm lint-fix to automatically correct some of these.
  • pnpm dtslint: Run type-level tests.
  • pnpm docgen: Ensure the documentation generates correctly and reflects any changes made.

Document Your Changes

JSDoc Comments

When adding a new feature, it's important to document your code using JSDoc comments. This helps other developers understand the purpose and usage of your changes. Include at least the following in your JSDoc comments:

  • A Short Description: Summarize the purpose and functionality of the feature.
  • Example: Provide a usage example under the @example tag to demonstrate how to use the feature.
  • Since Version: Use the @since tag to indicate the version in which the feature was introduced. If you're unsure about the version, please consult with a project maintainer.
  • Category (Optional): You can categorize the feature with the @category tag to help organize the documentation. If you're unsure about what category to assign, ask a project maintainer.

Changeset Documentation

Before committing your changes, document them with a changeset. This process helps in tracking modifications and effectively communicating them to the project team and users:

pnpm changeset

During the changeset creation process, you will be prompted to select the appropriate level for your changes:

  • patch: Opt for this if you are making small fixes or minor changes that do not affect the library's overall functionality.
  • minor: Choose this for new features that enhance functionality but do not disrupt existing features.
  • major: Select this for any changes that result in backward-incompatible modifications to the library.

Finalizing Your Contribution

Commit Your Changes

Once you have documented your changes with a changeset, it’s time to commit them to the repository. Use a clear and descriptive commit message, which could be the same message you used in your changeset:

git commit -am 'Add some feature'

Linking to Issues

If your commit addresses an open issue, reference the issue number directly in your commit message. This helps to link your contribution clearly to specific tasks or bug reports. Additionally, if your commit resolves the issue, you can indicate this by adding a phrase like ", closes #<issue-number>". For example:

git commit -am 'Add some feature, closes #123'

This practice not only helps in tracking the progress of issues but also automatically closes the issue when the commit is merged, streamlining project management.

Push to Your Fork

Push the changes up to your GitHub fork:

git push origin my-branch

Create a Pull Request

Open a pull request against the appropriate branch on the original repository:

  • main branch: For minor patches or bug fixes.
  • next-minor branch: For new features that are non-breaking.
  • next-major branch: For changes that introduce breaking modifications.

Please be patient! We will do our best to review your pull request as soon as possible.

io's People

Contributors

fubhy avatar gcanti avatar github-actions[bot] avatar imax153 avatar jessekelly881 avatar joshamaju avatar khraksmamtsov avatar krist7599555 avatar mattiamanzati avatar mikearnaldi avatar patroza avatar pigoz avatar schniz avatar sledorze avatar steida avatar sukovanej avatar tim-smart avatar tylors avatar vecerek avatar viztea avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

io's Issues

Feature request: Multi-arg flatMap

It's very common (at least in the code I write) to have multiple Effect.flatMap chains. It would be very useful to extend the flatMap function to help reduce boilerplate.

Effect.flatMap( // Effect<..., C>
    Effect<..., A>
    (a: A) => Effect<..., B>,
    (a: B) => Effect<..., C>,
    // etc...
)

From Discord: Missing Context Derivation in `effect-ts`: Renamed or No Longer Available?

Summary

The context derivation functionality in effect-ts has been removed. There is a discussion about whether it was intentionally removed or if a pull request should be made to bring it back. It is mentioned that accessing and using the context at the same time is not advisable, and it is better to access it as part of layers and use it as part of services. There is a mention of the issues with generics in the old API for context derivation. It is suggested that if the pattern of context derivation is used frequently, it might be worth reconsidering the architecture. There is a suggestion to improve the old derive API by using a proxy pattern, but there is also a concern about potential performance issues with proxies. Overall, it is concluded that if the current usage of context derivation is not causing any major issues, it might be better to keep things as they are.

Example article

Missing Context Derivation in effect-ts: Renamed or No Longer Available?

In the Effect-TS ecosystem, there was a feature called "Context Derivation" that allowed for the extraction of multiple functions at once. However, it has been removed from the library.

The removal of this feature was intentional, as it is not advisable to access and use the same context at the same time. Instead, the recommended approach is to access the context as part of layers (constructors) and use it as part of services.

The main reason for this change is to avoid issues with environment capture that can occur when the context is accessed and used outside of the layer lifecycle. By separating the access and use of the context, it helps to ensure proper encapsulation and prevent potential bugs.

While the old API for context derivation had issues with generics, the new single function approach does not have this problem. Therefore, both approaches are equally advisable.

If you have been using the context derivation pattern extensively, it might be worth reconsidering the architectural design of your code. It is generally recommended to avoid exposing end users to potentially dangerous patterns and provide a more controlled and safer API.

If you have specific use cases where you still find the context derivation pattern useful, you can consider keeping it in your userland code. However, it is important to carefully evaluate the trade-offs and potential risks associated with this approach.

In terms of API improvements, there was a suggestion to enhance the old derive function by using a proxy pattern. However, it is worth noting that proxies can introduce performance issues, so careful consideration should be given to the trade-offs before implementing such a solution.

In conclusion, the missing context derivation feature in effect-ts has been intentionally removed. The recommended approach is to access the context as part of layers and use it as part of services. If you have specific use cases where context derivation is still necessary, you can consider keeping it in your userland code, but it is important to carefully evaluate the trade-offs and potential risks.

Discord thread

https://discord.com/channels/795981131316985866/1140758295662051370

Add `getCached` function to `Cache` module

@IMax153

It would be useful to have a utility function on Cache for "get cached value if it exists in the cache, otherwise none"

Pseudocode for workaround to do this:

const getCached = <T>(someCache, key): Effect<never, never, Option<T>> =>
 pipe(
   someCache.contains(key),
   T.ifEffect(
     //remove E since we know it's cached, the lookup fn won't get called so E is impossible
     pipe(cache.get(key), T.map(O.some), T.catchTags(...)), 
     T.succeedNone()
   )
)

Unify attempt & try

sometimes we use try and attempt with the same meaning e.g. Effect.tryPromise and sometimes Effect.attempt

we should probably standardize to attempt

[Bug] Cause.reduceWithContext

import { Duration, Effect, Stream, pipe } from "effect";

pipe(
  Stream.tick(Duration.decode(1000)),
  Stream.runForEach(Effect.log),
  Effect.runPromise
);
Error: BUG: Cause.reduceWithContext - please report an issue at https://github.com/Effect-TS/io/issues

Feature request: printing metrics

I find myself in situations where I'd like to conveniently access and print metrics in the browser console or terminal when running a program.

I think it would be great if @effect/io exposed a generic pretty printer for metrics that could be used standalone.

Tim is currently working on a metrics module for otel: Effect-TS/opentelemetry#9 that could be used as inspiration.

Configurable path delimiter in ConfigError

I've built myself a simple file-system configuration provider. It works fine but I would like to improve the error messages that are produced when a configuration is not found, supported, or valid. For example, returning a:

Effect.fail(ConfigError.MissingData([...path], "Path not found"));

from the load function of the ConfigProvider would result in the following error message in case of nested configuration:

(Missing data at /secrets.MYSQL_PASSWORD: "Path not found")

I would like to be able to say:

Effect.fail(ConfigError.MissingData([...path], "Path not found", { pathDelim: "/" }));

and get the following instead:

(Missing data at /secrets/MYSQL_PASSWORD: "Path not found")

loop + iterate examples to reflect new api

  • iterate and loop have 'moral equivalent' examples that use the names of the old function arguments
  • the loop example should have a return statement at the end similar to iterate
  • they also use one letter variable names. full variable names provide additional clarity (example from docs recently)
  • finally thoughts on renaming the while option to condition or something similar? while being a language keyword breaks syntax highlighting (you can see this in the api page) and prevents this kind of functionality:
const while = (x: number) => x > 5;
// ^ ERROR "'while' is not allowed as a variable declaration name."
Effect.loop(1, { while, ...otherOptions });

Stack Tracing

The back story of stack tracing is long, but let's start with a quick explainer on what is the goal.

Let's say you have a program like:

import { pipe } from "@effect/data/Function"
import * as Effect from "@effect/io/Effect"

declare const doA: () => void
declare const doB: () => void

export const program = pipe(
  Effect.sync(() => doA()),
  Effect.flatMap(() => Effect.sync(() => doB()))
)

And let's assume we don't expect doA or doB to potentially throw but as we all know we discover in the worst way that they indeed have a bug that blows up.

When you run the code without any tracing facility you will get a plain JS stack trace of where the error is thrown from (that could be deeply nested inside doA) and you then get a bunch of completely useless frames pointing to FiberRuntime.runLoop where the function is invoked from.

By default you don't get any pointer to the Effect.sync(() => doA/B()) calls that and that makes debugging hard.

Historical Solution

  • via typescript transform plugin, add @traced (or similar) to the definition of sync and the code becomes: withTrace(location)(Effect.sync(() => doA()))
  • via typescript transform plugin, add __trace as the last argument and the code becomes Effect.sync(() => doA(), location)

Current Solution

  • all effect functions create a new Error() when invoked and carry that error object inside, to limit costs we both limit the stack frames of the new Error() generated and have a system that once you are in a function that has a trace nested traces are skipped.

Problems with Current Solution

  • when functions are used tacitely the trace is still broken, i.e. pipe(f, Effect.sync, Effect.forever)
  • while new Error() is a JS standard the stack field generation is not and runtimes have different formats
  • it requires source maps to work, without source maps you get messy errors
  • the stackTraceLimit config on the Error prototype is a standard in V8, others are following but there is no guarantee
  • in node you still need to enable source maps explicitly and bare the added cost
  • browsers and bundlers and frameworks complexify everything, even if you get to setup source map correctly the maps are only processed by the dev tools leading to:
    Screenshot 2023-05-22 140814
  • when tracing is fully disabled there is still a ~3x cost due to the region-based logic that enables disabling tracing for nested cases that ends up wrapping code with restore(f)(x)

Advantages

  • in debug / dev mode it usually works out of the box and you get nice and complete error traces
  • with bits of pain you can get good traces in backend
  • no compiler plugin needed, especially not a ts plugin that depends on types

Conclusion

After all the research and feedback from users I am not sure I like the current solution, it doesn't seem to help in the way that it was supposed to help and it is costly (while not too costly, it still adds up).

Path forward

We have been working on native span based tracing (like open telemetry), while it is not aimed at the exact same target we can use span data to provide a stack like message in the error, it requires diligence from the user perspective of writing code like:

import { pipe } from "@effect/data/Function"
import * as Effect from "@effect/io/Effect"

declare const doA = Effect.withSpan("doA")(
  Effect.sync(() => potentiallyFailingA())
)
declare const doB: = Effect.withSpan("doB")(
  Effect.sync(() => potentiallyFailingB())
)

export const program = Effect.withSpan("program")(
  pipe(
    doA,
    Effect.flatMap(() => doB)
  )
)

and any error will include the path to root (cut at n levels) of spans like:

program > doA:
  message bla bla from error
  ... potentially native error stack if present

the same code will also provide full tracing to exporters when configured so that the spans created provide full telemetry for your app code.

Of course one wouldn't need to annotate every single effect but rather "bite sized" blocks of code, it won't point exactly to where the error is generated but will be "close enough" to be debugged without too much effort.

Effect.tapFinally

Hi guys

I'm implementing something like disposing step in my program, like so:

  const whateverRecords = await pipe(
    Effect.tryPromise(() => MongoClient.connect(config.mongodbUrl)),
    Effect.flatMap((client) =>
      pipe(
        Effect.sync(() => client.db()),
        Effect.flatMap((db) =>
          Effect.tryPromise(() =>
            db
              .collection("whatever")
              .find()
              .toArray()
          )
        ),
        Effect.tapBoth(
          () => Effect.tryPromise(() => client.close()),
          () => Effect.tryPromise(() => client.close())
        )
      )
    ),
    Effect.runPromise
  );

is Effect.tapBoth a correct place where I close my connection, the problem here I'm forced to define 2 duplicate functions.
Does it make sense to implement something like Effect.tapFinally, or there is some alternative that I can use more seamlessly?

Thank you.

Feature request: Support using multiple Caches

Allow multiple Caches(e.g. redis, localStorage, in-memory, etc.) to be used simultaneously. Some kind of CacheStrategy API is probably needed for this to work in order to specify where a value should be saved, which Cache should be checked first for a value, possibly moving values between caches(e.g. redis -> memory when accessed more frequently), etc...

See also: https://www.npmjs.com/package/cache-manager

Feature request: tapErrorTag

Like

catchTag("Err", e => /*...*/)

but for tapError.

Useful for logging specific errors. E.g.

Effect.tapErrorTag("RequestError", e => Effect.logError("Error connecting to API"))

Improve error recovery semantics

Currently:

1: catchAll recovers from all errors in case there is at least 1 typed failure, ignoring the rest
2: orElse recovers from error only if there are only typed errors, non recovering on defects and swallowing failures if there are defects (for type safety)

I believe we should never discard defects and revise the semantics to:

1: remove the postfix All from all the recovery
2: catchAll => catch that only recovers if there is a single typed failure, elects the typed failure to Cause.Elected(failure) and rethrows it as a defect
3: remove orElse, equivalent to catch

cc @gcanti we should also rename orElse to catch in the fp-ts types such as Either / Option
cc @IMax153 on the semantics

This will be a divergence from ZIO but I believe ZIO has poor semantics here as it enables for cases where recovery of typed failures ignores defects that could occur in finalization.

Feature request: throwTag, throwTagWith

Similar to orDie or orDieWith but only throws(kills fiber) when a specific error(given the tag) occurs and removes that error from the type. (naming is probably not great..)

`opCodes/configError"' has no exported member 'OP_SOURCE_UNAVAILABLE'.` etc

πŸ› Bug report

Current Behavior

When installing I get a number of TS compilation errors

node_modules/@effect/io/Config/Error.d.ts:94:28 - error TS2694: Namespace '"/blah/node_modules/@effect/io/internal_effect_untraced/opCodes/configError"' has no exported member 'OP_SOURCE_UNAVAILABLE'.

94     readonly _tag: OpCodes.OP_SOURCE_UNAVAILABLE;

Expected behavior

I would expect no compilation errors from the library

Your environment

Didn't try below 0.6.0

Software Version(s)
@effect/schema v0.6.0-v0.8.0
TypeScript 4.9.5

From Discord: Difference in callback parameters for `filterOrFail` and `filterOrDie`

Summary

The user was asking about the difference in callback parameters for filterOrFail and filterOrDie in the Effect-TS library. They wanted to pass the original value in their exception when using these functions. Currently, they need to add a new level of nesting to access the value in the orDieWith callback. The suggestion was made to use Effect.fail or Effect.die in the orElse callback as a workaround. The user found this solution helpful.

Example article

Difference in callback parameters for filterOrFail and filterOrDie

The Effect-TS ecosystem provides two functions, filterOrFail and filterOrDie, that allow you to filter the values of an Effect based on a condition. However, there is a difference in the callback parameters between these two functions.

filterOrFail

The filterOrFail function has the following signature:

filterOrFail: {
  <A, B extends A, X extends A, E2>(
    filter: Refinement<A, B>,
    orFailWith: (a: X) => E2
  ): <R, E>(self: Effect<R, E, A>) => Effect<R, E2 | E, B>
  <A, X extends A, Y extends A, E2>(
    filter: Predicate<X>,
    orFailWith: (a: Y) => E2
  ): <R, E>(self: Effect<R, E, A>) => Effect<R, E2 | E, A>
  <R, E, A, B extends A, X extends A, E2>(
    self: Effect<R, E, A>,
    filter: Refinement<A, B>,
    orFailWith: (a: X) => E2
  ): Effect<R, E | E2, B>
  <R, E, A, X extends A, Y extends A, E2>(
    self: Effect<R, E, A>,
    filter: Predicate<X>,
    orFailWith: (a: Y) => E2
  ): Effect<R, E | E2, A>
} = effect.filterOrFail

The filterOrFail function takes a condition (filter) and a callback function (orFailWith). If the condition is not satisfied for a value in the Effect, the callback function is called with the value as an argument, and the Effect is transformed into an error effect with the result of the callback function as the error value.

Here's an example usage of filterOrFail:

import { Effect } from '@effect-ts/core'
import { filterOrFail } from '@effect-ts/core/Effect'

const isPositive = (n: number) => n > 0

const effectWithFilter = Effect.succeed(10).pipe(
  filterOrFail(isPositive, (n) => new Error(`${n} is not positive`))
)

effectWithFilter.run().then(console.log) // Output: Left(Error: 10 is not positive)

In the above example, the filterOrFail function is used to filter the value 10 based on the condition isPositive. Since 10 is not a positive number, the callback function is called with 10 as an argument, and the Effect is transformed into an error effect with the error value Error: 10 is not positive.

filterOrDie

The filterOrDie function has the following signature:

filterOrDie: {
  <A, B extends A>(
    filter: Refinement<A, B>,
    orDieWith: LazyArg<unknown>
  ): <R, E>(self: Effect<R, E, A>) => Effect<R, E, B>
  <A, X extends A>(filter: Predicate<X>, orDieWith: LazyArg<unknown>): <R, E>(self: Effect<R, E, A>) => Effect<R, E, A>
  <R, E, A, B extends A>(self: Effect<R, E, A>, filter: Refinement<A, B>, orDieWith: LazyArg<unknown>): Effect<R, E, B>
  <R, E, A, X extends A>(self: Effect<R, E, A>, filter: Predicate<X>, orDieWith: LazyArg<unknown>): Effect<R, E, A>
} = effect.filterOrDie

The filterOrDie function also takes a condition (filter), but instead of a callback function, it takes a lazy argument (orDieWith). If the condition is not satisfied for a value in the Effect, the lazy argument is evaluated and the Effect is transformed into a die effect with the result of the lazy argument as the die value.

Here's an example usage of filterOrDie:

import { Effect } from '@effect-ts/core'
import { filterOrDie } from '@effect-ts/core/Effect'

const isPositive = (n: number) => n > 0

const effectWithFilter = Effect.succeed(10).pipe(
  filterOrDie(isPositive, () => new Error('Value is not positive'))
)

effectWithFilter.run().then(console.log) // Output: Left(Die: Error: Value is not positive)

In the above example, the filterOrDie function is used to filter the value 10 based on the condition isPositive. Since 10 is not a positive number, the lazy argument is evaluated and the Effect is transformed into a die effect with the die value Error: Value is not positive.

Conclusion

Both filterOrFail and filterOrDie functions provide a way to filter values in an Effect based on a condition. The main difference lies in the callback parameters they accept. filterOrFail allows you to provide a callback function that is called with the value when the condition is not satisfied, while filterOrDie allows you to provide a lazy argument that is evaluated when the condition is not satisfied.

Discord thread

https://discord.com/channels/795981131316985866/1144172722826514493

Bug: Functions that work with tagged error fail at type level when non-tagged error present

Functions such as catchTag, etc. require that E extends {_tag: string } which is not always the case(e.g. when using Error). For example, the following fails:

const fails = pipe(
  Effect.fail<TestError | Error>(new TestError()),
  Effect.catchTag("TestError", () => Effect.succeed("")) // type error, Error doesn't extend { _tag: string }
);

The following is a more flexible type def:

<K extends (E extends { _tag: string } ? E["_tag"] : never), E>

It behaves identically for tagged errors while allowing non-tagged errors to pass through w/o type errors.

https://codesandbox.io/p/sandbox/compassionate-shtern-lqt2l5

Feature Request - Add Pretty Print for `Exit` Data Type

Description

Current Behavior

Currently, the Exit data type lacks a user-friendly pretty print representation, making it difficult for developers to quickly understand the result of an Effect operation.

Feature Request

It would be beneficial to have a built-in pretty print function for the Exit data type, similar to what is available for the Either data type. This will greatly improve the developer experience by providing a more human-readable output when working with Effect operations.

Example

For instance, currently, when using the Effect.runSync function with Effect.either, the output is nicely formatted and easy to understand:

console.log(Effect.runSync(Effect.either(Effect.succeed(1))));
/* Output:
{ _tag: 'Right', right: 1 }
*/

However, when using Effect.exit with the same Effect operation, the output becomes more complex and less user-friendly:

console.log(Effect.runSync(Effect.exit(Effect.succeed(1))));
/* Output:
EffectPrimitiveSuccess {
  _tag: 'Success',
  i0: 1,
  i1: undefined,
  i2: undefined,
  trace: undefined,
  [Symbol(@effect/io/Effect)]: { _R: [Function: _R], _E: [Function: _E], _A: [Function: _A] }
}
*/

Function arguments

I've always felt the inner need to compact code but I am not that sure it's the best in all the cases, we should have a bit of a "vote" about:

import { pipe } from "@effect/data/Function"
import * as Effect from "@effect/io/Effect"

const randomResult = Effect.suspend(() => Math.random() > 0.5 ? Effect.fail("err") : Effect.succeed("ok"))

// current

export const program1 = Effect.matchCauseEffect(
  randomResult,
  (cause) => Effect.logErrorCause(cause),
  (str) => Effect.logInfo(str)
)

export const program1pipeable = pipe(
  randomResult,
  Effect.matchCauseEffect(
    (cause) => Effect.logErrorCause(cause),
    (str) => Effect.logInfo(str)
  )
)

// proposed

export const program2 = Effect.matchCauseEffect2(randomResult, {
  onFailure: (cause) => Effect.logErrorCause(cause),
  onSuccess: (str) => Effect.logInfo(str)
})

export const program2pipeable = pipe(
  randomResult,
  Effect.matchCauseEffect2({
    onFailure: (cause) => Effect.logErrorCause(cause),
    onSuccess: (str) => Effect.logInfo(str)
  })
)

and generally about any function that takes >1 arguments except the self.

From Discord: Missing Stack Trace in EffectA

Summary

In the discussion, the user mentioned that there seems to be a missing stack trace in EffectA. They referred to an issue on the Effect-TS/io GitHub repository where the problem was discussed. The user also asked if it is recommended to have a span for each effect. The response was that it is not necessary for each effect, but rather for each logical block.

The user then asked about the update that introduced this change and mentioned that they still don't see the stack trace. The response was that the stack trace functionality was gradually introduced over a few minor updates, starting with span tracing and then cause augmentation. The user was advised to use runExit and Cause.pretty to see the stack trace. If they still face issues, they were encouraged to post a reproducible example as an issue on the GitHub repository.

The user further mentioned that Cause.pretty only gives them the top-level span and asked if it is possible to get a hierarchy of all spans. The response was that Cause.pretty should provide all spans from the root.

Finally, the user mentioned that they investigated and found that catchTag seems to remove child spans, and they provided a failing example.

Key takeaways:

  • Stack trace functionality was introduced gradually over multiple updates.
  • runExit and Cause.pretty can be used to see the stack trace.
  • Cause.pretty should provide all spans from the root.
  • If there are issues, it is recommended to post a reproducible example as an issue on the GitHub repository.

Example article

Missing Stack Trace in EffectA

Introduction

In the Effect-TS ecosystem, the EffectA type is used to represent effectful computations. However, there have been reports of missing stack traces when using EffectA. This article aims to provide an overview of the issue and potential solutions.

Issue Description

The missing stack trace issue in EffectA arises when there are no spans defined for the effects. A span represents a logical block of code and is used to capture the stack trace information. Without spans, it becomes difficult to trace the execution flow and identify the source of errors.

Solution

To address the missing stack trace issue, it is recommended to use spans for each logical block of code in your EffectA computations. Spans can be added using the span function provided by the @effect/io library.

Here's an example of how to add spans to an EffectA computation:

import { span, EffectA } from '@effect/io'

const myEffect: EffectA<unknown, Error, number> = span('myEffect', () => {
  // Your effectful computation here
  return 42;
});

By adding spans to your EffectA computations, you will be able to capture the stack trace information and get more detailed error messages.

Cause Augmentation

In addition to adding spans, the @effect/io library also provides cause augmentation to enhance error reporting. Cause augmentation allows you to attach additional information to the error cause, such as spans, to provide a more detailed error trace.

To access the augmented cause and pretty print it, you can use the runExit function and the Cause.pretty method.

Here's an example of how to use runExit and Cause.pretty:

import { runExit, Cause } from '@effect/io'

const result = runExit(myEffect);

if (result._tag === 'Failure') {
  console.error(Cause.pretty(result.cause));
}

By using runExit and Cause.pretty, you can obtain the augmented cause and print it to the console, which will include the spans and other relevant information.

Conclusion

The missing stack trace issue in EffectA can be resolved by adding spans to your EffectA computations using the span function. Additionally, cause augmentation can be used to enhance error reporting by attaching spans and other information to the error cause.

If you encounter any issues or have further questions, please don't hesitate to open an issue on the Effect-TS/io GitHub repository.

Happy coding with Effect-TS!

Discord thread

https://discord.com/channels/795981131316985866/1144234525971460146

From Discord: Converting Either to Effect in ZIO: ZIO.fromEither

Summary

The discussion revolved around the idea of having a fromEither function in the Effect-TS ecosystem that would transform an eager Either into a lazy Effect. The motivation behind this was to provide a more explicit and beginner-friendly way to convert from Either to Effect.

Some key takeaways from the discussion are:

  • Either is already an eager value, while Effect is a lazy computation.
  • There is no inherent need to convert Either to Effect, as Either can be used directly in many cases.
  • If you do need to convert Either to Effect, you can use Effect.flatten(Effect.sync(() => either)).

The main benefit of having a fromEither function would be syntactic convenience and explicitness, especially for beginners. However, it's important to consider the trade-offs and ensure that the conversion is done intentionally and in the appropriate context.

Background

The Either type represents a value that can be either a success (Right) or a failure (Left). On the other hand, Effect is a monadic data type that represents a computation that can produce a value, have side effects, or fail.

Converting Either to Effect

Approach 1: Using Effect.flatten and Effect.sync

One way to convert an Either value to an Effect is by using Effect.flatten and Effect.sync. Here's an example:

import { Effect } from "@effect-ts/core"
import { Either, right } from "@effect-ts/core/Classic/Either"

const eitherValue: Either<Error, string> = right("foo")

const effectValue = Effect.flatten(Effect.sync(() => eitherValue))

In this approach, we use Effect.sync to wrap the Either value inside a synchronous effect. Then, we use Effect.flatten to flatten the nested effect and obtain the final Effect value.

Using .fromEither has a few benefits:

  1. Simplicity: It provides a more concise and expressive way to convert an Either value to an Effect without the need for additional flattening operations.

  2. Beginner-Friendly: The .fromEither function makes the intention of the conversion explicit and is easier for beginners to understand.

Conclusion

In this article, we explored different approaches to convert an Either value to an Effect. While both approaches achieve the desired conversion, using .fromEither provides a more concise and beginner-friendly solution. Consider using .fromEither when working with Either values to simplify your code and improve readability.

Discord thread

https://discord.com/channels/795981131316985866/1146411021607641191

Effect.all what to do

We've been playing with the idea of a single Effect.all api for a while but I can't seem to be able to make it work well for both the pipeable and non pipeable cases.

I have a few options:

  • restrict Effect.all to use overloads such that direct usage works like: Effect.all([a. b]), Effect.all(a, b), Effect.all({ a, b }) and add Effect.iterable(x) for Iterable<Effect> => Effect<Array>
  • hope and pray that TS improves inference of overloads (in the iteration plan) knowing that there's a good chance that it won't land as it may break half the world (but also may not, and would be the best design)
  • rollback to our old nomenclature where we distinguish between tuple / struct and iterables

From Discord: Proposing a `Effect.flatMapError` Function

Summary

The proposed flatMapError function would take an effect self and a mapping function fn that transforms the error type E to a new effect. The function signature can be either:

function flatMapError<R, E, A, R2, E2>(self: Effect<R, E, A>, fn: (e: E) => Effect<R2, never, E2>): Effect<R | R2, E2, A>

or

function flatMapError<R, E, A, R2, E2, E3>(self: Effect<R, E, A>, fn: (e: E) => Effect<R2, E2, E3>): Effect<R | R2, E2 | E3, A>

The key takeaway is that the flatMapError function allows you to handle errors from an effect and transform them into a new effect. This can be useful for error recovery or for chaining effects that may produce different error types.

Example article

Proposing a Effect.flatMapError Function

The Effect.flatMapError function is a proposed addition to the Effect-TS ecosystem. It allows you to handle errors in a more flexible way by providing a mapping function that transforms the error type of an effect.

Function Signature

The function signature of Effect.flatMapError is as follows:

function flatMapError<R, E, A, R2, E2>(self: Effect<R, E, A>, fn: (e: E) => Effect<R2, never, E2>): Effect<R | R2, E2, A>

This function takes an effect self with an error type E, and a mapping function fn that takes an error of type E and returns a new effect with an error type E2. The resulting effect has the same success type A as the original effect.

Example Usage

Here's an example of how you can use Effect.flatMapError:

import { Effect } from '@effect/io'

// An effect that may fail with an error
const effect: Effect<unknown, string, number> = Effect.fail('Oops!')

// A mapping function that transforms the error type
const mappingFn = (error: string) => Effect.succeed(error.length)

// Using flatMapError to handle the error
const result = Effect.flatMapError(effect, mappingFn)

// Running the effect and handling the result
result.run().then(console.log) // Output: 5

In this example, the effect can fail with an error of type string. We define a mapping function mappingFn that takes the error message and returns a new effect that succeeds with the length of the error message. We use Effect.flatMapError to apply the mapping function to the original effect, resulting in a new effect with the transformed error type. Finally, we run the effect and handle the result.

Handling Eventually Raised Errors

One question that arises is how Effect.flatMapError should handle eventually raised errors from the effect returned by the mapping function. One approach is to force the effect to never raise errors by using never as the error type. This ensures that any errors raised by the mapping function are treated as defects and cause the effect to fail.

import { Effect } from '@effect/io'

const effect: Effect<unknown, string, number> = Effect.fail('Oops!')

const mappingFn = (error: string) => Effect.fail(`Mapping error: ${error}`)

const result = Effect.flatMapError(effect, mappingFn)

result.run().catch(console.error) // Output: Mapping error: Oops!

In this example, the mapping function mappingFn returns an effect that fails with a new error message. Since we use never as the error type for the effect returned by mappingFn, any errors raised by that effect are treated as defects and cause the overall effect to fail.

Conclusion

The proposed Effect.flatMapError function provides a convenient way to handle errors in a flexible manner by allowing you to transform the error type of an effect. This can be useful in scenarios where you need to handle errors in a specific way or perform additional computations based on the error.

Discord thread

https://discord.com/channels/795981131316985866/1144164954526715964

Schedule.jittered requires Random.Random

Actual signature:

export declare const jittered: <Env, In, Out>(self: Schedule<Env, In, Out>) => Schedule<Env | Random.Random, In, Out>;

Expected return type:

Schedule<Env, In, Out>

Unexpected timeout behaviour

From ZIO documentation (https://zio.dev/reference/error-management/recovering/timing-out/#ziotimeout)

If the effect is uninterruptible it will be blocked until the original effect safely finished its work, and then the timeout operator produces the None value

Repro:

import * as Effect from "@effect/io/Effect"
 
const program = Effect.log("start doing something.").pipe(
  Effect.flatMap(() => Effect.sleep("2 seconds")),
  Effect.flatMap(() => Effect.log("my job is finished!")),
  Effect.as("result")
)
 
Effect.runPromise(
  Effect.uninterruptible(program).pipe(Effect.timeout("1 seconds"))
).then(console.log)
/*
... message="start doing something."
{ _tag: 'None' }
... message="my job is finished!"
*/

Actual Output:

... message="start doing something."
{ _tag: 'None' }
... message="my job is finished!"

Expected Output:

... message="start doing something."
... message="my job is finished!"
{ _tag: 'None' }

Invalid Type for Tags with Layers

I'm currently trying to create a simple Express Server with Layers and such, but currently I'm getting thrown an annoying type error, where The Context.Tag doesn't comply with what Layer expects.

import * as Effect from "@effect/io/Effect";
import * as Context from "@effect/data/Context";
import * as Layer from "@effect/io/Layer";
import express, { type Express } from "express";

const App = Context.Tag<Express>();

export const AppLive = Layer.succeed(App, express());

Screenshot from 2023-07-14 21-41-20

My current versions of @effect/io and @effect/data are:

"@effect/data": "^0.15.0",
"@effect/io": "^0.32.2"

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.