naan is a functional programming prelude
for the Rust language that is:
* easy
* useful
* std
- and alloc
-optional
* FAST - exclusively uses concrete types (no dyn
amic dispatch) meaning near-zero perf cost
All of this is made possible with a trick using Generic associated types to emulate Kinds
In type theory, it can be useful to have language to differentiate between a concrete type (u8
, Vec<u8>
, Result<File, io::Error>
)
and a generic type without its parameters supplied. (Vec
, Option
, Result
)
For example, Vec
is a 1-argument (unary) type function, and Vec<u8>
is a concrete type.
Kind refers to how many (if any) parameters a type has.
In vanilla Rust, Result::map
and Option::map
have very similar shapes:
```rust
impl Result {
fn map(self, f: impl FnMut(A) -> B) -> Result;
}
impl Option {
fn map(self, f: impl FnMut(A) -> B) -> Option;
}
it would be useful (for reasons we'll expand on later) to have them
both implement a `Map` trait:
rust
trait Map {
fn map(self: Self, f: impl FnMut(A) -> B) -> Self;
}
``
but this code snippet isn't legal Rust because
Selfneeds to be generic (kind
* -> *)
and in vanilla Rust
Self` must be a concrete type.
With the introduction of Generic associated types,
we can write a "type function of kind * -> *
" trait (here called HKT
).
Using this we can implement HKT
for Option
, Result
, or any Self
essentially generic by tying it to
and write the Map
trait from above in legal Rust:
```rust trait HKT { type Of; }
struct OptionHKT; impl HKT for OptionHKT { type Of = Option; }
trait Map
impl Map
Currying is the technique where naan
gets its name. Function currying is the strategy of splitting functions that
accept more than one argument into functions that return functions.
Concrete example:
rust
fn foo(String, usize) -> usize;
foo(format!("bar"), 12);
would be curried into:
rust
fn foo(String) -> impl Fn(usize) -> usize;
foo(format!("bar"))(12);
Currying allows us to provide some of a function's arguments and provide the rest of this partially applied function's arguments at a later date.
This allows us to use functions to store state, and lift functions that accept any number
of parameters to accept Results using Apply
EXAMPLE: reusable function with a stored parameter ```rust use std::fs::File;
use naan::prelude::*;
fn copyfileto_dir(dir: String, file: File) -> std::io::Result<()> { // ... # Ok(()) }
fn main() { let dir = std::env::var("DESTDIR").unwrap(); let copy = copyfiletodir.curry().call(dir);
File::open("a.txt").bind1(copy.clone()) .bind1(|| File::open("b.txt")) .bind1(copy.clone()) .bind1(|| File::open("c.txt")) .bind1(copy); }
/* equivalent to: fn main() { let dir = std::env::var("DEST_DIR").unwrap();
copy_file_to_dir(dir.clone(), File::open("a.txt")?)?;
copy_file_to_dir(dir.clone(), File::open("b.txt")?)?;
copy_file_to_dir(dir, File::open("c.txt")?)?;
} */ ```
EXAMPLE: lifting a function to accept Results (or Options) ```rust use std::fs::File;
use naan::prelude::*;
fn append_contents(from: File, to: File) -> std::io::Result<()> { // ... # Ok(()) }
fn main() -> std::io::Result<()> { Ok(append_contents.curry()).apply1(File::open("from.txt")) .apply1(File::open("to.txt")) .flatten() }
/* equivalent to: fn main() -> std::io::Result<()> { let from = File::open("from.txt")?; let to = File::open("to.txt")?; append_contents(from, to) } */ ```
naan introduces a few new function traits that add
ergonomics around currying and function composition;
F1
, F2
and F3
. These traits extend the builtin function
traits Fn
and FnOnce
with methods that allow currying and function
composition.
(note that each arity has a "callable multiple times"
version and a "callable at least once" version. The latter traits are
denoted with a suffix of Once
)
``rust
pub trait F2Once<A, B, C>: Sized {
/// The concrete type that
curry` returns.
type Curried;
/// Call the function fn call1(self, a: A, b: B) -> C;
/// Curry this function, transforming it from
///
/// fn(A, B) -> C
/// to
/// fn(A) -> fn(B) -> C
fn curry(self) -> Self::Curried;
}
pub trait F2: F2Once { /// Call the function with all arguments fn call(&self, a: A, b: B) -> C; }
impl
Function composition is the strategy of chaining functions sequentially by automatically passing the output of one function to the input of another.
This very powerful technique lets us concisely express programs in terms of data that flows through pipes, rather than a sequence of time-bound statements:
```rust use naan::prelude::*;
struct Apple; struct Orange; struct Grape;
struct Banana;
fn appletoorange(a: Apple) -> Orange { Orange } fn orangetogrape(o: Orange) -> Grape { Grape } fn grapetobanana(g: Grape) -> Banana { Banana }
fn main() { let appletobanana = appletoorange.chain(orangetogrape) .chain(grapetobanana); asserteq!(appleto_banana.call(Apple), Banana) } ```
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.