# Widening and Narrowing — Environment Transformations
Sometimes your effect needs *part* of an environment, but you have the whole thing. Or you need to thread an effect through a context that provides more than required. This is where `zoom_env`, `contramap_env`, and capability subtyping come in.
## The Mismatch Problem
Imagine your application needs several capabilities:
```rust
// Effect needs only LoggerKey
fn log_event(msg: &str) -> Effect<(), LogError, caps!(LoggerKey)> { ... }
// Caller has DatabaseKey + LoggerKey + ConfigKey
fn process(data: Data) -> Effect<(), AppError, caps!(DatabaseKey, LoggerKey, ConfigKey)> { ... }
```
You can't call `log_event` inside `process` without adapting the environment — the `R` types don't match. You need to *narrow* or *widen*.
## zoom_env: Narrow the Environment
`zoom_env` adapts an effect to work with a *larger* environment by providing a lens from the larger type to the smaller one:
```rust
// Adapt log_event to work with a struct holding logger + other fields
let app_log = log_event("hello").zoom_env(|env: &AppEnv| &env.logger);
```
Now `app_log` has type `Effect<(), LogError, AppEnv>`. The function extracts the `Logger` from `AppEnv` and feeds it to the original effect.
Inside `effect!`, the pattern looks like:
```rust
fn process(data: Data) -> Effect<(), AppError, caps!(DatabaseKey, LoggerKey, ConfigKey)> {
effect!(|r| {
~ log_event("start").zoom_env(|e| extract_logger(e)).map_error(AppError::Log);
~ db_query(data).zoom_env(|e| extract_db(e)).map_error(AppError::Db);
Ok(())
})
}
```
## contramap_env: Transform the Environment
While `zoom_env` narrows, `contramap_env` transforms. It applies a function to convert whatever environment the caller provides into what the effect actually needs:
```rust
// Effect needs a raw string URL
fn connect(url: &str) -> Effect<Database, DbError, String> { ... }
// You have a Config that contains the URL
let with_config = connect_raw.contramap_env(|cfg: &Config| cfg.db_url.clone());
// Now type is Effect<Database, DbError, Config>
```
`contramap_env` is the formal name for "adapt the environment type." In practice, most code uses `zoom_env` for the common case of extracting a field.
## caps! and automatic subtyping
For capability DI, prefer [`caps!`](../../src/capability/set.rs) and the `~` bind operator inside `effect!`. A wider runtime environment satisfies a narrower `R`: every [`CapList`](../../src/capability/set.rs) shares one [`Env`](../../src/capability/env.rs); [`cap_into_bind`](../../src/capability/cap_bind.rs) clones that env and verifies the inner keys (see ADR 0005).
```rust
use id_effect::{Effect, caps, effect, run_with, provide};
fn query(id: u64) -> Effect<User, DbError, caps!(DatabaseKey)> {
effect!(|r| {
let db = ~DatabaseKey;
db.fetch_user(id)
})
}
fn log_event(msg: &str) -> Effect<(), LogError, caps!(LoggerKey)> {
effect!(|r| {
let log = ~LoggerKey;
log.info(msg);
})
}
fn app() -> Effect<User, AppError, caps!(DatabaseKey, LoggerKey)> {
effect!(|r| {
~log_event("start");
~query(42)
})
}
run_with(
[provide!(DatabaseLive), provide!(LoggerLive)],
app(),
)?;
```
**Automatic binding with `~`:** when the inner effect needs any single key from the outer `caps!(…)` list, `effect!` expands `~inner(...)` to [`cap_into_bind`](../../src/capability/cap_bind.rs) — no manual projection.
**Capability lookup:** `~DatabaseKey` inside `effect!` borrows that capability from `r` (same as [`require!(DatabaseKey)`](../../src/capability/require.rs)).
**Implicit `|r|`:** write `effect!(|r| { … })`; Rust infers `&mut caps!(…)` from the enclosing function's `Effect<_, _, caps!(…)>` return type. You still declare `caps!(…)` once on the signature.
**Outside `effect!`**, narrow explicitly with [`CapWiden::widen`](../../src/capability/set.rs) or [`project_at_*`](../../src/capability/set.rs):
```rust
let wide: caps!(DatabaseKey, LoggerKey) = /* from build_env or run_with */;
let narrow: caps!(DatabaseKey) = wide.widen();
run_blocking(query(1), narrow)?;
```
When an inner function declares `caps!(DatabaseKey)` and the caller holds `caps!(DatabaseKey, LoggerKey)`, the compiler checks that every required key is present — no positional tuple indexing, no runtime downcasts.
## R as Documentation Revisited
These combinators highlight why `R` is valuable as documentation. When you see:
```rust
fn log_event(msg: &str) -> Effect<(), LogError, caps!(LoggerKey)>
```
You know *exactly* what this function needs. You don't need to read its body to see if it also touches the database. Adaptation at the call site stays explicit.
Compare to the pre-effect alternative:
```rust
// Traditional: you'd need to read the body to know what `env` is used for
fn log_event(env: &AppEnv, msg: &str) -> Result<(), LogError> { ... }
```
With `R`, the function declares what it needs. With `zoom_env` or `CapWiden`, the caller declares how to satisfy it.
## When to Use These
In practice, `zoom_env` and `contramap_env` appear most often in *library code* — when writing reusable utilities that should work with any environment containing the right piece. Application code typically uses [`caps!`](../../src/capability/set.rs) / [`CapWiden`](../../src/capability/set.rs) and named capability keys (Chapters 5–6), which avoid the need for explicit projection.
Think of `zoom_env` as the manual fallback when automatic capability subtyping isn't the right fit.