nami 0.11.0

A powerful, lightweight reactive framework.
Documentation
//! # Map Module
//!
//! This module provides transformation and memoization capabilities for reactive values.
//!
//! The `Map` type enables you to transform values from one type to another while preserving
//! the reactive nature of the computation. It automatically caches the result of the transformation
//! for better performance, invalidating the cache only when the source value changes.
//!
//! ## Usage Example
//!
//! ```rust
//! use nami::{binding, Binding, Signal};
//! use nami::map::map;
//!
//! let number: Binding<i32> = binding(5);
//! let doubled = map(number, |n: i32| n * 2);
//!
//! assert_eq!(doubled.get(), 10);
//!
//! // The transformation is automatically cached
//! doubled.get(); // Uses cached value, doesn't recompute
//! ```

use core::{marker::PhantomData, panic::Location};

use crate::{Signal, SignalIdentity, watcher::Context};

/// A reactive computation that transforms values from a source computation.
///
/// `Map<C, F, Output>` applies a transformation function `F` to the results
/// of a source computation `C`, producing a value of type `Output`. The result
/// is automatically cached and only recomputed when the source value changes.
#[derive(Debug)]
pub struct Map<C, F, Output> {
    source: C,
    f: F,
    discriminator: usize,
    _marker: PhantomData<Output>,
}

impl<C, F, Output> Map<C, F, Output>
where
    C: Signal,
    F: 'static + Clone + Fn(C::Output) -> Output,
    Output: 'static, // Prevents confusing `does implement Signal` errors, but fast fail.
{
    /// Creates a new `Map` that transforms values from `source` using function `f`.
    ///
    /// # Parameters
    ///
    /// * `source`: The source computation whose results will be transformed
    /// * `f`: The transformation function to apply to the source's results
    ///
    /// # Returns
    ///
    /// A new `Map` instance that will transform values from the source.
    #[track_caller]
    pub fn new(source: C, f: F) -> Self {
        Self {
            source,
            f,
            discriminator: SignalIdentity::call_site_discriminator::<(F, Output)>(
                Location::caller(),
            ),
            _marker: PhantomData,
        }
    }
}

/// Helper function to create a new `Map` transformation.
///
/// This is a convenience wrapper around `Map::new()` with improved type inference.
///
/// # Parameters
///
/// * `source`: The source computation whose results will be transformed
/// * `f`: The transformation function to apply to the source's results
///
/// # Returns
///
/// A new `Map` instance that will transform values from the source.
///
/// # Example
///
/// ```rust
/// use nami::{binding, Binding, Signal};
/// use nami::map::map;
///
/// let counter: Binding<i32> = binding(1);
/// let doubled = map(counter, |n: i32| n * 2);
/// assert_eq!(doubled.get(), 2);
/// ```
#[track_caller]
pub fn map<C, F, Output>(source: C, f: F) -> Map<C, F, Output>
where
    C: Signal + 'static,
    Output: 'static,
    F: 'static + Clone + Fn(C::Output) -> Output,
{
    Map::new(source, f)
}

impl<C: Clone, F: Clone, Output> Clone for Map<C, F, Output> {
    fn clone(&self) -> Self {
        Self {
            source: self.source.clone(),
            f: self.f.clone(),
            discriminator: self.discriminator,
            _marker: PhantomData,
        }
    }
}

impl<C, F, Output> Signal for Map<C, F, Output>
where
    C: Signal,
    F: 'static + Clone + Fn(C::Output) -> Output,
    Output: 'static,
{
    type Output = Output;
    type Guard = C::Guard;

    /// Computes the transformed value, using the cache when available.
    fn get(&self) -> Output {
        (self.f)(self.source.get())
    }

    fn identity(&self) -> Option<SignalIdentity> {
        self.source
            .identity()
            .map(|identity| identity.with_discriminator(self.discriminator))
    }

    /// Registers a watcher to be notified when the transformed value changes.
    fn watch(&self, watcher: impl Fn(Context<Self::Output>) + 'static) -> Self::Guard {
        let this = self.clone();

        self.source.watch(move |context| {
            let context = context.map(|value| (this.f)(value));
            watcher(context);
        })
    }
}

impl_signal_ops!(Map<C, F, Output>, [C, F, Output], Output);