README.md

# effect

[![Package Version](https://img.shields.io/hexpm/v/effect)](https://hex.pm/packages/effect)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/effect/)

```sh
gleam add effect@1.2.0
```

This library includes an implementation of `Effect` that handles results differently.
See [effect_result](#effect-with-result) for details.

```gleam
import effect

pub fn main() {
  let google: Effect(Result(String, Error)) = {
    use uri <- effect.try_replace_error(
      uri.parse("https://www.google.com"),
      UriParse,
    )
    use req <- effect.try_replace_error(request.from_uri(uri), UriParse)
    use res <- effect.try_await_map_error(fetch.send(req), Fetch)
    use text <- effect.try_await_map_error(fetch.read_text_body(res), Fetch)

    text.body |> Ok |> effect.dispatch
  }

  use res <- effect.perform(google)
  case res {
    Ok(body) -> io.println(body)
    Error(e) -> e |> string.inspect |> io.println_error
  }
}
```

Further documentation can be found at <https://hexdocs.pm/effect>.

## Development

```sh
gleam run   # Run the project
gleam test  # Run the tests
```

# Effect With Result

A lightweight library for modeling asynchronous effects and error handling in Gleam, inspired by [effect-ts](https://github.com/Effect-TS/core). 

This library allows you to treat asynchronous operations and potential failures as **first-class effectful computations**, which can be composed in a purely functional style before finally being executed.

The motivation was to create an API for dealing with promises from [gleam_promises](https://hexdocs.pm/gleam_javascript/gleam/javascript/promise.html) without having to "color" functions.

## Table of Contents

1. [Overview](#overview)  
2. [Usage](#usage)  
   - [Basic Construction](#basic-construction)  
   - [Chaining Effects](#chaining-effects)  
   - [Handling Promises](#handling-promises)  

## Overview

- **Asynchronous & Error-Encoded**: Each `Effect(a, e)` can produce either an `Ok(a)` (success) or an `Error(e)` (failure).  
- **Composable**: Build up complex workflows using `map`, `map_error`, and `try`.  
- **Lifts Promises**: Bridge JavaScript `promise.Promise(Result(a, e))` into the Gleam effect world with `from_promise`, `try_await`, and `try_await_map_error`.  
- **Perform**: Nothing executes until you call `perform(effect, callback)`. This separates definition from execution.

## Usage

Below are common usage scenarios demonstrating how to create and compose effects, handle failures, work with promises, and finally execute the effect.

### Basic Construction

```gleam
import effect/effect_result as effect
import gleam/io

pub fn main() {
  // Construct an effect from a plain Result:
  let eff_from_result = effect.from_result(Ok("Hello"))

  // Succeed / Fail shortcuts:
  let eff_ok = effect.succeed(42)
  let eff_err = effect.fail("Oops!")

  // Perform them:
  effect.perform(eff_from_result, fn(res) {
    case res {
      Ok(str) -> io.println("Got: " <> str)
      Error(err) -> io.println("Error: " <> err)
    }
  })
}
```

### Chaining Effects

Use `try` to sequence operations, only continuing on success, or short-circuiting on error:

```gleam
import effect/effect_result as effect
import gleam/io

type TooSmallError {
  TooSmallError
}

pub fn main() {
  let computation =
    effect.succeed(10)
    // successful effect
    |> effect.try(fn(x: Int) {
      // 
      case x < 5 {
        True -> effect.fail(TooSmallError)
        False -> effect.succeed(x * 2)
      }
    })
    |> effect.map(fn(double: Int) { double + 1 })

  effect.perform(computation, fn(res: Result(Int, TooSmallError)) {
    case res {
      // prints 21 if x=10
      Ok(n) -> io.println("Final result: " <> int.to_string(n))
      // TooSmallError
      Error(_err) -> io.println("Error: " <> "Too small!")
    }
  })
}
```

### Handling Promises

You can convert a `promise.Promise(Result(a, e))` into an `Effect(a, e)` using `from_promise`. Then compose it with `try_await` to sequence a follow-up effect:

```gleam
type FetchError {
  FetchError(String)
}


fn fetch_data() -> promise.Promise(Result(String, FetchError)) {
  // For example, some JavaScript network call
  promise.new(fn(resolve) {
    // resolve(Ok("Server data"))
    resolve(Error(FetchError("Network error")))
  })
}

fn main() {
  // `try_await` waits for Ok(a), then calls `f(a) -> Effect(b, e)`
  let eff =
    effect.try_await(fetch_data(), fn(data) {
      // do something with the data
      effect.from_result(Ok("Got data: " <> data))
    })

  effect.perform(eff, fn(res) {
    case res {
      // Server Data
      Ok(msg) -> io.println(msg)
      // Network Error
      Error(FetchError(msg)) -> io.println("Failed: " <> msg)
    }
  })
}
```

If you need to map the error type *before* continuing, use `try_await_map_error`.