├── .gitignore ├── .github ├── workflows │ ├── delete-runs.yml │ ├── build-docs.yml │ ├── lint.yml │ ├── build-stable.yml │ ├── build-nightly.yml │ ├── coverage.yml │ ├── build-nightly-all-features.yml │ ├── build-stable-all-features.yml │ └── test.yml └── dependabot.yml ├── rustfmt.toml ├── LICENSE ├── Cargo.toml ├── src ├── curry.rs ├── rcurry.rs ├── concat_args.rs ├── lib.rs └── curried.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | # Rust-analyzer panics 13 | rust-ice-*.txt -------------------------------------------------------------------------------- /.github/workflows/delete-runs.yml: -------------------------------------------------------------------------------- 1 | name: Delete old workflow runs 2 | on: 3 | schedule: 4 | - cron: '0 0 1 * *' 5 | 6 | jobs: 7 | del_runs: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | actions: write 11 | contents: read 12 | steps: 13 | - name: Delete workflow runs 14 | uses: Mattraks/delete-workflow-runs@v2 15 | with: 16 | token: ${{ github.token }} 17 | repository: ${{ github.repository }} 18 | retain_days: 30 19 | keep_minimum_runs: 6 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Please see the documentation for all configuration options: 2 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 3 | 4 | version: 2 5 | updates: 6 | - package-ecosystem: "cargo" 7 | directory: "/" 8 | schedule: 9 | interval: "weekly" 10 | ignore: 11 | # These are peer deps of Cargo and should not be automatically bumped 12 | - dependency-name: "semver" 13 | - dependency-name: "crates-io" 14 | rebase-strategy: "disabled" 15 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | brace_style = "AlwaysNextLine" 2 | control_brace_style = "AlwaysNextLine" 3 | remove_nested_parens = true 4 | reorder_impl_items = true 5 | reorder_modules = true 6 | reorder_imports = true 7 | space_after_colon = true 8 | spaces_around_ranges = false 9 | tab_spaces = 4 10 | unstable_features = true 11 | wrap_comments = true 12 | comment_width = 170 13 | max_width = 170 14 | trailing_comma = "Never" 15 | 16 | # TO BE DESIRED: 17 | # - don't add space between '*' and '/' binary operators 18 | # - Add newline in empty multi-line block 19 | # - No space in generic const expr block 20 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | on: 3 | schedule: 4 | - cron: '29 15 * * *' 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | jobs: 10 | doc: 11 | name: Docs 12 | runs-on: ubuntu-latest 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | rust: 17 | #- stable 18 | #- beta 19 | - nightly 20 | steps: 21 | - uses: actions/checkout@v4 22 | name: "Checkout" 23 | - uses: dtolnay/rust-toolchain@nightly 24 | with: 25 | profile: "minimal" 26 | toolchain: "${{ matrix.rust }}" 27 | override: true 28 | name: "Install Rust ${{ matrix.rust }}" 29 | - uses: "actions-rs/cargo@v1" 30 | with: 31 | command: "check" 32 | name: "Run `cargo check`" 33 | - uses: dtolnay/install@cargo-docs-rs 34 | name: "Install cargo docs-rs" 35 | - run: cargo docs-rs 36 | name: "Run `cargo docs-rs`" -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | env: 8 | RUST_BACKTRACE: 1 9 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 10 | jobs: 11 | lints: 12 | name: "Lints" 13 | runs-on: "ubuntu-latest" 14 | steps: 15 | - uses: "actions/checkout@v3" 16 | name: "Checkout" 17 | - uses: "actions-rs/toolchain@v1" 18 | with: 19 | profile: "minimal" 20 | #toolchain: "stable" 21 | toolchain: "nightly" 22 | override: true 23 | components: "rustfmt, clippy" 24 | name: "Install Rust nightly" 25 | - uses: "actions-rs/cargo@v1" 26 | with: 27 | command: "fmt" 28 | args: "--all -- --check" 29 | name: "Run `cargo fmt`" 30 | - uses: "actions-rs/cargo@v1" 31 | with: 32 | command: "clippy" 33 | args: "-- -D warnings -A incomplete-features" 34 | name: "Run `cargo clippy`" 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 sigurd4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/build-stable.yml: -------------------------------------------------------------------------------- 1 | name: Build-stable 2 | on: 3 | schedule: 4 | - cron: '29 15 * * *' 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | env: 10 | RUST_BACKTRACE: 1 11 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 12 | jobs: 13 | check: 14 | name: "Check" 15 | runs-on: "ubuntu-latest" 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | rust: 20 | - stable 21 | #- beta 22 | #- nightly 23 | steps: 24 | - uses: "actions/checkout@v3" 25 | name: "Checkout" 26 | - uses: "actions-rs/toolchain@v1" 27 | with: 28 | profile: "minimal" 29 | toolchain: "${{ matrix.rust }}" 30 | override: true 31 | name: "Install Rust ${{ matrix.rust }}" 32 | - name: cache 33 | uses: "Swatinem/rust-cache@v2" 34 | - uses: "actions-rs/cargo@v1" 35 | with: 36 | command: "check" 37 | name: "Run `cargo check`" 38 | - uses: "taiki-e/install-action@v2" 39 | with: 40 | tool: "cargo-hack" 41 | name: "Install cargo-hack" 42 | - run: "cargo hack check --no-dev-deps" 43 | name: "Check all features with 'cargo-hack'" 44 | -------------------------------------------------------------------------------- /.github/workflows/build-nightly.yml: -------------------------------------------------------------------------------- 1 | name: Build-nightly 2 | on: 3 | schedule: 4 | - cron: '29 15 * * *' 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | env: 10 | RUST_BACKTRACE: 1 11 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 12 | jobs: 13 | check: 14 | name: "Check" 15 | runs-on: "ubuntu-latest" 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | rust: 20 | #- stable 21 | #- beta 22 | - nightly 23 | steps: 24 | - uses: "actions/checkout@v3" 25 | name: "Checkout" 26 | - uses: "actions-rs/toolchain@v1" 27 | with: 28 | profile: "minimal" 29 | toolchain: "${{ matrix.rust }}" 30 | override: true 31 | name: "Install Rust ${{ matrix.rust }}" 32 | - name: cache 33 | uses: "Swatinem/rust-cache@v2" 34 | - uses: "actions-rs/cargo@v1" 35 | with: 36 | command: "check" 37 | name: "Run `cargo check`" 38 | - uses: "taiki-e/install-action@v2" 39 | with: 40 | tool: "cargo-hack" 41 | name: "Install cargo-hack" 42 | - run: "cargo hack check --no-dev-deps" 43 | name: "Check all features with 'cargo-hack'" 44 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: 3 | push: 4 | branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | env: 8 | RUST_BACKTRACE: 1 9 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 10 | jobs: 11 | coverage: 12 | name: "Code Coverage" 13 | runs-on: "ubuntu-latest" 14 | container: 15 | image: xd009642/tarpaulin:develop-nightly 16 | options: --security-opt seccomp=unconfined 17 | steps: 18 | - uses: "actions/checkout@v3" 19 | name: "Checkout" 20 | - uses: "actions-rs/toolchain@v1" 21 | with: 22 | profile: "minimal" 23 | toolchain: "nightly" 24 | override: true 25 | name: "Install Rust nightly" 26 | - name: "Run cargo-tarpaulin" 27 | run: | 28 | cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out xml 29 | - name: "Upload to codecov.io" 30 | uses: "codecov/codecov-action@v5" 31 | with: 32 | token: ${{secrets.CODECOV_TOKEN}} # not required for public repos 33 | fail_ci_if_error: true 34 | slug: ${{github.repository}} 35 | - name: "Archive code coverage results" 36 | uses: "actions/upload-artifact@v4" 37 | with: 38 | name: "code-coverage-report" 39 | path: "cobertura.xml" 40 | -------------------------------------------------------------------------------- /.github/workflows/build-nightly-all-features.yml: -------------------------------------------------------------------------------- 1 | name: Build-nightly-all-features 2 | on: 3 | schedule: 4 | - cron: '29 15 * * *' 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | env: 10 | RUST_BACKTRACE: 1 11 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 12 | jobs: 13 | check: 14 | name: "Check" 15 | runs-on: "ubuntu-latest" 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | rust: 20 | #- stable 21 | #- beta 22 | - nightly 23 | steps: 24 | - uses: "actions/checkout@v3" 25 | name: "Checkout" 26 | - uses: "actions-rs/toolchain@v1" 27 | with: 28 | profile: "minimal" 29 | toolchain: "${{ matrix.rust }}" 30 | override: true 31 | name: "Install Rust ${{ matrix.rust }}" 32 | - name: cache 33 | uses: "Swatinem/rust-cache@v2" 34 | - uses: "actions-rs/cargo@v1" 35 | with: 36 | command: "check" 37 | name: "Run `cargo check`" 38 | - uses: "taiki-e/install-action@v2" 39 | with: 40 | tool: "cargo-hack" 41 | name: "Install cargo-hack" 42 | - run: "cargo hack check --each-feature --optional-deps --all-targets ${{ vars.CARGO_HACK_EACH_FEATURE_EXTRA_ARGS }}" 43 | name: "Check all features with 'cargo-hack'" 44 | -------------------------------------------------------------------------------- /.github/workflows/build-stable-all-features.yml: -------------------------------------------------------------------------------- 1 | name: Build-stable-all-features 2 | on: 3 | schedule: 4 | - cron: '29 15 * * *' 5 | push: 6 | branches: [ "master" ] 7 | pull_request: 8 | branches: [ "master" ] 9 | env: 10 | RUST_BACKTRACE: 1 11 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 12 | jobs: 13 | check: 14 | name: "Check" 15 | runs-on: "ubuntu-latest" 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | rust: 20 | - stable 21 | #- beta 22 | #- nightly 23 | steps: 24 | - uses: "actions/checkout@v3" 25 | name: "Checkout" 26 | - uses: "actions-rs/toolchain@v1" 27 | with: 28 | profile: "minimal" 29 | toolchain: "${{ matrix.rust }}" 30 | override: true 31 | name: "Install Rust ${{ matrix.rust }}" 32 | - name: cache 33 | uses: "Swatinem/rust-cache@v2" 34 | - uses: "actions-rs/cargo@v1" 35 | with: 36 | command: "check" 37 | name: "Run `cargo check`" 38 | - uses: "taiki-e/install-action@v2" 39 | with: 40 | tool: "cargo-hack" 41 | name: "Install cargo-hack" 42 | - run: "cargo hack check --each-feature --optional-deps --all-targets ${{ vars.CARGO_HACK_EACH_FEATURE_EXTRA_ARGS }}" 43 | name: "Check all features with 'cargo-hack'" 44 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | env: 8 | RUST_BACKTRACE: 1 9 | RUST_LOG: "cargo_tarpaulin=trace,llvm_profparser=trace" 10 | jobs: 11 | test: 12 | name: "Test" 13 | runs-on: "ubuntu-latest" 14 | strategy: 15 | matrix: 16 | rust: 17 | #- stable 18 | #- beta 19 | - nightly 20 | steps: 21 | - uses: "actions/checkout@v3" 22 | name: "Checkout" 23 | - uses: "actions-rs/toolchain@v1" 24 | with: 25 | profile: "minimal" 26 | toolchain: "${{ matrix.rust }}" 27 | override: true 28 | name: "Install Rust ${{ matrix.rust }}" 29 | - uses: "actions-rs/cargo@v1" 30 | with: 31 | command: "test" 32 | name: "Run `cargo test`" 33 | - uses: "taiki-e/install-action@v2" 34 | with: 35 | tool: "cargo-hack" 36 | name: "Install cargo-hack" 37 | - run: "cargo hack test --each-feature --optional-deps --all-targets ${{ vars.CARGO_HACK_EACH_FEATURE_EXTRA_ARGS }}" 38 | name: "Check all features with 'cargo-hack'" 39 | - name: Upload test results to Codecov 40 | if: ${{ !cancelled() }} 41 | uses: codecov/test-results-action@v1 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "currying" 3 | version = "0.4.0" 4 | edition = "2021" 5 | license = "MIT" 6 | keywords = ["function", "fp", "curry", "currying", "haskell"] 7 | categories = ["rust-patterns", "algorithms", "mathematics", "no-std::no-alloc"] 8 | description = "A crate for currying anything implementing `FnOnce`. Arguments can be passed one at a time, yielding a new something implementing `FnOnce` (and possibly `FnMut` and `Fn`) which can be called with one less argument." 9 | repository = "https://github.com/sigurd4/currying" 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [lib] 14 | name = "currying" 15 | 16 | [lints.rust] 17 | unsafe_code = "forbid" 18 | 19 | [badges] 20 | maintainence = {status = "as-is"} 21 | 22 | [features] 23 | default = ["rcurry", "16"] 24 | rcurry = [] 25 | 8 = ["tupleops/8"] 26 | 16 = ["tupleops/16", "8"] 27 | 32 = ["tupleops/32", "16"] 28 | 64 = ["tupleops/64", "32"] 29 | 96 = ["tupleops/96", "64"] 30 | 128 = ["tupleops/128", "96"] 31 | 160 = ["tupleops/160", "128"] 32 | 192 = ["tupleops/192", "160"] 33 | 224 = ["tupleops/224", "192"] 34 | 256 = ["tupleops/256", "224"] 35 | dont_hurt_yourself_by_using_all_features = ["tupleops/dont_hurt_yourself_by_using_all_features"] 36 | 37 | [dependencies] 38 | moddef = "0.3.0" 39 | tupleops = {version = "0.1.1", features = ["concat"]} 40 | tupleops-macros = "0.1.0" -------------------------------------------------------------------------------- /src/curry.rs: -------------------------------------------------------------------------------- 1 | use core::marker::Tuple; 2 | 3 | use crate::Curried; 4 | 5 | /// A trait for things which may be curried. 6 | /// 7 | /// C is the leftmost argument being applied in the curry. 8 | /// 9 | /// X is the rest of the arguments left over after currying. 10 | /// 11 | /// This trait is automatically implemented for anything implementing [FnOnce](core::ops::FnOnce) which takes one or more argument. 12 | /// 13 | /// # Examples 14 | /// 15 | /// ```rust 16 | /// use currying::*; 17 | /// 18 | /// let f = |x, y, z| x + y + z; 19 | /// let (x, y, z) = (1, 2, 3); 20 | /// 21 | /// let fx = f.curry(x); 22 | /// 23 | /// assert_eq!(fx(y, z), f(x, y, z)); 24 | /// 25 | /// let fxy = fx.curry(y); 26 | /// 27 | /// assert_eq!(fxy(z), f(x, y, z)); 28 | /// 29 | /// let fxyz = fxy.curry(z); 30 | /// 31 | /// assert_eq!(fxyz(), f(x, y, z)); 32 | /// ``` 33 | pub trait Curriable = Curry>; 34 | 35 | /// A trait providing the method for currying from the left. 36 | /// 37 | /// Only types that implement [FnOnce](core::ops::FnOnce) and can take a leftmost argument of type `C` can be called once curried. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ```rust 42 | /// use currying::*; 43 | /// 44 | /// let f = |x, y, z| x + y + z; 45 | /// let (x, y, z) = (1, 2, 3); 46 | /// 47 | /// let fx = f.curry(x); 48 | /// 49 | /// assert_eq!(fx(y, z), f(x, y, z)); 50 | /// 51 | /// let fxy = fx.curry(y); 52 | /// 53 | /// assert_eq!(fxy(z), f(x, y, z)); 54 | /// 55 | /// let fxyz = fxy.curry(z); 56 | /// 57 | /// assert_eq!(fxyz(), f(x, y, z)); 58 | /// ``` 59 | pub const trait Curry: Sized 60 | { 61 | type Output; 62 | 63 | fn curry_once(self, arg: C) -> Self::Output; 64 | fn curry_mut(&mut self, arg: C) -> <&mut Self as Curry>::Output 65 | { 66 | self.curry_once(arg) 67 | } 68 | fn curry(&self, arg: C) -> <&Self as Curry>::Output 69 | { 70 | self.curry_once(arg) 71 | } 72 | } 73 | 74 | impl const Curry for F 75 | { 76 | type Output = Curried<(C,), (), F>; 77 | 78 | fn curry_once(self, arg: C) -> Self::Output 79 | { 80 | Curried::curry(self, arg) 81 | } 82 | } -------------------------------------------------------------------------------- /src/rcurry.rs: -------------------------------------------------------------------------------- 1 | use core::marker::Tuple; 2 | 3 | use crate::Curried; 4 | 5 | /// A trait for things which may be curried. 6 | /// 7 | /// C is the rightmost argument being applied in the curry. 8 | /// 9 | /// X is the rest of the arguments left over after currying. 10 | /// 11 | /// This trait is automatically implemented for anything implementing [FnOnce](core::ops::FnOnce) which takes one or more argument. 12 | /// 13 | /// # Examples 14 | /// 15 | /// ```rust 16 | /// use currying::*; 17 | /// 18 | /// let f = |x, y, z| x + y + z; 19 | /// let (x, y, z) = (1, 2, 3); 20 | /// 21 | /// let fz = f.rcurry(z); 22 | /// 23 | /// assert_eq!(fz(x, y), f(x, y, z)); 24 | /// 25 | /// let fyz = fz.rcurry(y); 26 | /// 27 | /// assert_eq!(fyz(x), f(x, y, z)); 28 | /// 29 | /// let fxyz = fyz.rcurry(x); 30 | /// 31 | /// assert_eq!(fxyz(), f(x, y, z)); 32 | /// ``` 33 | pub trait RCurriable = RCurry>; 34 | 35 | /// A trait providing the method for currying from the right. 36 | /// 37 | /// Only types that implement [FnOnce](core::ops::FnOnce) and can take a rightmost argument of type `C` can be called once curried. 38 | /// 39 | /// # Examples 40 | /// 41 | /// ```rust 42 | /// use currying::*; 43 | /// 44 | /// let f = |x, y, z| x + y + z; 45 | /// let (x, y, z) = (1, 2, 3); 46 | /// 47 | /// let fz = f.rcurry(z); 48 | /// 49 | /// assert_eq!(fz(x, y), f(x, y, z)); 50 | /// 51 | /// let fyz = fz.rcurry(y); 52 | /// 53 | /// assert_eq!(fyz(x), f(x, y, z)); 54 | /// 55 | /// let fxyz = fyz.rcurry(x); 56 | /// 57 | /// assert_eq!(fxyz(), f(x, y, z)); 58 | /// ``` 59 | pub const trait RCurry: Sized 60 | { 61 | type Output; 62 | 63 | fn rcurry_once(self, arg: C) -> Self::Output; 64 | fn rcurry_mut(&mut self, arg: C) -> <&mut Self as RCurry>::Output 65 | { 66 | self.rcurry_once(arg) 67 | } 68 | fn rcurry(&self, arg: C) -> <&Self as RCurry>::Output 69 | { 70 | self.rcurry_once(arg) 71 | } 72 | } 73 | 74 | impl const RCurry for F 75 | { 76 | type Output = Curried<(), (C,), F>; 77 | 78 | fn rcurry_once(self, arg: C) -> Self::Output 79 | { 80 | Curried::rcurry(self, arg) 81 | } 82 | } -------------------------------------------------------------------------------- /src/concat_args.rs: -------------------------------------------------------------------------------- 1 | use core::marker::Tuple; 2 | 3 | use tupleops::TupleConcatMany; 4 | 5 | pub const trait ConcatArgs: TupleConcatMany + Sized 6 | { 7 | fn concat_args(self) -> Self::Type; 8 | } 9 | 10 | impl ConcatArgs for T 11 | where 12 | T: TupleConcatMany 13 | { 14 | default fn concat_args(self) -> Self::Type 15 | { 16 | tupleops::concat_many(self) 17 | } 18 | } 19 | 20 | macro_rules! impl_concat_args { 21 | ($($t0:ident $($t:ident)*)?) => { 22 | impl const ConcatArgs for ((L,), ($($t0, $($t,)*)?), (R,)) 23 | { 24 | fn concat_args(self) -> Self::Type 25 | { 26 | let ((l,), ($($t0, $($t,)*)?), (r,)) = self; 27 | (l, $($t0, $($t,)*)? r,) 28 | } 29 | } 30 | impl const ConcatArgs for ((L,), ($($t0, $($t,)*)?), ()) 31 | { 32 | fn concat_args(self) -> Self::Type 33 | { 34 | let ((l,), ($($t0, $($t,)*)?), ()) = self; 35 | (l, $($t0, $($t,)*)?) 36 | } 37 | } 38 | impl<$($t0, $($t,)*)? R> const ConcatArgs for ((), ($($t0, $($t,)*)?), (R,)) 39 | { 40 | fn concat_args(self) -> Self::Type 41 | { 42 | let ((), ($($t0, $($t,)*)?), (r,)) = self; 43 | ($($t0, $($t,)*)? r,) 44 | } 45 | } 46 | $(impl_concat_args!($($t)*);)? 47 | }; 48 | } 49 | 50 | #[cfg(any(not(feature = "8"), feature = "dont_hurt_yourself_by_using_all_features"))] 51 | impl_concat_args!(); 52 | #[cfg(not(feature = "dont_hurt_yourself_by_using_all_features"))] 53 | #[cfg(all(feature = "8", not(feature = "16")))] 54 | impl_concat_args!( 55 | _3 _4 _5 _6 _7 _8 56 | ); 57 | #[cfg(not(feature = "dont_hurt_yourself_by_using_all_features"))] 58 | #[cfg(all(feature = "16", not(feature = "32")))] 59 | impl_concat_args!( 60 | _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _16 61 | ); 62 | #[cfg(not(feature = "dont_hurt_yourself_by_using_all_features"))] 63 | #[cfg(feature = "32")] 64 | impl_concat_args!( 65 | _3 _4 _5 _6 _7 _8 _9 _10 _11 _12 _13 _14 _16 _17 _18 _19 _20 _21 _22 _23 _24 _25 _26 _27 _28 _29 _30 _31 _32 66 | ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status (nightly)](https://github.com/sigurd4/currying/workflows/Build-nightly/badge.svg)](https://github.com/sigurd4/currying/actions/workflows/build-nightly.yml) 2 | [![Build Status (nightly, all features)](https://github.com/sigurd4/currying/workflows/Build-nightly-all-features/badge.svg)](https://github.com/sigurd4/currying/actions/workflows/build-nightly-all-features.yml) 3 | 4 | [![Build Status (stable)](https://github.com/sigurd4/currying/workflows/Build-stable/badge.svg)](https://github.com/sigurd4/currying/actions/workflows/build-stable.yml) 5 | [![Build Status (stable, all features)](https://github.com/sigurd4/currying/workflows/Build-stable-all-features/badge.svg)](https://github.com/sigurd4/currying/actions/workflows/build-stable-all-features.yml) 6 | 7 | [![Test Status](https://github.com/sigurd4/currying/workflows/Test/badge.svg)](https://github.com/sigurd4/currying/actions/workflows/test.yml) 8 | [![Lint Status](https://github.com/sigurd4/currying/workflows/Lint/badge.svg)](https://github.com/sigurd4/currying/actions/workflows/lint.yml) 9 | 10 | [![Latest Version](https://img.shields.io/crates/v/currying.svg)](https://crates.io/crates/currying) 11 | [![License:MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 12 | [![Documentation](https://img.shields.io/docsrs/currying)](https://docs.rs/currying) 13 | [![Coverage Status](https://img.shields.io/codecov/c/github/sigurd4/currying)](https://app.codecov.io/github/sigurd4/currying) 14 | 15 | # Currying 16 | 17 | A crate for currying anything implementing `FnOnce`. 18 | 19 | Arguments can be passed one at a time, yielding a new something implementing `FnOnce` (and possibly `FnMut` and `Fn`) which can be called with one less argument. It also implements `AsyncFnOnce`, `AsyncFnMut` and `AsyncFn` if the feature `async` is enabled, since this is an experimental feature. Curried arguments are then omitted when calling the curried function, as they have already been passed. 20 | 21 | ## Example 22 | 23 | ```rust 24 | use currying::*; 25 | 26 | let f = |x, y, z| x + y + z; 27 | let (x, y, z) = (1, 2, 3); 28 | 29 | let fx = f.curry(x); 30 | 31 | assert_eq!(fx(y, z), f(x, y, z)); 32 | 33 | let fxz = fx.rcurry(z); 34 | 35 | assert_eq!(fxz(y), f(x, y, z)); 36 | 37 | let fxyz = fxy.curry(y); 38 | 39 | assert_eq!(fxyz(), f(x, y, z)); 40 | ``` 41 | 42 | ## Experimental features 43 | 44 | While this crate does use nightly features regardless, i've found that especially the compile-time stuff tend to break in new versions of the rust language. This is why i've isolated it into a special opt-in feature. If it no longer compiles, please report the error here on github, however the base crate should still work at the very least. 45 | 46 | ### Compile-time currying 47 | 48 | Currying also works at compile-time. 49 | 50 | ```rust 51 | use currying::*; 52 | 53 | const fn f(x: u8, y: u8, z: u8) -> u8 { 54 | x + y + z 55 | } 56 | 57 | const X: u8 = 1; 58 | const Y: u8 = 2; 59 | const Z: u8 = 3; 60 | 61 | const { 62 | let fx = f.curry(X); 63 | 64 | assert!(fx(Y, Z) == f(X, Y, Z)); 65 | 66 | let fxz = fx.rcurry(Z); 67 | 68 | assert!(fxz(Y) == f(X, Y, Z)); 69 | 70 | let fxyz = fxz.curry(Y); 71 | 72 | assert!(fxyz() == f(X, Y, Z)); 73 | } 74 | ``` 75 | 76 | ### Asyncronous function traits 77 | 78 | Asyncronous function traits are an experminental feature. Enable it with the `async` or the `experimental` feature flag. 79 | 80 | It should work, but i've not tested it yet. 81 | 82 | ## Currying from the right 83 | 84 | Currying can be done from the right too, with the method `rcurry()`. 85 | 86 | This is a stable feature, and is enabled by default. You can opt out of it by disabling the `rcurry` feature flag. -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![feature(tuple_trait)] 3 | #![feature(unboxed_closures)] 4 | #![feature(fn_traits)] 5 | #![feature(trait_alias)] 6 | #![feature(const_trait_impl)] 7 | #![feature(const_destruct)] 8 | #![feature(const_precise_live_drops)] 9 | #![feature(async_fn_traits)] 10 | #![feature(specialization)] 11 | 12 | //! A crate for currying functions in rust 13 | //! 14 | //! Arguments can be passed one at a time, yielding a new something implementing [FnOnce](core::ops::FnOnce) 15 | //! (and possibly [FnMut](core::ops::FnMut) and [Fn](core::ops::Fn)) which can be called with one less argument. 16 | //! 17 | //! It also implements [AsyncFnOnce](core::ops::AsyncFnOnce), [AsyncFnMut](core::ops::AsyncFnMut) and [AsyncFn](core::ops::AsyncFn) if the feature `async` is enabled, 18 | //! since this is an experimental feature. 19 | //! 20 | //! Curried arguments are then omitted when calling the curried function, as they have already been passed. 21 | //! 22 | //! # Examples 23 | //! 24 | //! ```rust 25 | //! use currying::*; 26 | //! 27 | //! let f = |x, y, z| x + y + z; 28 | //! let (x, y, z) = (1, 2, 3); 29 | //! 30 | //! let fx = f.curry(x); 31 | //! 32 | //! assert_eq!(fx(y, z), f(x, y, z)); 33 | //! 34 | //! let fxz = fx.rcurry(z); 35 | //! 36 | //! assert_eq!(fxz(y), f(x, y, z)); 37 | //! 38 | //! let fxyz = fxz.curry(y); 39 | //! 40 | //! assert_eq!(fxyz(), f(x, y, z)); 41 | //! ``` 42 | //! 43 | //! Currying also works at compile-time. 44 | //! 45 | //! ```rust 46 | //! # #![feature(const_trait_impl)] 47 | //! use currying::*; 48 | //! 49 | //! const fn f(x: u8, y: u8, z: u8) -> u8 { 50 | //! x + y + z 51 | //! } 52 | //! 53 | //! const X: u8 = 1; 54 | //! const Y: u8 = 2; 55 | //! const Z: u8 = 3; 56 | //! 57 | //! const { 58 | //! let fx = f.curry(X); 59 | //! 60 | //! assert!(fx(Y, Z) == f(X, Y, Z)); 61 | //! 62 | //! let fxz = fx.rcurry(Z); 63 | //! 64 | //! assert!(fxz(Y) == f(X, Y, Z)); 65 | //! 66 | //! let fxyz = fxz.curry(Y); 67 | //! 68 | //! assert!(fxyz() == f(X, Y, Z)); 69 | //! } 70 | //! ``` 71 | moddef::moddef!( 72 | flat(pub) mod { 73 | curried, 74 | curry, 75 | rcurry for cfg(feature = "rcurry"), 76 | concat_args 77 | } 78 | ); 79 | 80 | #[cfg(test)] 81 | mod test 82 | { 83 | #[cfg(feature = "rcurry")] 84 | #[test] 85 | fn test() 86 | { 87 | use crate::*; 88 | 89 | let f = |x, y, z| x + y + z; 90 | let (x, y, z) = (1, 2, 3); 91 | 92 | let fx = f.curry(x); 93 | 94 | assert_eq!(fx(y, z), f(x, y, z)); 95 | 96 | let fxz = fx.rcurry(z); 97 | 98 | assert_eq!(fxz(y), f(x, y, z)); 99 | 100 | let fxyz = fxz.curry(y); 101 | 102 | assert_eq!(fxyz(), f(x, y, z)); 103 | } 104 | 105 | #[test] 106 | fn test_mut() 107 | { 108 | use crate::Curry; 109 | 110 | let i0 = 0; 111 | let mut i = i0; 112 | let n = 1; 113 | 114 | let mut f = |j| { 115 | i += j; 116 | i 117 | }; 118 | 119 | let mut fj = f.curry_mut(n); 120 | 121 | for k in 1..10 122 | { 123 | assert_eq!(fj(), i0 + k * n) 124 | } 125 | } 126 | 127 | #[test] 128 | fn test_once() 129 | { 130 | use crate::Curry; 131 | 132 | let i0 = 0; 133 | let i = i0; 134 | let n = 1; 135 | 136 | let f = |j| i + j; 137 | 138 | let i = f.curry_once(n)(); 139 | 140 | assert_eq!(i, i0 + n) 141 | } 142 | 143 | #[cfg(feature = "rcurry")] 144 | #[test] 145 | fn test_const() 146 | { 147 | use crate::*; 148 | 149 | const fn f(x: u8, y: u8, z: u8) -> u8 150 | { 151 | x + y + z 152 | } 153 | 154 | const X: u8 = 1; 155 | const Y: u8 = 2; 156 | const Z: u8 = 3; 157 | 158 | const { 159 | let fx = f.curry(X); 160 | 161 | assert!(fx(Y, Z) == f(X, Y, Z)); 162 | 163 | let fxz = fx.rcurry(Z); 164 | 165 | assert!(fxz(Y) == f(X, Y, Z)); 166 | 167 | let fxyz = fxz.curry(Y); 168 | 169 | assert!(fxyz() == f(X, Y, Z)); 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/curried.rs: -------------------------------------------------------------------------------- 1 | use core::marker::Tuple; 2 | 3 | use crate::concat_args::ConcatArgs; 4 | 5 | use core::ops::{AsyncFn, AsyncFnMut, AsyncFnOnce}; 6 | 7 | /// A struct which represents a curried function. 8 | /// 9 | /// This struct implements [FnOnce](core::ops::FnOnce), [FnMut](core::ops::FnMut) and [Fn](core::ops::Fn) if the curried function also implements these traits. 10 | /// 11 | /// It also implements [AsyncFnOnce](core::ops::AsyncFnOnce), [AsyncFnMut](core::ops::AsyncFnMut) and [AsyncFn](core::ops::AsyncFn) if the feature `async` is enabled, 12 | /// since this is an experimental feature. 13 | /// 14 | /// Curried arguments are then omitted when calling the curried function, as they have already been passed. 15 | /// 16 | /// # Examples 17 | /// 18 | /// ```rust 19 | /// use currying::*; 20 | /// 21 | /// let f = |x, y, z| x + y + z; 22 | /// let (x, y, z) = (1, 2, 3); 23 | /// 24 | /// let fx = f.curry(x); 25 | /// 26 | /// assert_eq!(fx(y, z), f(x, y, z)); 27 | /// 28 | /// let fxz = fx.rcurry(z); 29 | /// 30 | /// assert_eq!(fxz(y), f(x, y, z)); 31 | /// 32 | /// let fxyz = fxz.curry(y); 33 | /// 34 | /// assert_eq!(fxyz(), f(x, y, z)); 35 | /// ``` 36 | #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] 37 | pub struct Curried 38 | where 39 | LX: Tuple, 40 | RX: Tuple 41 | { 42 | args_left: LX, 43 | args_right: RX, 44 | func: F 45 | } 46 | 47 | impl Curried 48 | where 49 | LX: Tuple, 50 | RX: Tuple 51 | { 52 | pub(crate) const fn new(args_left: LX, args_right: RX, func: F) -> Self 53 | { 54 | Self { 55 | args_left, 56 | args_right, 57 | func 58 | } 59 | } 60 | } 61 | 62 | impl Curried<(C,), (), F> 63 | { 64 | pub(crate) const fn curry(func: F, arg: C) -> Self 65 | { 66 | Self::new((arg,), (), func) 67 | } 68 | } 69 | 70 | #[cfg(feature = "rcurry")] 71 | impl Curried<(), (C,), F> 72 | { 73 | pub(crate) const fn rcurry(func: F, arg: C) -> Self 74 | { 75 | Self::new((), (arg,), func) 76 | } 77 | } 78 | 79 | impl const FnOnce for Curried 80 | where 81 | LX: Tuple, 82 | X: Tuple, 83 | RX: Tuple, 84 | (LX, X, RX): ~const ConcatArgs, 85 | F: ~const FnOnce 86 | { 87 | type Output = F::Output; 88 | 89 | extern "rust-call" fn call_once(self, args: X) -> Self::Output 90 | { 91 | self.func.call_once((self.args_left, args, self.args_right).concat_args()) 92 | } 93 | } 94 | 95 | impl const FnMut for Curried 96 | where 97 | LX: Tuple + Copy, 98 | X: Tuple, 99 | RX: Tuple + Copy, 100 | (LX, X, RX): ~const ConcatArgs, 101 | F: ~const FnMut 102 | { 103 | extern "rust-call" fn call_mut(&mut self, args: X) -> Self::Output 104 | { 105 | self.func.call_mut((self.args_left, args, self.args_right).concat_args()) 106 | } 107 | } 108 | 109 | impl const Fn for Curried 110 | where 111 | LX: Tuple + Copy, 112 | X: Tuple, 113 | RX: Tuple + Copy, 114 | (LX, X, RX): ~const ConcatArgs, 115 | F: ~const Fn 116 | { 117 | extern "rust-call" fn call(&self, args: X) -> Self::Output 118 | { 119 | self.func.call((self.args_left, args, self.args_right).concat_args()) 120 | } 121 | } 122 | 123 | impl AsyncFnOnce for Curried 124 | where 125 | LX: Tuple, 126 | X: Tuple, 127 | RX: Tuple, 128 | (LX, X, RX): ConcatArgs, 129 | F: AsyncFnOnce 130 | { 131 | type Output = F::Output; 132 | 133 | type CallOnceFuture = F::CallOnceFuture; 134 | 135 | extern "rust-call" fn async_call_once(self, args: X) -> Self::CallOnceFuture 136 | { 137 | self.func.async_call_once((self.args_left, args, self.args_right).concat_args()) 138 | } 139 | } 140 | 141 | impl AsyncFnMut for Curried 142 | where 143 | LX: Tuple + Copy, 144 | X: Tuple, 145 | RX: Tuple + Copy, 146 | (LX, X, RX): ConcatArgs, 147 | F: AsyncFnMut 148 | { 149 | type CallRefFuture<'a> = F::CallRefFuture<'a> 150 | where 151 | Self: 'a; 152 | 153 | extern "rust-call" fn async_call_mut(&mut self, args: X) -> Self::CallRefFuture<'_> 154 | { 155 | self.func.async_call_mut((self.args_left, args, self.args_right).concat_args()) 156 | } 157 | } 158 | 159 | impl AsyncFn for Curried 160 | where 161 | LX: Tuple + Copy, 162 | X: Tuple, 163 | RX: Tuple + Copy, 164 | (LX, X, RX): ConcatArgs, 165 | F: AsyncFn 166 | { 167 | extern "rust-call" fn async_call(&self, args: X) -> Self::CallRefFuture<'_> 168 | { 169 | self.func.async_call((self.args_left, args, self.args_right).concat_args()) 170 | } 171 | } --------------------------------------------------------------------------------