Effect-ts: why doesn’t T.chain chain for me?

Viewed 152

I have what I think is a simple program:

import * as T from "@effect-ts/core/Effect";
import { pipe } from "@effect-ts/core/Function";
import { tag } from "@effect-ts/core/Has";

interface ConsoleModule {
  log: (message: string) => T.UIO<void>;
}
const ConsoleModule = tag<ConsoleModule>();
const log = (m: string) =>
  T.accessServiceM(ConsoleModule)((console) => console.log(m));
const program = pipe(
  log("hello"),
  T.chain(() => log("world"))
);

pipe(
  program,
  T.provideService(ConsoleModule)({
    log: (message) =>
      T.effectAsync(() => {
        console.log(message);
      }),
  }),
  T.run
);

But it writes only “hello” and not “world“. I think I am missing something very basic about how chain (or perhaps how pipe) is supposed to function

1 Answers

TLRD: in this case, using T.succeedWith instead of T.effectAsync will make the program work as you expect it to.

Based on the question's code, your understanding of pipe and chain is fine. The problem here is your usage of T.effectAsync. If you look up its definition things will surely become clearer:

Imports an asynchronous side-effect into a pure Effect value. [...] The callback function must be called at most once.

For instance, this would work:


T.effectAsync(cb => {
    setTimeout(() => {
      console.log(message)
      cb(T.unit) // if you were looking to return something, this could be a T.succeed("value") or a T.fail("some error")
    }, 1000)
  })

In case you're starting with Effect-TS, and considering documentation is scarce, my usual recommendation is to look through the tests and actual source code for answers.

Related