Effect 4.0
effect.website
effect.website
I’m working on a little social media web app and it has been so incredibly simple to build the API and a background worker process with Effect. I get logging, telemetry, error handling, concurrency, API doc generation, and more for free!
I think this library gets somewhat maligned for being complex, which I don’t think is fair for how much it offers given how much more complex a an equivalent solution would be without Effect.
With v4, I also don’t feel like I am even having to wrestle with TypeScript’s type system at all. I define some basic interfaces for my services and schemas for my data models and everything is just inferred through usage for the most part. It honestly starts to feel like I’m writing JavaScript for the most part.
This library gets lots of comparisons to functional programming languages/libraries, which is fair, but I think someone working in .NET/C# would be feel right at home.
Rebuild JS's core data structures: https://effect.website/docs/v4/api/effect/Array
Then rebuild the entire JS ecosystem on top of their custom data structures.
And then the main heading on their home page is "Reliable TypeScript for the AI era"? Really? I'll pass.
> Works with JavaScript arrays
a = [1, 2, 3];
foo = effect.Array.append(a, 4);
instead of using what's built in to the language? a = [1, 2, 3];
foo = [...a, 4];"Use when you need to guarantee a non-empty result after adding a required trailing value."
...whatever that means lol
a = [undefined];
res = a.pop() // undefined
a = [];
res = a.pop() // undefinedWithout the type there are a few possibilities: 1. I could forget the assertion and have buggy code without realizing. 2. I could ensure that all uses do the assertion. Even if we can statically know that it is empty. This costs us the check for every call of the function, and ends up being more code.
Alternatively I could statically know in advance. The code won't let me `Array.pop` because that is invalid, so:
1. I *cannot* make that mistake
2. It cost no runtime performance
3. It cost no extra code in a function
1: I say soft-enforce because if someone were to pass a value incorrectly cast as NonEmpty, it would still have a runtime error, but that is a bug elsewhere, not here.but they seem to want to rebuild everything, eg
> The Console service exposes common console methods such as logging, warnings, errors, groups, counters, tables, and timers. Because console access goes through a service, programs can use custom console implementations in tests or other environments. This module also includes scoped helpers that close console groups or timers automatically.
yeah idk mate
Instead, it is a case of the active users of the library saying, "Wow, the Effect way of doing this is so much nicer, can we have everything this way please?"
Console is IO and IO is the effect.
But I only read the news when it comes to npm, so maybe my fear is not justified.
If you look at Effect's package.json on GitHub[1], you'll see that they have several "devDependencies", but no regular "dependencies". That means, unless you are working on the package itself, using it does not require downloading any other packages whatsoever. Thus, Effect is not susceptible to any of the issues that you have read about in the news. The only way malicious code could be transported through the package would be if the maintainers suddenly became evil.
That being said, I don't use Effect and I think it's kind of overrated by the FP community. But, I appreciate the ideas behind it, and I felt the need to educate you about your needless fear of any library simply existing on NPM.
[0] https://unit42.paloaltonetworks.com/monitoring-npm-supply-ch...
[1] https://github.com/Effect-TS/effect/blob/main/package.json
Their main page makes the product pretty clear.
I can't parse this into meaning anything:
"Reliable TypeScript for the AI era
Build production-ready systems your team can ship, customers can depend on, and AI agents can work with."
OK, it's written in TypeScript, I guess. Is it a library? What does it do?
import { Effect } from "effect"
const divide = (a: number, b: number): Effect.Effect<number, Error, never> =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
Effect.runSync(divide(4, 2)) // => 2
This seems to be similar in spirit to railway oriented programming (see e.g. https://returns.readthedocs.io/en/latest/pages/railway.html), but I’m not 100% confident here. type Result e v = Ok v | Err e
https://en.wikipedia.org/wiki/Result_type> Reliable TypeScript for the AI era
It is a library.
Really reminded me of this classic pitch (and yes I know it's not the original turbo encabulator).
> More over, whenever fluorescent score motion is required it may also be employed in conjunction with a drawn reciprocation Dingle arm to reduce sinusoidal depleneration. The Retro Encabulator has now reached a high level of development and it's being successfully used in the operation of Millford Trunions. It's available soon wherever Rockwell Automation products are sold.
And if multiple people are wondering what something fundamentally is, perhaps it's the pitch, not the people.
To be fair "algebraic effects" is part of my priors. If you don't know what that is those specific words won't help you, but the examples should!
But that can't be a library? Must be a framework? (eg more batteries included)
That's not the definition of a framework.
You call a library. A framework calls you.
Anyways, I understood it as a Typescript copy of Scala's ZIO library.
Maybe it's the lack of tacit function calling, the conventions, AI/modern-marketing push, or simply the daunting scope and scale (even more so with v4).
Still, despite my gripes, if you're willing to spend the time and effort, it's the safest and most elegant way to write TypeScript. Plus, it's now dependency-free, and the increasingly good LSP/editor support removes a lot of the friction that used to come with completions and diagnostics.
[1] https://github.com/gcanti/fp-ts [2] https://effect.website/blog/ts-plus-postmortem
import { make, useAtomValue } from "@effect/atom-react"
import { Atom } from "effect/reactivity"
import * as React from "react"
import { renderToStaticMarkup } from "react-dom/server"
const User = make((name: string) => Atom.make(name))
function UserName() {
const atom = User.use()
const value = useAtomValue(atom)
return React.createElement("span", null, value)
}
export function App() {
return React.createElement(
User.Provider,
{ value: "Ada" },
React.createElement(UserName)
)
}
renderToStaticMarkup(React.createElement(App)) // => "<span>Ada</span>"
(src = https://effect.website/docs/v4/api/atom-react/ScopedAtom) import { createContext, useContext, useState } from "react";
import { renderToStaticMarkup } from "react-dom/server";
const UserContext = createContext(null);
function UserProvider({ value, children }) {
const [name] = useState(value);
return <UserContext.Provider value={name}>{children}</UserContext.Provider>;
}
function UserName() {
return <span>{useContext(UserContext)}</span>;
}
function App() {
return (
<UserProvider value="Ada">
<UserName />
</UserProvider>
);
}
renderToStaticMarkup(<App />); // => "<span>Ada</span>"Not sure what benefit this atom stuff brings. In react I can see that `value` is an argument, which from an fp point of view is a clear pro.
It has truly led to shipping more robust programs that are easier to debug. I don’t use it everywhere, but I don’t think twice with larger backend projects.
¹: https://journal.stuffwithstuff.com/2015/02/01/what-color-is-...
²: https://www.scala-lang.org/api/3.5.0/docs/docs/reference/exp...
I don’t see it mentioned here, but 4 also comes with a much simpler API in some modules. Schema is much better. It’s an exceptional update. I followed many of the video logs on YouTube and was impressed by the team. This is one of my favourite software projects, period. This rebuild is incredibly well done, and I hope a lot of people are willing to try it out and adopt it, especially with the LTS.
Eventually I really hope we see programming languages that lean back in to what Erlang started more. This is a leap, but imo having inbuilt great patterns for system design would look a whole lot like Effect! Systems practically assemble themselves. There's great powerful meaningful abstractions under foot.
The only other meaningful effort I can call out is Clojure, which made a handful of good primitives. That community only really used atoms and defs, less refs (software transactional memory) and agents, is what I've been told.
I just don't like these DI systems that pull stuff "out of thin air" auto-magically instead of just passing arguments.
Effect's type-safe DI simplifies application architecture (you now have a clear algebra that is interfaces + layer composition), promotes important architecture principles (e.g. it's a lot easier to compose within the application when components are not tangled because they have to pass "ambient" service props through), and as a result apps are a lot easier to maintain and evolve.
This is the sort of guardrail that really matters in the age of AI, because AI can still mess things up, but the blast radius of messing things up locally is very different than sloppifying architecture—and makes the problem of slop itself more treatable.
One issue with Effect is that it's hard to intro because it does many things. But it does them well, and it does all of them because only a coherent system can deliver the promise of allowing you to write production-ready TypeScript.
I'll list some things:
- Effects: how you describe programs. For some people it helps to think of them as promises on steroids. In addition to tracking the return type, they track the types of errors it could fail with, as well as its dependencies (see context/layers).
- Fibers: primitives for concurrency—but most of the time you don't have to think about them because the `Effect` methods handle most common scenarios
- Context/Layers: type-safe dependency injection. Have your cake and eat it too. You define context interfaces, use them within effects, and TypeScript forces you provide implementations of those interfaces via layers. Layers can provide more than one context, and can also depend on other layers. Very powerful.
- Schema: think Zod, but with the concept of codecs (to be fair Zod has this too), which means you define how things encode (e.g. what API takes and responds with) and decode (e.g. what my app works with). I love `Model`, which derives schemas for the API side and repo (db) side, so I can define a domain model declaratively with things like "account number should be replaced with * except the last 4 digits in the API; it should be Redacted (wrapped, can't leak to logs etc) within the app logic, and should be encrypted in the database".
- Standard library: lots of things that should standard in JS/TS and aren't, or exist but are not coherent or play well together. When I don't use Effect, I have to choose between doing things well or shipping (and suffering the consequences). And I don't mean just strings, dates, decimals. I mean also queues, cache, etc.
- SQL primitives to work with transactions, migrations, queries. There are interfaces on top of this for popular ORMs.
- HTTP server; and ways to define your API with types and schemas, then implement it.
- Telemetry, logging by default
...and I could go on. I am not looking back.
Simple code. Nothing clever, just logic and clear expression of what needs to be done.
It's so pleasant to just write code that does things.
Code that you can put a breakpoint on to debug.
Code that doesn't go through a library abstraction, a framework, a philosophy, three AI agents, a complex type system, metaprogramming and a cross compiler to run.
I understand that maintainable code needs organization, but I think we go off the rails when the systems of organization dominate the work, and the actual execution logic seems secondary.
I see so many teams spend endless cycles debating theories of frameworks, fp vs oop, type systems, AI rigs, etc..., and barely any time at all on solving the actual problem.
Effect solves this elegantly.
> barely any time at all on solving the actual problem
This is how we should measure things TBH. For me, Effect solves very real problems. As a Principal engineer, I sleep so much better overseeing many teams and agents releasing to production because of Effect.
> Code that you can put a breakpoint on to debug.
This is a pain point for sure. For me, the tradeoff is clearly positive (because also the need for debugging that way decreases significantly with more declarative, type-safe, well-architected, telemetry-first code), but it is still very unfortunate.