// Functional primitives and conveniences // // Copyright (C) 2014-2023 Ryan Specialty, LLC. // // This file is part of TAME. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Functional primitives and conveniences. //! //! TODO: More information on TAMER's stance on Rust, //! the architecture of the compiler, //! and how that squares with functional programming. //! //! The definitions in this module are _derived from needs in TAMER_. //! This is _not_ intended to be a comprehensive functional programming //! library, //! nor is such a thing desirable. //! //! This module is named `f` rather than `fn` because the latter is a //! reserved word in Rust and makes for awkward (`r#`-prefixed) imports. /// A type providing a `map` function from inner type `T` to `U`. /// /// In an abuse of terminology, /// this functor is polymorphic over the entire trait, /// rather than just the definition of `map`, /// allowing implementations to provide multiple specialized `map`s /// without having to define individual `map_*` methods. /// Rust will often be able to infer the proper types and invoke the /// intended `map` function within a given context, /// but methods may still be explicitly disambiguated using the /// turbofish notation if this is too confusing in context. /// Strictly speaking, /// if a functor requires a monomorphic function /// (so `T = U`), /// then it's not really a functor. /// We'll refer to these structures informally as monomorphic functors, /// since they provide the same type of API as a functor, /// but cannot change the underlying type. /// /// This trait also provides a number of convenience methods that can be /// implemented in terms of [`Functor::map`]. /// /// Why a primitive `map` instead of `fmap`? /// ======================================== /// One of the methods of this trait is [`Functor::fmap`], /// which [is motivated by Haskell][haskell-functor]. /// This trait implements methods in terms of [`map`](Self::map) rather than /// [`fmap`](Self::fmap) because `map` is a familiar idiom in Rust and /// fits most naturally with surrounding idiomatic Rust code; /// there is no loss in generality in doing so. /// Furthermore, /// implementing `fmap` requires the use of moved closures, /// which is additional boilerplate relative to `map`. /// /// [haskell-functor]: https://hackage.haskell.org/package/base/docs/Data-Functor.html pub trait Functor: Sized { /// Type of object resulting from [`Functor::map`] operation. /// /// The term "target" originates from category theory, /// representing the codomain of the functor. type Target = Self; /// A structure-preserving map between types `T` and `U`. /// /// This unwraps any number of `T` from `Self` and applies the /// function `f` to transform it into `U`, /// wrapping the result back up into [`Self`]. /// /// This is the only method that needs to be implemented on this trait; /// all others are implemented in terms of it. fn map(self, f: impl FnOnce(T) -> U) -> Self::Target; /// Curried form of [`Functor::map`], /// with arguments reversed. /// /// `fmap` returns a unary closure that accepts an object of type /// [`Self`]. /// This is more amenable to function composition and a point-free style. fn fmap(f: impl FnOnce(T) -> U) -> impl FnOnce(Self) -> Self::Target { move |x| x.map(f) } /// Map over self, /// replacing each mapped element with `value`. /// /// This is equivalent to mapping with an identity function. /// /// The name `overwrite` was chosen because `replace` is already used in /// core Rust libraries to overwrite and then _return_ the original /// value, /// whereas this function overwrites and then returns /// [`Self::Target`]. /// /// This is intended for cases where there's a single element that will /// be replaced, /// taking advantage of [`Functor`]'s trait-level polymorphism. fn overwrite(self, value: U) -> Self::Target { self.map(|_| value) } /// Curried form of [`Functor::overwrite`], /// with arguments reversed. fn foverwrite(value: U) -> impl FnOnce(Self) -> Self::Target { move |x| x.overwrite(value) } } impl Functor for Option { type Target = Option; fn map(self, f: impl FnOnce(T) -> U) -> >::Target { Option::map(self, f) } } /// A nullary [`Fn`] delaying some computation. /// /// For the history and usage of this term in computing, /// see . pub trait Thunk = Fn() -> T; /// Data represented either as a reference with a `'static` lifetime /// (representing a computation already having been performed), /// or a [`Thunk`] that will produce similar data when invoked. /// /// The purpose of this trait is to force an API to defer potentially /// expensive computations in situations where it may be too easy to /// accidentally do too much work due to Rust's eager argument evaluation /// strategy /// (e.g. see Clippy's `expect_fun_call` lint). /// /// This is sort of like a static [`std::borrow::Cow`], /// in the sense that it can hold a reference or owned value; /// a thunk can return a value of any type or lifetime /// (including owned), /// whereas non-thunks require static references. /// _The burden is on the trait implementation to enforce these /// constraints._ pub trait ThunkOrStaticRef { type Output: AsRef; /// Force the [`Thunk`] or return the static reference, /// depending on the type of [`Self`]. fn call(self) -> Self::Output; } impl, F: Fn() -> R> ThunkOrStaticRef for F { type Output = R; fn call(self) -> R { self() } } impl ThunkOrStaticRef for &'static str { type Output = &'static str; fn call(self) -> &'static str { self } }