fp-core

repository·master·Indexed 23 days ago

https://github.com/jasonshin/fp-core.rs

A library for functional programming in Rust (version 0.1.9) providing purely functional data structures and tools to supplement the Rust Standard Library. It includes implementations for Functors, Monads, Comonads, Applicatives, Lenses, and various morphisms, as well as utilities for function composition via the compose! macro and support for Lightweight Higher Kinded Types.

Tokens
8.4K
Snippets
32
Records
52
Agent score
81%

What's inside fp-core

  1. What is a Lens and how to use it

    master

    A Lens is a type that pairs a getter and a non-mutating setter for a data structure. It allows you to view and update specific parts of a structure immutably.

    Lenses provide three primary capabilities:

    1. get: Retrieves a reference to a part of the structure.
    2. set: Returns a new version of the structure with the specified value updated.
    3. over: Applies a function to the value retrieved by the getter and returns a new version of the structure with the result.

    Lenses are also composable, allowing you to perform immutable updates to deeply nested data by composing multiple lenses together.

    trait Lens<S, A> {
        fn over(s: &S, f: &Fn(Option<&A>) -> A) -> S {
            let result: A = f(Self::get(s));
            Self::set(result, &s)
        }
        fn get(s: &S) -> Option<&A>;
        fn set(a: A, s: &S) -> S;
    }
  2. Define Lambdas in Rust

    master

    Lambdas (or anonymous functions) are functions defined without a name that can be treated as values. They are frequently passed as arguments to Higher-Order Functions.

    let closure_annotated = |i: i32| { i + 1 };
    let closure_inferred = |i| i + 1;
  3. Implement runtime contracts for functions

    master

    Contracts specify the obligations and guarantees of a function's behavior at runtime. You can implement a contract as a function that returns a boolean, then use it within your main logic to validate inputs and return a Result if the contract is violated.

    let contract = | x: &i32 | -> bool {
        x > &10
    };
    
    let add_one = | x: &i32 | -> Result<i32, String> {
        if contract(x) {
            return Ok(x + 1);
        }
        Err("Cannot add one".to_string())
    };
    
    // Usage
    match add_one(&11) {
        Ok(x) => assert_eq!(x, 12),
        _ => panic!("Failed!")
    }
  4. Understand Higher Kinded Types (HKT) via Lightweight HKT

    master

    Rust does not natively support Higher Kinded Types (types with a 'hole', like trait Functor<F<A>>). To work around this, you can use a pattern called Lightweight Higher Kinded Type.

    This involves defining a trait that maps a type to its 'Target' (the type after applying a transformation).

    pub trait HKT<A, B> {
        type URI;
        type Target;
    }
    
    // Example: Lifting Option
    impl<A, B> HKT<A, B> for Option<A> {
        type URI = Self;
        type Target = Option<B>;
    }
  5. Use Higher-Order Functions (HOF)

    master

    A Higher-Order Function is a function that either takes one or more functions as arguments or returns a function as its result.

    let filter = | predicate: fn(&i32) -> bool, xs: Vec<i32> | {
        xs.into_iter().filter(predicate).collect::<Vec<i32>>()
    };
    
    let is_even = |x: &i32| { x % 2 == 0 };
    
    filter(is_even, vec![1, 2, 3, 4, 5, 6]);
  6. Implement and use a Comonad

    master

    A Comonad is the dual of a Monad. It is an object that implements both Extend and Extract traits.

    • extract: Takes a value out of the comonad.
    • extend: Runs a function on the Comonad, returning a new Comonad.

    Example usage with Option:

    Some(1).extract(); // 1
    Some(1).extend(|co| co.extract() + 1); // Some(2)
  7. Understand Monoids

    master

    A Monoid is a set equipped with two components:

    1. A binary function (often called combine or append) that takes two elements of the set and returns another element of the same set.
    2. An identity value that, when combined with any element x, results in x.

    To be a valid Monoid, the operation must be associative: (a + b) + c == a + (b + c).

    Common Examples:

    • Addition: Set is numbers, function is +, identity is 0.
    • Array Concatenation: Set is arrays, function is .concat(), identity is [].
    • Functions: If identity and compose functions are provided, functions themselves form a monoid.
  8. Check for Idempotency

    master

    A function is idempotent if applying it multiple times to the same input produces the same result as applying it once. This is a key property for many functional operations like sorting or absolute value calculations.

    let abs = | x: i32 | -> i32 { x.abs() };
    let x: i32 = 10;
    let result = abs(abs(x));
    assert_eq!(result, x);
  9. Implement a Setoid

    master

    A Setoid is a set with an equivalence relation. To implement Setoid, an object must provide an equals function that satisfies:

    1. Reflexivity: a.equals(a) == true
    2. Symmetry: a.equals(b) == b.equals(a)
    3. Transitivity: If a.equals(b) and b.equals(c), then a.equals(c)

    Note: Rust's standard Eq trait resembles the Setoid specification.

    trait Setoid {
        fn equals(&self, other: &Self) -> bool;
    }
    
    impl Setoid for Vec<i32> {
        fn equals(&self, other: &Self) -> bool {
            self.len() == other.len()
        }
    }
    
    assert_eq!(vec![1, 2].equals(&vec![1, 2]), true);
  10. Implement and use an Applicative

    master

    An Applicative functor is an object that implements Apply (providing the ap function) and Pure (providing the of function).

    • of: Lifts a value into the applicative context (also known as pure or return).
    • ap: Applies a function wrapped in an applicative context to a value wrapped in another applicative context of the same type.
    let x = Option::of(Some(1)).ap(Some(|x| x + 1));
    assert_eq!(x, Some(2));
  11. Understand Algebraic Data Types (ADT)

    master

    Algebraic Data Types are composite types made by combining other types. There are two main categories:

    1. Sum Type: A type that represents a choice between different variants. The total number of possible values is the sum of the values of its variants. In Rust, enum is the primary way to represent sum types.
    2. Product Type: A type that combines multiple fields together. The total number of possible values is the product of the values of its constituent fields. In Rust, struct and tuples are product types.
  12. Use Closures for state retention

    master

    A closure is a scope that retains variables available to a function when it was created. In Rust, you often use the move keyword to transfer ownership of captured variables into the closure, which is essential for partial application.

    let add_to = |x: i32| move |y: i32| x + y;
    
    let add_to_five = add_to(5);
    // add_to_five now has '5' baked into its scope
    
    add_to_five(3); // => 8