{"id":22399910,"url":"https://github.com/laugharne/rust_combinators","last_synced_at":"2025-03-27T00:17:02.566Z","repository":{"id":220300484,"uuid":"751272120","full_name":"Laugharne/rust_combinators","owner":"Laugharne","description":"Combinators are higher-order functions that can combine or transform functions, enabling more abstract and concise code.","archived":false,"fork":false,"pushed_at":"2024-02-01T09:44:31.000Z","size":5,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-01T05:41:45.451Z","etag":null,"topics":["combinator","combinators","rust","rust-lang"],"latest_commit_sha":null,"homepage":"https://blog.devgenius.io/combinators-in-rust-a8d494634774","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Laugharne.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-02-01T09:24:19.000Z","updated_at":"2024-02-01T09:26:25.000Z","dependencies_parsed_at":"2024-02-01T10:47:12.735Z","dependency_job_id":"742283f2-5977-4bf3-be0d-14f968bebe12","html_url":"https://github.com/Laugharne/rust_combinators","commit_stats":null,"previous_names":["laugharne/rust_combinators"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2Frust_combinators","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2Frust_combinators/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2Frust_combinators/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Laugharne%2Frust_combinators/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Laugharne","download_url":"https://codeload.github.com/Laugharne/rust_combinators/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245755681,"owners_count":20667027,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["combinator","combinators","rust","rust-lang"],"created_at":"2024-12-05T08:10:29.468Z","updated_at":"2025-03-27T00:17:02.547Z","avatar_url":"https://github.com/Laugharne.png","language":null,"readme":"\u003c!-- TOC --\u003e\n\n- [Theoretical Foundations](#theoretical-foundations)\n- [Why Combinators in Rust?](#why-combinators-in-rust)\n- [Basic Combinators in Rust](#basic-combinators-in-rust)\n\t- [map](#map)\n\t- [and_then](#and_then)\n\t- [filter](#filter)\n- [Advanced Combinators and Their Usage](#advanced-combinators-and-their-usage)\n\t- [fold](#fold)\n\t- [zip](#zip)\n- [Combinators with Closures](#combinators-with-closures)\n- [Practical Applications](#practical-applications)\n- [Error Handling with Combinators](#error-handling-with-combinators)\n\t- [map_err](#map_err)\n\t- [or_else](#or_else)\n- [Asynchronous Programming with Combinators](#asynchronous-programming-with-combinators)\n\t- [Using and_then with Futures](#using-and_then-with-futures)\n\t- [Combining Futures with join! and select!](#combining-futures-with-join-and-select)\n- [Chaining and Composition](#chaining-and-composition)\n- [Crafting your own combinator](#crafting-your-own-combinator)\n\t- [Step 1: Understanding the Goal](#step-1-understanding-the-goal)\n\t- [Step 2: Setting Up Your Rust Environment](#step-2-setting-up-your-rust-environment)\n\t- [Step 3: Writing the Combinator](#step-3-writing-the-combinator)\n\t- [Step 4: Implementing maybe_apply](#step-4-implementing-maybe_apply)\n\t- [Step 5: Testing Your Combinator](#step-5-testing-your-combinator)\n\t- [Step 6: Running Your Tests](#step-6-running-your-tests)\n\t- [Step 7: Using Your Combinator in Practice](#step-7-using-your-combinator-in-practice)\n\n\u003c!-- /TOC --\u003e\n\n\n\u003e **Combinators** are higher-order functions that can combine or transform functions, enabling more abstract and concise code.\n\nLet’s explore the theory behind combinators and do some combinators coding in Rust.\n\n\n# Theoretical Foundations\n\nIn [functional programming](https://blog.devgenius.io/functional-programming-patterns-in-rust-bc14f3fe9626), a combinator is a function constructed solely from other functions without relying on variables or constants. The roots of combinators trace back to combinatory logic, a foundational theory in mathematical logic that predates computer science. In this context, combinators serve as the building blocks for constructing expressions and encapsulating computation patterns.\n\n# Why Combinators in Rust?\n\nRust’s embrace of [functional programming](https://blog.devgenius.io/functional-programming-patterns-in-rust-bc14f3fe9626) concepts, such as higher-order functions, closures, and pattern matching, provides a fertile ground for using combinators. Combinators can enhance code readability, reduce boilerplate, and facilitate a declarative programming style. By leveraging combinators, Rust developers can express complex logic succinctly and compose reusable components effectively.\n\n# Basic Combinators in Rust\n\nLet’s start with some basic combinators that are frequently used in Rust programming.\n\n## `map`\n\nThe `map` combinator applies a function to each element of an iterator, transforming them into a new form. It's widely used for data transformation tasks.\n```rust\nlet nums = vec![1, 2, 3, 4];  \nlet squares: Vec = nums.iter().map(|\u0026x| x * x).collect();  \nprintln!(\"{:?}\", squares); // Output: [1, 4, 9, 16]\n```\n\n## `and_then`\n\nThe `and_then` combinator is used with `Option` and `Result` types to chain operations that may return `Option` or `Result`. It's particularly useful for sequential operations where each step may fail or produce an optional value.\n\n```rust\nfn sqrt(x: f64) -\u003e Option {  \n    if x \u003e= 0.0 { Some(x.sqrt()) } else { None }  \n}  \n  \nlet result = Some(4.0).and_then(sqrt);  \nprintln!(\"{:?}\", result); // Output: Some(2.0)\n```\n\n## `filter`\n\nThe `filter` combinator is used to selectively include elements from an iterator based on a predicate function.\n\n```rust\nlet nums = vec![1, 2, 3, 4, 5];  \n  \nlet even_nums: Vec = nums.into_iter().filter(|x| x % 2 == 0).collect();  \n  \nprintln!(\"{:?}\", even_nums); // Output: [2, 4]\n```\n\n# Advanced Combinators and Their Usage\n\nAs we delve deeper into Rust’s functional features, we encounter more sophisticated combinators that cater to complex scenarios.\n\n## `fold`\n\nThe `fold` combinator aggregates elements of an iterator by applying a binary operation, starting from an initial value.\n\n```rust\nlet nums = vec![1, 2, 3, 4];  \n  \nlet sum = nums.iter().fold(0, |acc, \u0026x| acc + x);  \n  \nprintln!(\"{}\", sum); // Output: 10\n```\n\n## `zip`\n\nThe `zip` combinator pairs up elements from two iterators into a single iterator of tuples. It's useful for iterating over two sequences in parallel.\n\n```rust\nlet nums1 = vec![1, 2, 3];  \n  \nlet nums2 = vec![4, 5, 6];  \n  \nlet zipped: Vec\u003c_= nums1.iter().zip(nums2.iter()).collect();  \n  \nprintln!(\"{:?}\", zipped); // Output: [(1, 4), (2, 5), (3, 6)]\n```\n\n# Combinators with Closures\n\nClosures in Rust are anonymous functions that can capture their environment. Combining closures with combinators allows for powerful and flexible code patterns.\n\n```rust\nlet threshold = 2;  \n  \nlet nums = vec![1, 2, 3, 4];  \n  \nlet filtered: Vec = nums.into_iter().filter(|\u0026x| x \u003e threshold).collect();  \n  \nprintln!(\"{:?}\", filtered); // Output: [3, 4]\n```\n\n# Practical Applications\n\nCombinators find practical applications in various domains, such as data processing, asynchronous programming, and functional reactive programming (FRP). For instance, in web development with frameworks like Actix or Rocket, combinators are used to compose middleware and request handlers in a declarative manner.\n\n```rust\n// Hypothetical example with a web framework  \nlet app = App::new()  \n    .route(\"/\", HttpMethod::GET, |req| {  \n        req.query(\"id\")  \n           .and_then(parse_id)  \n           .map(fetch_data)  \n           .map(Json)  \n    });\n```\n\nIn this example, the route handler chains several operations: extracting a query parameter, parsing it, fetching data based on the parsed ID, and finally wrapping the response in JSON.\n\n# Error Handling with Combinators\n\nRust’s `Result` type is a powerful tool for [error handling](https://medium.com/coinmonks/rust-error-handling-in-practice-376d86ba12ca), representing a computation that might fail. Combinators like `map`, `and_then`, `or_else`, and `map_err` allow for elegant and concise [error-handling](https://medium.com/coinmonks/rust-error-handling-in-practice-376d86ba12ca) workflows.\n\n## `map_err`\n\nThe `map_err` combinator is used to transform the error part of a `Result`. It's particularly useful when you need to convert errors from one type to another.\n\n```rust\nfn parse_number(num_str: \u0026str) -\u003e Result {  \n    num_str.parse::().map_err(|e| e.to_string())  \n}  \n  \nlet result = parse_number(\"10\");  \nprintln!(\"{:?}\", result); // Output: Ok(10)  \nlet result = parse_number(\"a10\");  \nprintln!(\"{:?}\", result); // Output: Err(\"invalid digit found in string\")\n```\n\n## `or_else`\n\nThe `or_else` combinator provides a way to handle errors and possibly recover from them, allowing for fallback operations or error transformations.\n\n```rust\nfn try_parse_or_zero(num_str: \u0026str) -\u003e Result {  \n    num_str.parse::().or_else(|_| Ok(0))  \n}  \n  \nlet result = try_parse_or_zero(\"20\");  \n  \nprintln!(\"{:?}\", result); // Output: Ok(20)  \n  \nlet result = try_parse_or_zero(\"abc\");  \n  \nprintln!(\"{:?}\", result); // Output: Ok(0)\n```\n\n# Asynchronous Programming with Combinators\n\nRust’s asynchronous programming model, based on futures and async/await, heavily relies on combinators to manage asynchronous computations. Combinators like `then`, `and_then`, `map`, and `map_err` are used with `Future`s to chain asynchronous operations in a non-blocking way.\n\n## Using `and_then` with Futures\n\nThe `and_then` combinator can be used with futures to perform sequential asynchronous operations, where the output of one operation is the input to the next.\n\n```rust\nasync fn fetch_url(url: \u0026str) -\u003e Result {  \n    reqwest::get(url).await?.text().await  \n}  \n  \nasync fn process_url_data(url: \u0026str) {  \n    fetch_url(url)  \n        .and_then(|data| async move {  \n            println!(\"Fetched data: {}\", data);  \n            Ok(())  \n        })  \n        .await  \n        .unwrap_or_else(|e| eprintln!(\"Error fetching data: {}\", e));  \n}  \n// In an async runtime context  \n// process_url_data(\"http://example.com\").await;\n```\n\nThis example demonstrates fetching data from a URL and then processing that data asynchronously. The `and_then` combinator ensures that the data processing step only occurs if the data fetching step succeeds.\n\n## Combining Futures with `join!` and `select!`\n\nRust also provides macros like `join!` and `select!` to work with multiple futures concurrently, allowing for parallel computation and race conditions handling.\n\n- `join!` waits for all futures to complete and returns a tuple of their results.\n- `select!` waits for the first future to complete and returns its result, cancelling the remaining futures.\n\n```rust\nasync fn task_one() -i32 { 1 }  \n  \nasync fn task_two() -i32 { 2 }  \n  \nasync fn run_tasks() {  \n    let (result_one, result_two) = join!(task_one(), task_two());  \n    println!(\"Results: {}, {}\", result_one, result_two);  \n    let either = select! {  \n        result_one = task_one().fuse() =result_one,  \n        result_two = task_two().fuse() =result_two,  \n    };  \n    println!(\"First completed: {}\", either);  \n}  \n// In an async runtime context  \n// run_tasks().await;\n```\n\n# Chaining and Composition\n\nOne of the key features of combinators is their ability to chain operations. Rust’s standard library provides numerous methods on types like `Option`, `Result`, and iterators, which are essentially built-in combinators.\n\nChaining allows for the composition of multiple operations in a concise and readable manner. For example, using `Option`'s `map` and `and_then` methods:\n\n```rust\nfn square(x: i32) -\u003ei32 { x * x }  \n  \nfn to_str(x: i32) -\u003eOption { Some(x.to_string()) }  \n  \nlet result: Option = Some(2).map(square).and_then(to_str);\n```\n\nHere, `square` is applied to the value inside `Some`, and then `to_str` transforms the squared number into a string, all in a seamless chain of operations.\n\n# Crafting your own combinator\n\n## Step 1: Understanding the Goal\n\nFirst, it’s essential to define what your combinator will do. For this guide, let’s create a combinator named `maybe_apply`. This combinator will work with the `Option` type and take two arguments: an `Option` and a function `F` that takes a `T` and returns a `U`. The combinator will apply the function to the value inside the `Option` if it is `Some`, otherwise, it will return `None`.\n\n## Step 2: Setting Up Your Rust Environment\n\nEnsure you have a Rust environment set up. You’ll need Rust installed on your system, which you can do by following the instructions on the official Rust website.\n\n## Step 3: Writing the Combinator\n\nOpen your favorite editor or IDE, and start a new Rust project if necessary:\n\n```bash\ncargo new combinators_example  \ncd combinators_example\n```\n\nNow, open the `src/lib.rs` file (or `src/main.rs` if you prefer an executable) and start implementing the `maybe_apply` combinator.\n\n## Step 4: Implementing `maybe_apply`\n\n```rust\nfn maybe_apply(option: Option, f: F) -\u003e Option  \nwhere  \n    F: FnOnce(T) -\u003e U,  \n{  \n    match option {  \n        Some(value) =Some(f(value)),  \n        None =None,  \n    }  \n}\n```\n\nIn this implementation:\n\n- `T` and `U` are type parameters representing the types before and after applying the function `F`.\n- `F` is constrained by `FnOnce(T) -U`, meaning it takes a `T` and returns a `U`. `FnOnce` is used because the function is consumed once called, suitable for closures that take ownership of their captured variables.\n- The `match` expression checks if the `Option` is `Some` or `None`. If `Some`, it applies `f` to the contained value, otherwise, it propagates `None`.\n\n## Step 5: Testing Your Combinator\n\nTo verify that `maybe_apply` works as intended, write some tests. Add the following code to the bottom of your `lib.rs` or `main.rs`:\n\n```rust\n#[cfg(test)]  \nmod tests {  \n    use super::*;  \n  \n#[test]  \n    fn test_maybe_apply_some() {  \n        let result = maybe_apply(Some(5), |x| x * 2);  \n        assert_eq!(result, Some(10));  \n    }  \n    #[test]  \n    fn test_maybe_apply_none() {  \n        let result: Option = maybe_apply(None, |x: i32| x * 2);  \n        assert_eq!(result, None);  \n    }  \n}\n```\n\nThese tests cover the two possible scenarios: applying the function to a value inside `Some` and passing a `None` through unchanged.\n\n## Step 6: Running Your Tests\n\nRun your tests to ensure everything works as expected:\n\ncargo test\n\nIf all goes well, you should see output indicating that both tests have passed.\n\n## Step 7: Using Your Combinator in Practice\n\nWith `maybe_apply` tested and ready, you can now use it in your Rust applications. Here's a simple example:\n\n```rust\nfn main() {  \n    let value = Some(10);  \n    let doubled = maybe_apply(value, |x| x * 2);  \n    println!(\"Doubled: {:?}\", doubled); // Should print \"Doubled: Some(20)\"  \n}\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaugharne%2Frust_combinators","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flaugharne%2Frust_combinators","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flaugharne%2Frust_combinators/lists"}