This document explains the fundamental concepts in deep_causality_core, the foundational crate for causal computation
in DeepCausality.
deep_causality_core provides a monadic framework for building causal reasoning systems. At its heart is the idea
that causality can be modeled as a functional dependency:
E₂ = f(E₁)
Where an effect E₂ is produced by applying a causal function f to an incoming effect E₁. The core crate
provides types and traits to:
- Propagate effects through chains of causal functionsl
- Handle errors automatically (short-circuiting)
- Maintain audit logs for explainability
- Support interventions for counterfactual reasoning
- Carry state and context for Markovian processes
A monad is a design pattern from functional programming that allows composable, chainable computations. In DeepCausality, the monad pattern enables:
- Composition: Chain multiple causal steps with
bind - Error Propagation: Errors automatically short-circuit subsequent steps
- Logging: Every operation appends to an audit trail
- Type Safety: The compiler enforces correct usage
| Operation | Description |
|---|---|
pure(value) |
Lifts a plain value into the monadic context |
bind(f) |
Chains a computation, passing value/state/context to f |
intervene(new_value) |
Forces a new value mid-chain (for counterfactuals) |
This is the fundamental type in deep_causality_core. It's a 5-arity container that unifies:
| Field | Type Parameter | Description |
|---|---|---|
value |
V |
The primary data being transformed (wrapped in EffectValue<V>) |
state |
S |
Mutable state that evolves through the chain (Markovian) |
context |
C |
Read-only configuration or environment data |
error |
E |
Error state that short-circuits further computation |
logs |
L |
Append-only audit history |
In complex causal reasoning, we need more than just a value:
- Value: The data flowing through the pipeline
- State: Information that accumulates (e.g., counters, risk scores)
- Context: Global configuration that doesn't change (e.g., thresholds)
- Error: Failure information that preserves logs even when computation fails
- Logs: Complete history for explainability and compliance
To simplify common use cases, two type aliases are provided:
pub type PropagatingEffect<T> =
CausalEffectPropagationProcess<T, (), (), CausalityError, EffectLog>;Use when:
- You don't need state across steps
- You don't need external context
- You want simple, functional transformations
Example:
use deep_causality_core::PropagatingEffect;
let result = PropagatingEffect::pure(10.0)
.bind( | val, _, _ | PropagatingEffect::pure(val.into_value().unwrap() * 2.0))
.bind( | val, _, _ | PropagatingEffect::pure(val.into_value().unwrap() + 5.0));
// Result: 25.0pub type PropagatingProcess<T, S, C> =
CausalEffectPropagationProcess<T, S, C, CausalityError, EffectLog>;Use when:
- You need to accumulate state (Markov property)
- You need global configuration
- You're building complex, multi-stage pipelines
Example:
use deep_causality_core::PropagatingProcess;
#[derive(Clone, Default)]
struct RiskState {
total_risk: f64
}
let process: PropagatingProcess<f64, RiskState, () > =
PropagatingProcess::pure(0.5);EffectValue wraps the actual data being propagated. It's an enum with variants for different scenarios:
| Variant | Description |
|---|---|
None |
Absence of a value (like Option::None) |
Value(T) |
A concrete value of type T |
ContextualLink(id1, id2) |
Reference to data in a Context |
RelayTo(index, effect) |
Dispatch command for adaptive routing |
Map(HashMap) |
Collection of named sub-effects |
// Extracting the inner value
let effect = PropagatingEffect::pure(42);
let inner: Option<i32> = effect.value.into_value();Every CausalEffectPropagationProcess carries an EffectLog that records:
- Timestamped entries for each operation
- Intervention markers (when
intervene()is called) - Error messages when failures occur
- Explainability: Trace back why a result was reached
- Auditability: Compliance-ready record of decisions
- Debugging: Understand flow through complex graphs
use deep_causality_haft::LogAddEntry;
let mut log = EffectLog::new();
log.add_entry("Step 1: Validated input");
log.add_entry("Step 2: Applied transformation");The Intervenable trait enables intervention, a key operation for counterfactual analysis.
In causal inference, an intervention forces a variable to a specific value, breaking its natural dependencies. This
is Pearl's do() operator.
pub trait Intervenable<T> {
fn intervene(self, new_value: T) -> Self;
}Behavior:
- Replaces the current value with
new_value - Preserves the log history (adds intervention marker)
- Propagates any existing error (intervention can't fix errors)
use deep_causality_core::{Intervenable, PropagatingEffect};
// Factual: Natural causal chain
let factual = PropagatingEffect::pure(0.8)
.bind( | x, _, _ | PropagatingEffect::pure(x.into_value().unwrap() * 2.0));
// Result: 1.6
// Counterfactual: What if the input had been 0.2?
let counterfactual = PropagatingEffect::pure(0.8)
.intervene(0.2) // Force value to 0.2
.bind( | x, _, _ | PropagatingEffect::pure(x.into_value().unwrap() * 2.0));
// Result: 0.4
// Causal effect = Factual - Counterfactual = 1.2CausalMonad implements the MonadEffect5 trait from deep_causality_haft, providing:
| Method | Description |
|---|---|
pure(value) |
Lifts a value into CausalEffectPropagationProcess |
bind(process, f) |
Chains a function, handling errors and logs |
DeepCausality uses HKT patterns via the deep_causality_haft crate. The CausalMonad works with:
Effect5trait: Declares the 5-arity effect structureMonadEffect5trait: Providespureandbind- Witness types:
PropagatingEffectWitness,PropagatingProcessWitness
This enables generic programming over different effect types while maintaining type safety.
Errors in deep_causality_core use CausalityError:
pub struct CausalityError(pub CausalityErrorEnum);
pub enum CausalityErrorEnum {
InternalLogicError,
Custom(String),
// ... other variants
}When an error occurs:
- The error is stored in the
errorfield - Subsequent
bind()calls skip execution - Logs are preserved up to the error point
- The final result contains the error and complete log
let result = PropagatingEffect::pure(10)
.bind( | _, _, _ | PropagatingEffect::from_error(
CausalityError::new(CausalityErrorEnum::Custom("Failed".into()))
))
.bind( | x, _, _ | {
// This never executes!
PropagatingEffect::pure(x.into_value().unwrap() * 2)
});
// result.error is Some(...)
// result.logs contains entries up to failureA common pattern is to unwrap EffectValue and error if it's None:
let result = PropagatingEffect::pure(Some(42))
.bind_or_error( | val, _, _ | {
// val is already unwrapped from EffectValue!
PropagatingEffect::pure(val * 2)
}, "Expected a value, got None");This avoids manual pattern matching on every step.
For complex graphs, deep_causality_core provides a builder pattern:
use deep_causality_core::{ControlFlowBuilder, ExecutableNode, NodeType};
let graph = ControlFlowBuilder::new()
.add_node(ExecutableNode::new(0, NodeType::Start, my_start_fn))
.add_node(ExecutableNode::new(1, NodeType::Process, my_process_fn))
.add_edge(0, 1)
.build();This is useful for constructing explicit causal graphs at runtime.
| Concept | Type/Trait | Purpose |
|---|---|---|
| Core Container | CausalEffectPropagationProcess |
5-arity monad (V, S, C, E, L) |
| Stateless Alias | PropagatingEffect<T> |
Simple functional chains |
| Stateful Alias | PropagatingProcess<T, S, C> |
Markovian processes |
| Payload | EffectValue<T> |
Wrapped value with dispatch variants |
| Audit Log | EffectLog |
Timestamped operation history |
| Monad Impl | CausalMonad |
pure and bind operations |
| Intervention | Intervenable |
Counterfactual do() operator |
| Errors | CausalityError |
Short-circuiting error propagation |
- See examples/starter_example for Pearl's Ladder demonstration
- See examples/core_examples for more
PropagatingEffectpatterns - See deep_causality_haft for the HKT foundation