Reading the effect-ts docs, and it’s basically a Result and Option with many helper functions, a way to compose functions, with type-safety (as much as you can get in TypeScript). If you’ve ever used Folktale, Sanctuary, or true-myth, you’ll immediately know the basics. The run API reminds me of Python’s async io or Scala’s ZIO.
Unlike the other libraries, though, you get TypeScript type safety, with training wheels to learn how to stop using exceptions (e.g. throw) and embrace returning errors as values. Like any good ML style, they differentiate between the different type of I/O with types so you know “this side-effect, console.log and some other stuff, will never fail” (safe) vs. “I’m going to fetch something… then parse the response…” (unsafe).
Like RxJS, or Bluebird, it has all the helper functions to integrate with sync, async, callbacks, and to transform between.
Sadly, because of the type safety, interfaces, and the various support for running different types of effect workflows, like RxJS, they have to create their _own_ pipelines. Given the challenges of JavaScript promise API’s, and the alpha state of the JavaScript pipeline proposal, they provide good function composition API’s you’d expect from a library giving you typed functional programming. The only downside for beginners is that you’ll have to box and unbox things for yourself; it’s not all flatMapped, which makes sense from a type-safety perspective, but beginners may want to brush up on JavaScript Promises first, then Array concat and flat + flatMap as a warm up.
Unlike a long JavaScript promise chain, instead of re-running the code over and over, commenting out various sections with logs/taps, you have this thing called a “TypeScript compiler” to help you identify what’s coming in and what should go out. That’s what a lot us Functional Programmers were missing, hence using tools like ReScript, Elm, and true-myth. Some utilize JSDocs, but… they still talk openly to Midna.
Speaking of tap, yes, it comes with a typesafe tap function.
I should point out here that by default, all errors short circuit the pipe, just like a Promise chain does, so you’ll feel right at home. That, and they’re lazy by default; although, I’m too new to know if they’re not eager by default like JavaScript Promises.
Effect is also another brave enough to throw their hat into the ring, claiming they can possibility resurrect generators in JavaScript, and have people _desire_ to use them vs. “oh god, not the star functions again…”. They clothed it to look like async/await. While I applaud their effort, if you’re using async/await syntax, you probably aren’t too keen to change your imperative style to functional style, and thus probably not going to use it with Effect-ts. But who knows, maybe someone will use that to write an imperative Seq (e.g. infinite array, called Sequence, F#’s is pretty nice).
Their use of _ symbol for holes will immediately be familiar, but their use of it as a function parameter that you _actually_ use later will not. Be wary. Cool convention, it just may throw you off if you’re coming from FP languages.
If you’re on drugs, check out Effect.loop. Someone smart asked “How can we make a for loop deterministic and palatable to Functional Programmers?” they then smoked crack and did a good job.
There are hints of pattern matching throughout. It’s not true pattern matching like the JavaScript proposal, or what you’re used to in languages like ReScript and Rust, but it _is_ there, like in Effect.exit; FORCING developers to handle the un-happy path. A few of their helper types include a match function which users of Folktale should feel right at home.
Given this is TypeScript, and thus JavaScript, it’s impossible to trap all errors. Despite not giving you types for it, Effect-TS _still_ captures them, and that’s a pretty dope safety feature that any FP developer will love.
Sadly, like everything in TypeScript, making simple types requires a lot of syntax, and sometimes you have to define things twice. Such is the case in custom errors in their docs. They’ve followed the standard of making a class in JavaScript, which is common for those creating classes that extend Error to pattern match between the different error types. I’d prefer a simple TypeScript Discriminated Union (which is verbose compared to other functional languages unions, but… it’s still a Union). However, I respect their decision to use interface instead of type, and class instead of function/object to extend an olive branch to the curious OOP devs. Being friendly is good. Still, there _are_ reasons they’re using _tag symbols as you’ll read later in the docs, and having this metadata is helpful to provide helpful compiler errors as well as various filters that run behind the scenes throughout pipelines (e.g. catchTags).
To be clear, it doesn’t have to be “_tag”; TypeScript just needs a differentiator; something unique for each of your interface or types, so the compiler can tell the difference vs. using the 1990’s style of “instanceof” which isn’t type safe.
Speaking of unions, further down, they _do_ show if you’re doing an operation where multiple occurs could occur, you can use a simple union type in the type def for whatever comes out of your effect chain which _is_ more normal and extremely helpful.
Rather then put all the onus on the programmer to create “as safe code as you can possibly create in TypeScript”, they provide a catchAll method, thank god.
They also have cancelable Promises called “Fibers“. If you’re familiar with Folktale’s Tasks, they’ll look familiar, although the engineering behind it is more than just “a Promise I can stop and clean up resources afterwards”. They’ve also chosen the immediate abort within reason which will make you not freak out about possible mutable state. They handle all the concurrency bits too if you’re doing many sequential things (e.g. Promise.then.then.then ) as well as parallel (e.g. Promise.all).
The Scope is nice in that it provides a common way to handle state-full resources. In Folktale Tasks, when your Promise was completed, whether it succeeded or failed, you could run a cleanup function. This is useful for things like closing database connections, saving files, flushing log results, moving iterators forward, etc. Effect-TS has incorporated BOTH types, not just releasing the resource, but getting it as well. For example, 90% of us programmers do NOT care if closing a Postgres connection actually works, especially if you’re in AWS Lambda for example. But connecting? Absolutely. These sometimes get complex too because they’re error prone, change per environment, etc. I’m not sure I dig the implementation yet, but I love the idea.
For those of us building libraries, they provide the basic logging details between all the pipes you’d expect so you can forward to a logger, like Winston/Pino/Bunyan that is the forwarded to CloudWatch/ElasticSearch/Splunk, etc.
I find it interesting that Effect-TS supports both data-first and data-last. It’s data-last like I’d expect for anything FP trying to be friends with a JavaScript developer. However, many of their API’s support data-first, AND data-last… which they call “dual”. While it looks like partial application, they just call it “overload”. I have a feeling most people will use data-last to be more explicit, despite TypeScript helping you get the curried function right, but whatever.
For some reason, they author(s) weren’t done making new friends with this library, so they also made it their mission to help stop primitive obsession as well. You can’t natively create distinct nominal types in TypeScript like you can in Elm or ReScript without making an unreadable mess that no one will write, and then just revert to using primitives everywhere. So Effect-TS created Branded types. Now 2 strings passed to a function will always be in the right order. Simple stuff, nice add-on feature, but not really related to effects, but SUPER important for type-safety. Not complaining, just interesting to see this “additional unrelated helper that’s awesome”.
Not going to say much on their do notation beyond herculean effort, well done giving your language constraints.
I was impressed to see a Jitter in the scheduling portion. For those of us UI developers who endeavor NOT to create retry-storms and accidental DDoS attacks when dealing with flaky API’s we’re forced to call, it’s nice to have that safety feature just built-into the library. Useful on the API side as well, sure, you just rarely see it on the UI side.
It’s not all sunshine and FP rainbows, though… this is JavaScript. So Effect-TS provides a Ref to update mutable state, and share it between various Fibers. If mutable state is your thing, it can be super helpful. Sadly it’s against my religion.
Their batching and concurrency controls are quite nice, but I get Angular vibes, and not good ones, with the amount of code and dependencies required just to setup an HTTP call. Having it type-safe with clear reasons why something failed is WORTH writing all that code for, no doubt, but you can get it with a lot less in ReScript or Elm, they just don’t have any of the concurrency/batching batteries included.
Obviously, Jesse Warden is going to be super irate when I see this young project having such wonderful documentation, but the “Testing” part is “TODO”. In my 20’s I’d be fine with it, it’s clear quality is baked in, but while I haven’t verified they have a test suite, I’m pretty strict nowadays about a testing culture, and so it’s super disappointing this part isn’t front and center.
It’s clear there’s a Scala influence because of the heavy FP/Zio vibes, which are great, and then the free use of the class keyword. FP zealots like myself ban that from their religion, but Scala devs have spent over a decade working with JVM code, so class is “a fine and accepted data type” for them. For use non-Scala FP’ers, class is NOT a datatype (DTO’s don’t count). So I’ve tried to keep that in mind when reading their docs that 99% of it could change interface to type, and class to {}.
If you’re an FP coder forced in TypeScript, Effect-TS is worth a look. If you’re new to Functional Programming, Effect-TS gives you all the basic primitives you need which are:
- a way to stop using Exceptions
- a way to model safely the absence of things (null/undefined)
- a way to build programs by composing functions together
- and do it in a type-safe way vs. un-typed JavaScript Promises
There’s lots more to come based on their docs and repo.