FreeApPath
FreeApPath<F, A> wraps FreeAp<F, A> for building applicative DSLs.
Unlike FreePath, operations in FreeApPath can be analyzed and potentially
executed in parallel because they don't depend on each other's results.
- Creating FreeApPath instances
- Difference from FreePath
- Static analysis of programs
- Parallel execution
- When to use (and when not to)
The Key Difference
FreePath (monadic): Each operation can depend on previous results.
FreeApPath (applicative): Operations are independent; results combine at the end.
// FreePath: second operation depends on first
FreePath<F, String> monadic = getUser(id).via(user ->
getOrders(user.id())); // Sequential: must wait for user
// FreeApPath: operations are independent
FreeApPath<F, Summary> applicative =
getUser(id).zipWith(getOrders(id), Summary::new); // Parallel-safe!
Creation
// Pure value
FreeApPath<ConfigOp.Witness, String> pure = Path.freeApPure("default");
// Lift an operation
FreeApPath<ConfigOp.Witness, String> dbUrl =
Path.freeApLift(new GetConfig("database.url"));
Core Operations
FreeApPath<ConfigOp.Witness, String> host = getConfig("host");
FreeApPath<ConfigOp.Witness, Integer> port = getConfig("port").map(Integer::parseInt);
// Combine independent operations
FreeApPath<ConfigOp.Witness, String> url =
host.zipWith(port, (h, p) -> "http://" + h + ":" + p);
// Map over results
FreeApPath<ConfigOp.Witness, String> upper = host.map(String::toUpperCase);
Static Analysis
Because operations are independent, you can analyze programs before running them:
// Collect all config keys that will be requested
Set<String> getRequestedKeys(FreeAp<ConfigOp.Witness, ?> program) {
return program.analyze(op -> {
GetConfig config = ConfigOpHelper.narrow(op);
return Set.of(config.key());
}, Monoids.set());
}
FreeApPath<ConfigOp.Witness, DbConfig> program =
getConfig("db.host")
.zipWith(getConfig("db.port"), DbConfig::new);
Set<String> keys = getRequestedKeys(program.run());
// Set.of("db.host", "db.port")
This enables:
- Validation before execution
- Optimization (batching, caching)
- Documentation generation
- Dependency analysis
Parallel Execution
Interpreters can exploit independence for parallelism:
// Sequential interpreter
NaturalTransformation<ConfigOp.Witness, IO.Witness> sequential =
op -> IO.of(() -> loadConfig(op.key()));
// Parallel interpreter (batch all requests)
Kind<IO.Witness, Config> parallel = program.run().foldMap(
batchingInterpreter,
ioApplicative
);
Running Programs
FreeApPath<ConfigOp.Witness, DbConfig> program =
getConfig("host").zipWith(getConfig("port"), DbConfig::new);
// Get the FreeAp structure
FreeAp<ConfigOp.Witness, DbConfig> freeAp = program.run();
// Interpret
Kind<IO.Witness, DbConfig> io = freeAp.foldMap(interpreter, ioApplicative);
// Execute
DbConfig config = IOKindHelper.narrow(io).unsafeRunSync();
When to Use
FreeApPath is right when:
- Operations are independent (don't depend on each other's results)
- You want to analyze programs before running (static analysis)
- Parallel/batched execution is beneficial
- Building configuration loaders, query builders, validation pipelines
FreeApPath is wrong when:
- Operations depend on previous results → use FreePath
- You don't need static analysis or parallelism
- Simpler direct effects suffice → use IOPath
// Define config operations
FreeApPath<ConfigOp.Witness, String> dbHost = getConfig("db.host");
FreeApPath<ConfigOp.Witness, Integer> dbPort = getConfig("db.port").map(Integer::parseInt);
FreeApPath<ConfigOp.Witness, String> dbName = getConfig("db.name");
// Combine into complete config (all three fetched independently)
FreeApPath<ConfigOp.Witness, DbConfig> dbConfig =
dbHost.zipWith3(dbPort, dbName, DbConfig::new);
// Analyze: what keys are needed?
Set<String> keys = analyze(dbConfig); // {db.host, db.port, db.name}
// Execute: fetch all in parallel/batch
DbConfig config = run(dbConfig, parallelInterpreter);
Applicative is less powerful than Monad (you can't use previous results to decide the next operation), but this limitation is a feature: it enables static analysis and parallelism that monads cannot provide.
- Free Applicative - Underlying type for FreeApPath
- FreePath - Monadic variant for dependent operations
- Applicative - The Applicative typeclass
Previous: FreePath Next: Composition Patterns