175 lines
6.5 KiB
Rust
175 lines
6.5 KiB
Rust
// 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 <http://www.gnu.org/licenses/>.
|
||
|
||
//! 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<T, U = T>: 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<T, U> Functor<T, U> for Option<T> {
|
||
type Target = Option<U>;
|
||
|
||
fn map(self, f: impl FnOnce(T) -> U) -> <Self as Functor<T, U>>::Target {
|
||
Option::map(self, f)
|
||
}
|
||
}
|
||
|
||
/// A nullary [`Fn`] delaying some computation.
|
||
///
|
||
/// For the history and usage of this term in computing,
|
||
/// see <https://en.wikipedia.org/wiki/Thunk>.
|
||
pub trait Thunk<T> = 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<T: ?Sized> {
|
||
type Output: AsRef<T>;
|
||
|
||
/// Force the [`Thunk`] or return the static reference,
|
||
/// depending on the type of [`Self`].
|
||
fn call(self) -> Self::Output;
|
||
}
|
||
|
||
impl<T: ?Sized, R: AsRef<T>, F: Fn() -> R> ThunkOrStaticRef<T> for F {
|
||
type Output = R;
|
||
|
||
fn call(self) -> R {
|
||
self()
|
||
}
|
||
}
|
||
|
||
impl ThunkOrStaticRef<str> for &'static str {
|
||
type Output = &'static str;
|
||
|
||
fn call(self) -> &'static str {
|
||
self
|
||
}
|
||
}
|