One of the best way to learn about a library is to read how other people used it.
These are some of the key points you should look out for in an effect codebase.
Start from simple services
Understanding a new effect codebase from the main entry point is challenging.
Take the main
function we implemented in this course:
import { Effect, Layer, ManagedRuntime } from "effect";
import { BuildPokeApiUrl } from "./BuildPokeApiUrl";
import { PokeApi } from "./PokeApi";
import { PokeApiUrl } from "./PokeApiUrl";
import { PokemonCollection } from "./PokemonCollection";
const MainLayer = Layer.mergeAll(PokeApi.Live);
const PokemonRuntime = ManagedRuntime.make(MainLayer);
export const program = Effect.gen(function* () {
const pokeApi = yield* PokeApi;
return yield* pokeApi.getPokemon;
});
const main = program.pipe(
Effect.catchTags({
FetchError: () => Effect.succeed("Fetch error"),
JsonError: () => Effect.succeed("Json error"),
ParseError: () => Effect.succeed("Parse error"),
})
);
PokemonRuntime.runPromise(main).then(console.log);
main
collects together all the services, layers, and runtime used in the app.
Instead, it's easier to understand a single service with no dependencies:
import { Context, Layer, type Array } from "effect"; // 👈 Only using effect core
export class PokemonCollection extends Context.Tag("PokemonCollection")<
PokemonCollection,
Array.NonEmptyArray<string>
>() {
static readonly Live = Layer.succeed(this, ["staryu", "perrserker", "flaaffy"]);
}
PokemonCollection
has no dependency on any other part of the codebase. Therefore it's possible to understand this service in isolation.
This should be your starting point to understand an effect codebase.
Bottom-up approach
Every effect codebase has at least services and layers. These are the building blocks of the effect composition model.
Therefore, it helps to find and understand services in isolation.
After you understand the role of a single service, you are more prepared to understand how they are combined to construct the full app.
Check installed packages
Many effect projects use a variety of effect ecosystem packages.
For example, this website contains the following inside package.json
:
"dependencies": {
"@effect/platform": "^0.58.20",
"@effect/platform-node": "^0.53.19",
"@effect/rpc": "^0.31.20",
"@effect/rpc-http": "^0.29.20",
"@effect/schema": "^0.68.17",
/// ...
}
This gives you a good idea of how the app is structured and what features implements.
Focus on a specific function
Effect helps in organizing your code. Nonetheless, it's not possible to erase the complexity of an app.
The first step is getting an idea of each services and how they combine. After that you'll need to read the actual implementation if you are looking to contribute or learn.
Here is an example from the docgen
package:
const getFunctionDeclarationSignature = (
f: ast.FunctionDeclaration
): string => {
const text = f.getText()
return pipe(
Option.fromNullable(f.compilerNode.body),
Option.match({
onNone: () => text.replace("export function ", "export declare function "),
onSome: (body) => {
const end = body.getStart() - f.getStart() - 1
return text
.substring(0, end)
.replace("export function ", "export declare function ")
}
})
)
}
It's not easy to understand the function in isolation without having an idea of the feature that it implements. Nonetheless, if you understand how Option
works (fromNullable
and match
), then it's all about figuring out the purpose of this specific function in the whole codebase.