├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── benches └── bench.rs ├── ci └── script.sh ├── publish.sh ├── rustfmt.toml ├── scrapmetal-derive ├── Cargo.toml └── src │ └── lib.rs ├── src ├── lib.rs ├── mutation.rs ├── query.rs ├── term_impls.rs └── transform.rs └── tests ├── company.rs └── derive_edge_cases.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /scrapmetal-derive/target/ 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | 4 | rust: 5 | - nightly 6 | 7 | cache: cargo 8 | 9 | env: 10 | matrix: 11 | - JOB="test" PROFILE="" FEATURES="" 12 | - JOB="test" PROFILE="--release" FEATURES="" 13 | - JOB="bench" PROFILE="--release" FEATURES="" 14 | 15 | matrix: 16 | fast_finish: true 17 | 18 | script: ./ci/script.sh 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | Released YYYY-MM-DD. 4 | 5 | #### Added 6 | 7 | * TODO (or remove section if none) 8 | 9 | #### Changed 10 | 11 | * TODO (or remove section if none) 12 | 13 | #### Deprecated 14 | 15 | * TODO (or remove section if none) 16 | 17 | #### Removed 18 | 19 | * TODO (or remove section if none) 20 | 21 | #### Fixed 22 | 23 | * TODO (or remove section if none) 24 | 25 | #### Security 26 | 27 | * TODO (or remove section if none) 28 | 29 | -------------------------------------------------------------------------------- 30 | 31 | # 0.1.0 32 | 33 | Released 2017-08-03. 34 | 35 | #### Added 36 | 37 | * Initial release! :) 38 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `scrapmetal` 2 | 3 | Hi! We'd love to have your contributions! If you want help or mentorship, reach 4 | out to us in a GitHub issue, or stop by [#rust on irc.mozilla.org](irc://irc.mozilla.org#rust) and ping 5 | `fitzgen`. 6 | 7 | 8 | 9 | 10 | 11 | - [Code of Conduct](#code-of-conduct) 12 | - [Filing an Issue](#filing-an-issue) 13 | - [Looking to Start Contributing to `scrapmetal`?](#looking-to-start-contributing-to-scrapmetal) 14 | - [Building](#building) 15 | - [Testing](#testing) 16 | - [Automatic code formatting](#automatic-code-formatting) 17 | - [Pull Requests and Code Reviews](#pull-requests-and-code-reviews) 18 | 19 | 20 | 21 | ## Code of Conduct 22 | 23 | We abide by the [Rust Code of Conduct][coc] and ask that you do as well. 24 | 25 | [coc]: https://www.rust-lang.org/en-US/conduct.html 26 | 27 | ## Filing an Issue 28 | 29 | Think you've found a bug? File an issue! To help us understand and reproduce the 30 | issue, provide us with: 31 | 32 | * A test case that can be used to reproduce the bug 33 | * The steps to reproduce the bug with the test case 34 | * The expected behavior 35 | * The actual actual (buggy) behavior 36 | 37 | ## Looking to Start Contributing to `scrapmetal`? 38 | 39 | * [Issues labeled "easy"](https://github.com/fitzgen/scrapmetal/issues?q=is%3Aopen+is%3Aissue+label%3Aeasy) 40 | 41 | ## Building 42 | 43 | Make sure that `rustup` is using nightly Rust, since `scrapmetal` depends on 44 | specialization. 45 | 46 | ``` 47 | $ cd scrapmetal/ 48 | $ rustup override set nightly 49 | $ cargo build 50 | ``` 51 | 52 | ## Testing 53 | 54 | Once you've already told `rustup` to use nightly Rust with `scrapmetal`, all you 55 | need to do is: 56 | 57 | ``` 58 | $ cargo test 59 | ``` 60 | 61 | ## Automatic code formatting 62 | 63 | We use [`rustfmt`](https://github.com/rust-lang-nursery/rustfmt) to enforce a consistent code style across the whole 64 | `scrapmetal` code base. 65 | 66 | You can install the latest version of `rustfmt` with this command: 67 | 68 | ``` 69 | $ rustup update nightly 70 | $ cargo install -f rustfmt-nightly 71 | ``` 72 | 73 | Ensure that `~/.cargo/bin` is on your path. 74 | 75 | Once that is taken care of, you can (re)format all code by running this command: 76 | 77 | ``` 78 | $ cargo fmt 79 | ``` 80 | 81 | The code style is described in the `rustfmt.toml` file in top level of the repo. 82 | 83 | ## Pull Requests and Code Reviews 84 | 85 | Ensure that each commit stands alone, and passes tests. This enables better `git 86 | bisect`ing when needed. If your commits do not stand on their own, then rebase 87 | them on top of the latest master and squash them into a single commit. 88 | 89 | All pull requests undergo code review before merging. 90 | 91 | Unsure who to ask for review? Ask any of: 92 | 93 | * `@fitzgen` 94 | * TODO: need more maintainers *hint hint* 95 | 96 | More resources: 97 | 98 | * [A Beginner's Guide to Rebasing and Squashing](https://github.com/servo/servo/wiki/Beginner's-guide-to-rebasing-and-squashing) 99 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scrapmetal" 3 | version = "0.1.0" 4 | repository = "https://github.com/fitzgen/scrapmetal" 5 | license = "Apache-2.0/MIT" 6 | authors = ["Nick Fitzgerald "] 7 | description = "Scrap Your Rust Boilerplate" 8 | 9 | [dependencies] 10 | 11 | [dev-dependencies.scrapmetal-derive] 12 | path = "scrapmetal-derive" 13 | version = "0.1.0" 14 | 15 | [workspace] 16 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 The Rust Project Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `scrapmetal`: Scrap Your Rust Boilerplate 2 | 3 | [![Build Status](https://travis-ci.org/fitzgen/scrapmetal.png?branch=master)](https://travis-ci.org/fitzgen/scrapmetal) [![scrapmetal on crates.io](https://img.shields.io/crates/v/scrapmetal.svg)](https://crates.io/crates/scrapmetal) [![scrapmetal on docs.rs](https://docs.rs/scrapmetal/badge.svg)](https://docs.rs/scrapmetal/) 4 | 5 | Generic transformations, queries, and mutations for Rust without the 6 | boilerplate. 7 | 8 | A port of some of the ideas and code 9 | from 10 | ["Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming" by Lämmel and Peyton Jones](https://www.microsoft.com/en-us/research/wp-content/uploads/2003/01/hmap.pdf) to 11 | Rust. 12 | 13 | ⚠ Depends on the specialization nightly Rust feature. ⚠ 14 | 15 | -------------------------------------------------------------------------------- 16 | 17 | Say we work on some software that models companies, their departments, 18 | sub-departments, employees, and salaries. We might have some type definitions 19 | similar to this: 20 | 21 | ```rust 22 | pub struct Company(pub Vec); 23 | 24 | pub struct Department(pub Name, pub Manager, pub Vec); 25 | 26 | pub enum SubUnit { 27 | Person(Employee), 28 | Department(Box), 29 | } 30 | 31 | pub struct Employee(pub Person, pub Salary); 32 | 33 | pub struct Person(pub Name, pub Address); 34 | 35 | pub struct Salary(pub f64); 36 | 37 | pub type Manager = Employee; 38 | pub type Name = &'static str; 39 | pub type Address = &'static str; 40 | ``` 41 | 42 | One of our companies has had a morale problem lately, and we want to transform 43 | it into a new company where everyone is excited to come in every Monday through 44 | Friday morning. But we can't really change the nature of the work, so we figure 45 | we can just give the whole company a 10% raise and call it close enough. This 46 | requires writing a bunch of functions with type signatures like `fn(self, k: 47 | f64) -> Self` for every type that makes up a `Company`, and since we recognize 48 | the pattern, we should be good Rustaceans and formalize it with a trait: 49 | 50 | ```rust 51 | pub trait Increase: Sized { 52 | fn increase(self, k: f64) -> Self; 53 | } 54 | ``` 55 | 56 | A company with increased employee salaries is made by increasing the salaries of 57 | each of its departments' employees: 58 | 59 | ```rust 60 | impl Increase for Company { 61 | fn increase(self, k: f64) -> Company { 62 | Company( 63 | self.0 64 | .into_iter() 65 | .map(|d| d.increase(k)) 66 | .collect() 67 | ) 68 | } 69 | } 70 | ``` 71 | 72 | A department with increased employee salaries is made by increasing its 73 | manager's salary and the salary of every employee in its sub-units: 74 | 75 | ```rust 76 | impl Increase for Department { 77 | fn increase(self, k: f64) -> Department { 78 | Department( 79 | self.0, 80 | self.1.increase(k), 81 | self.2 82 | .into_iter() 83 | .map(|s| s.increase(k)) 84 | .collect(), 85 | ) 86 | } 87 | } 88 | ``` 89 | 90 | A sub-unit is either a single employee or a sub-department, so either increase 91 | the employee's salary, or increase the salaries of all the people in the 92 | sub-department respectively: 93 | 94 | ```rust 95 | impl Increase for SubUnit { 96 | fn increase(self, k: f64) -> SubUnit { 97 | match self { 98 | SubUnit::Person(e) => { 99 | SubUnit::Person(e.increase(k)) 100 | } 101 | SubUnit::Department(d) => { 102 | SubUnit::Department(Box::new(d.increase(k))) 103 | } 104 | } 105 | } 106 | } 107 | ``` 108 | 109 | An employee with an increased salary, is that same employee with the salary 110 | increased: 111 | 112 | ```rust 113 | impl Increase for Employee { 114 | fn increase(self, k: f64) -> Employee { 115 | Employee(self.0, self.1.increase(k)) 116 | } 117 | } 118 | ``` 119 | 120 | And finally, a lone salary can be increased: 121 | 122 | ```rust 123 | impl Increase for Salary { 124 | fn increase(self, k: f64) -> Salary { 125 | Salary(self.0 * (1.0 + k)) 126 | } 127 | } 128 | ``` 129 | 130 | Pretty straightforward. 131 | 132 | But at the same time, that's a *whole* lot of boilerplate. The only interesting 133 | part that has anything to do with actually increasing salaries is the `impl 134 | Increase for Salary`. The rest of the code is just traversal of the data 135 | structures. If we were to write a function to rename all the employees in a 136 | company, most of this code would remain the same. Surely there's a way to factor 137 | all this boilerplate out so we don't have to manually write it all the time? 138 | 139 | Enter `scrapmetal`: 140 | 141 | ```rust 142 | // Imports 143 | #[macro_use] 144 | extern crate scrapmetal_derive; 145 | extern crate scrapmetal; 146 | use scrapmetal::{Everywhere, Transformation}; 147 | 148 | // Add derive(Term) to type definitions 149 | #[derive(Term)] 150 | pub struct Company(pub Vec); 151 | // Etc... 152 | 153 | // Define the `increase` transformation 154 | let increase = |s: Salary| Salary(s.0 * 1.1); 155 | let mut increase = Everywhere::new(Transformation::new(increase)); 156 | 157 | // Use the `increase` transformation 158 | let new_company = increase.transform(old_company); 159 | ``` 160 | 161 | Nothing more required! 162 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | include!(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/company.rs")); 3 | 4 | extern crate test; 5 | 6 | #[bench] 7 | fn bench_increase_with_boilerplate(b: &mut test::Bencher) { 8 | b.iter(|| { 9 | test::black_box(Company::default().increase(1.0)); 10 | }); 11 | } 12 | 13 | #[bench] 14 | fn bench_increase_scrapping_boilerplate(b: &mut test::Bencher) { 15 | let transformation = Transformation::new(|s: Salary| Salary(s.0 + 1.0)); 16 | let mut increase = Everywhere::new(transformation); 17 | b.iter(|| { 18 | test::black_box(increase.transform(Company::default())); 19 | }); 20 | } 21 | 22 | #[bench] 23 | fn bench_increase_in_place_with_boilerplate(b: &mut test::Bencher) { 24 | let mut company = Company::default(); 25 | b.iter(|| { 26 | company.increase_in_place(1.0); 27 | test::black_box(&mut company); 28 | }); 29 | } 30 | 31 | #[bench] 32 | fn bench_increase_in_place_scrapping_boilerplate(b: &mut test::Bencher) { 33 | let mutation = Mutation::new(|s: &mut Salary| s.0 += 1.0); 34 | let mut increase_in_place = MutateEverything::new(mutation); 35 | 36 | let mut company = Company::default(); 37 | b.iter(|| { 38 | increase_in_place.mutate(&mut company); 39 | test::black_box(&mut company); 40 | }); 41 | } 42 | 43 | #[bench] 44 | fn bench_highest_salary_with_boilerplate(b: &mut test::Bencher) { 45 | let company = Company::default(); 46 | b.iter(|| { 47 | test::black_box(company.highest_salary()); 48 | }); 49 | } 50 | 51 | #[bench] 52 | fn bench_highest_salary_scrapping_boilerplate(b: &mut test::Bencher) { 53 | let query = Query::new(|e: &Employee| Some(e.1.clone())); 54 | let mut highest_salary = Everything::new(query, cmp::max); 55 | let company = Company::default(); 56 | b.iter(|| { 57 | test::black_box(highest_salary.query(&company)); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /ci/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eux 4 | 5 | case "$JOB" in 6 | "test") 7 | cargo build $PROFILE --verbose --features "$FEATURES" 8 | cargo test $PROFILE --verbose --features "$FEATURES" 9 | ;; 10 | "bench") 11 | if [[ "$PROFILE" != "--release" ]]; then 12 | echo Benching a non-release build?? 13 | exit 1 14 | fi 15 | cargo bench --verbose --features "$FEATURES" 16 | ;; 17 | *) 18 | echo Unknown job: "$JOB" 19 | exit 1 20 | ;; 21 | esac 22 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | 5 | cd "$(dirname $0)" 6 | 7 | cd ./scrapmetal-derive/ 8 | cargo publish --dry-run 9 | 10 | cd .. 11 | cargo publish --dry-run 12 | 13 | cd ./scrapmetal-derive/ 14 | cargo publish 15 | 16 | cd .. 17 | cargo publish 18 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | reorder_imported_names = true 3 | write_mode = "Overwrite" 4 | closure_block_indent_threshold = 0 5 | use_try_shorthand = true 6 | -------------------------------------------------------------------------------- /scrapmetal-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Nick Fitzgerald "] 3 | description = "Custom derive support for scrapmetal." 4 | license = "Apache-2.0/MIT" 5 | name = "scrapmetal-derive" 6 | repository = "https://github.com/fitzgen/scrapmetal" 7 | version = "0.1.0" 8 | workspace = ".." 9 | 10 | [dependencies] 11 | quote = "0.3.15" 12 | syn = "0.11.11" 13 | 14 | [lib] 15 | proc_macro = true 16 | -------------------------------------------------------------------------------- /scrapmetal-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "1000"] 2 | 3 | extern crate proc_macro; 4 | extern crate syn; 5 | #[macro_use] 6 | extern crate quote; 7 | 8 | use proc_macro::TokenStream; 9 | use quote::Tokens; 10 | 11 | #[proc_macro_derive(Term)] 12 | pub fn derive_into_heap(input: TokenStream) -> TokenStream { 13 | let source = input.to_string(); 14 | let ast = syn::parse_derive_input(&source).unwrap(); 15 | let expanded = impl_term(&ast); 16 | 17 | // Uncomment to debug the generated code... 18 | // println!("\n\n{:?}", expanded); 19 | 20 | expanded.parse().unwrap() 21 | } 22 | 23 | fn impl_term(ast: &syn::DeriveInput) -> Tokens { 24 | match ast.body { 25 | syn::Body::Struct(ref data) => impl_term_for_struct(ast, data), 26 | syn::Body::Enum(ref variants) => impl_term_for_enum(ast, variants), 27 | } 28 | } 29 | 30 | fn impl_term_for_struct(ast: &syn::DeriveInput, data: &syn::VariantData) -> Tokens { 31 | match *data { 32 | syn::VariantData::Struct(ref fields) => impl_term_for_struct_struct(ast, fields), 33 | syn::VariantData::Tuple(ref fields) => impl_term_for_tuple_struct(ast, fields), 34 | syn::VariantData::Unit => impl_term_for_unit_struct(ast), 35 | } 36 | } 37 | 38 | fn impl_term_for_struct_struct(ast: &syn::DeriveInput, fields: &[syn::Field]) -> Tokens { 39 | let name = &ast.ident; 40 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 41 | 42 | let transforms: Vec<_> = fields.iter() 43 | .map(|f| { 44 | quote! { 45 | #f.ident : f.transform(self.#f.ident) , 46 | } 47 | }) 48 | .collect(); 49 | 50 | let queries: Vec<_> = fields.iter() 51 | .map(|f| { 52 | quote! { 53 | let r = q.query(&self.#f.ident); 54 | each(q, r); 55 | } 56 | }) 57 | .collect(); 58 | 59 | let mutations: Vec<_> = fields.iter() 60 | .map(|f| { 61 | quote! { 62 | let r = m.mutate(&mut self.#f.indent); 63 | each(m, r); 64 | } 65 | }) 66 | .collect(); 67 | 68 | quote! { 69 | impl #impl_generics ::scrapmetal::Term for #name #ty_generics 70 | #where_clause 71 | { 72 | #[inline] 73 | #[allow(unused_variables)] 74 | #[allow(unused_mut)] 75 | fn map_one_transform(self, f: &mut F) -> Self 76 | where 77 | F: ::scrapmetal::GenericTransform, 78 | { 79 | Self { 80 | #( #transforms )* 81 | } 82 | } 83 | 84 | #[inline] 85 | #[allow(unused_variables)] 86 | #[allow(unused_mut)] 87 | fn map_one_query(&self, q: &mut Q, mut each: F) 88 | where 89 | Q: ::scrapmetal::GenericQuery, 90 | F: FnMut(&mut Q, R), 91 | { 92 | #( #queries )* 93 | } 94 | 95 | #[inline] 96 | #[allow(unused_variables)] 97 | #[allow(unused_mut)] 98 | fn map_one_mutation(&mut self, mutation: &mut M, mut each: F) 99 | where 100 | M: ::scrapmetal::GenericMutate, 101 | F: FnMut(&mut M, R), 102 | { 103 | #( #mutations )* 104 | } 105 | } 106 | } 107 | } 108 | 109 | fn impl_term_for_tuple_struct(ast: &syn::DeriveInput, fields: &[syn::Field]) -> Tokens { 110 | let name = &ast.ident; 111 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 112 | 113 | let fields: Vec<_> = (0..fields.len()).map(syn::Ident::new).collect(); 114 | 115 | let transforms: Vec<_> = fields.iter() 116 | .map(|i| { 117 | quote! { 118 | f.transform(self.#i) , 119 | } 120 | }) 121 | .collect(); 122 | 123 | let queries: Vec<_> = fields.iter() 124 | .map(|i| { 125 | quote! { 126 | let r = q.query(&self.#i); 127 | each(q, r); 128 | } 129 | }) 130 | .collect(); 131 | 132 | let mutations: Vec<_> = fields.iter() 133 | .map(|i| { 134 | quote! { 135 | let r = m.mutate(&mut self.#i); 136 | each(m, r); 137 | } 138 | }) 139 | .collect(); 140 | 141 | quote! { 142 | impl #impl_generics ::scrapmetal::Term for #name #ty_generics 143 | #where_clause 144 | { 145 | #[inline] 146 | #[allow(unused_variables)] 147 | #[allow(unused_mut)] 148 | fn map_one_transform(self, f: &mut F) -> Self 149 | where 150 | F: ::scrapmetal::GenericTransform, 151 | { 152 | #name ( #( #transforms )* ) 153 | } 154 | 155 | #[inline] 156 | #[allow(unused_variables)] 157 | #[allow(unused_mut)] 158 | fn map_one_query(&self, q: &mut Q, mut each: F) 159 | where 160 | Q: ::scrapmetal::GenericQuery, 161 | F: FnMut(&mut Q, R), 162 | { 163 | #( #queries )* 164 | } 165 | 166 | #[inline] 167 | #[allow(unused_variables)] 168 | #[allow(unused_mut)] 169 | fn map_one_mutation(&mut self, m: &mut M, mut each: F) 170 | where 171 | M: ::scrapmetal::GenericMutate, 172 | F: FnMut(&mut M, R), 173 | { 174 | #( #mutations )* 175 | } 176 | } 177 | } 178 | } 179 | 180 | fn impl_term_for_unit_struct(ast: &syn::DeriveInput) -> Tokens { 181 | let name = &ast.ident; 182 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 183 | 184 | quote! { 185 | impl #impl_generics ::scrapmetal::Term for #name #ty_generics 186 | #where_clause 187 | { 188 | #[inline(always)] 189 | fn map_one_transform(self, _: &mut F) -> Self 190 | where 191 | F: ::scrapmetal::GenericTransform, 192 | { 193 | self 194 | } 195 | 196 | #[inline(always)] 197 | fn map_one_query(&self, _: &mut Q, _: F) 198 | where 199 | Q: ::scrapmetal::GenericQuery, 200 | F: FnMut(&mut Q, R), 201 | {} 202 | 203 | #[inline(always)] 204 | fn map_one_mutation(&mut self, _: &mut M, _: F) 205 | where 206 | M: ::scrapmetal::GenericMutate, 207 | F: FnMut(&mut M, R), 208 | {} 209 | } 210 | } 211 | } 212 | 213 | fn impl_term_for_enum(ast: &syn::DeriveInput, variants: &[syn::Variant]) -> Tokens { 214 | let name = &ast.ident; 215 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 216 | 217 | let transforms: Vec<_> = variants.iter() 218 | .map(|v| { 219 | let variant_ident = &v.ident; 220 | match v.data { 221 | syn::VariantData::Struct(ref fields) => { 222 | let field_names: Vec<_> = fields.iter() 223 | .map(|f| { 224 | let ident = &f.ident; 225 | quote! { 226 | #ident , 227 | } 228 | }) 229 | .collect(); 230 | 231 | let field_transforms: Vec<_> = fields.iter() 232 | .map(|f| { 233 | let ident = &f.ident; 234 | quote! { 235 | #ident : f.transform( #ident ) , 236 | } 237 | }) 238 | .collect(); 239 | 240 | quote! { 241 | #name :: #variant_ident { #( #field_names )* } => { 242 | #name :: #variant_ident { #( #field_transforms )* } 243 | } 244 | } 245 | } 246 | syn::VariantData::Tuple(ref fields) => { 247 | let tuple_names: Vec<_> = (0..fields.len()) 248 | .map(|i| { 249 | let c = ('a' as u8 + i as u8) as char; 250 | let mut s = String::with_capacity(1); 251 | s.push(c); 252 | syn::Ident::new(s) 253 | }) 254 | .collect(); 255 | 256 | let tuple_patterns: Vec<_> = tuple_names.iter() 257 | .map(|p| { 258 | quote! { 259 | #p , 260 | } 261 | }) 262 | .collect(); 263 | 264 | let tuple_transforms: Vec<_> = tuple_names.iter() 265 | .map(|p| { 266 | quote! { 267 | f.transform( #p ) , 268 | } 269 | }) 270 | .collect(); 271 | 272 | quote! { 273 | #name :: #variant_ident ( #( #tuple_patterns )* ) => { 274 | #name :: #variant_ident ( #( #tuple_transforms )* ) 275 | } 276 | } 277 | } 278 | syn::VariantData::Unit => { 279 | quote! { 280 | // Nothing to do here. 281 | } 282 | } 283 | } 284 | }) 285 | .collect(); 286 | 287 | let queries: Vec<_> = variants.iter() 288 | .map(|v| { 289 | let variant_ident = &v.ident; 290 | match v.data { 291 | syn::VariantData::Struct(ref fields) => { 292 | let field_names: Vec<_> = fields.iter() 293 | .map(|f| { 294 | let ident = &f.ident; 295 | quote! { 296 | ref #ident , 297 | } 298 | }) 299 | .collect(); 300 | 301 | let field_queries: Vec<_> = fields.iter() 302 | .map(|f| { 303 | let ident = &f.ident; 304 | quote! { 305 | let r = q.query( #ident ); 306 | each(q, r); 307 | } 308 | }) 309 | .collect(); 310 | 311 | quote! { 312 | #name :: #variant_ident { #( #field_names )* } => { 313 | #( #field_queries )* 314 | } 315 | } 316 | } 317 | syn::VariantData::Tuple(ref fields) => { 318 | let tuple_names: Vec<_> = (0..fields.len()) 319 | .map(|i| { 320 | let c = ('a' as u8 + i as u8) as char; 321 | let mut s = String::with_capacity(1); 322 | s.push(c); 323 | syn::Ident::new(s) 324 | }) 325 | .collect(); 326 | 327 | let tuple_patterns: Vec<_> = tuple_names.iter() 328 | .map(|p| { 329 | quote! { 330 | ref #p , 331 | } 332 | }) 333 | .collect(); 334 | 335 | let tuple_queries: Vec<_> = tuple_names.iter() 336 | .map(|p| { 337 | quote! { 338 | let r = q.query( #p ); 339 | each(q, r); 340 | } 341 | }) 342 | .collect(); 343 | 344 | quote! { 345 | #name :: #variant_ident ( #( #tuple_patterns )* ) => { 346 | #( #tuple_queries )* 347 | } 348 | } 349 | } 350 | syn::VariantData::Unit => { 351 | quote! { 352 | // Nothing to do here. 353 | } 354 | } 355 | } 356 | }) 357 | .collect(); 358 | 359 | let mutations: Vec<_> = variants.iter() 360 | .map(|v| { 361 | let variant_ident = &v.ident; 362 | match v.data { 363 | syn::VariantData::Struct(ref fields) => { 364 | let field_names: Vec<_> = fields.iter() 365 | .map(|f| { 366 | let ident = &f.ident; 367 | quote! { 368 | ref mut #ident , 369 | } 370 | }) 371 | .collect(); 372 | 373 | let field_mutations: Vec<_> = fields.iter() 374 | .map(|f| { 375 | let ident = &f.ident; 376 | quote! { 377 | let r = m.mutate( #ident ); 378 | each(m, r); 379 | } 380 | }) 381 | .collect(); 382 | 383 | quote! { 384 | #name :: #variant_ident { #( #field_names )* } => { 385 | #( #field_mutations )* 386 | } 387 | } 388 | } 389 | syn::VariantData::Tuple(ref fields) => { 390 | let tuple_names: Vec<_> = (0..fields.len()) 391 | .map(|i| { 392 | let c = ('a' as u8 + i as u8) as char; 393 | let mut s = String::with_capacity(1); 394 | s.push(c); 395 | syn::Ident::new(s) 396 | }) 397 | .collect(); 398 | 399 | let tuple_patterns: Vec<_> = tuple_names.iter() 400 | .map(|p| { 401 | quote! { 402 | ref mut #p , 403 | } 404 | }) 405 | .collect(); 406 | 407 | let tuple_mutations: Vec<_> = tuple_names.iter() 408 | .map(|p| { 409 | quote! { 410 | let r = m.mutate( #p ); 411 | each(m, r); 412 | } 413 | }) 414 | .collect(); 415 | 416 | quote! { 417 | #name :: #variant_ident ( #( #tuple_patterns )* ) => { 418 | #( #tuple_mutations )* 419 | } 420 | } 421 | } 422 | syn::VariantData::Unit => { 423 | quote! { 424 | // Nothing to do here. 425 | } 426 | } 427 | } 428 | }) 429 | .collect(); 430 | 431 | quote! { 432 | impl #impl_generics ::scrapmetal::Term for #name #ty_generics 433 | #where_clause 434 | { 435 | #[inline] 436 | #[allow(unused_variables)] 437 | #[allow(unused_mut)] 438 | fn map_one_transform(self, f: &mut F) -> Self 439 | where 440 | F: ::scrapmetal::GenericTransform, 441 | { 442 | match self { 443 | #( #transforms )* 444 | } 445 | } 446 | 447 | #[inline] 448 | #[allow(unused_variables)] 449 | #[allow(unused_mut)] 450 | fn map_one_query(&self, q: &mut Q, mut each: F) 451 | where 452 | Q: ::scrapmetal::GenericQuery, 453 | F: FnMut(&mut Q, R), 454 | { 455 | match *self { 456 | #( #queries )* 457 | } 458 | } 459 | 460 | #[inline] 461 | #[allow(unused_variables)] 462 | #[allow(unused_mut)] 463 | fn map_one_mutation(&mut self, m: &mut M, mut each: F) 464 | where 465 | M: ::scrapmetal::GenericMutate, 466 | F: FnMut(&mut M, R), 467 | { 468 | match *self { 469 | #( #mutations )* 470 | } 471 | } 472 | } 473 | } 474 | } 475 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Scrap Your Boilerplate! 2 | //! 3 | //! This crate provides the traversing, transforming, and querying helpers and 4 | //! combinators from the Haskell paper "Scrap Your Boilerplate: A Practical 5 | //! Design Pattern for Generic Programming" by Lämmel and Peyton Jones to Rust. 6 | //! 7 | #![feature(specialization)] 8 | #![deny(missing_docs)] 9 | #![deny(missing_debug_implementations)] 10 | 11 | mod mutation; 12 | mod query; 13 | mod term_impls; 14 | mod transform; 15 | 16 | pub use mutation::*; 17 | pub use query::*; 18 | pub use transform::*; 19 | 20 | /// Dynamically cast a value to a `T`. 21 | trait Cast: Sized { 22 | fn cast(self) -> Result; 23 | } 24 | 25 | /// A default blanket implementation that says the value cannot be cast to `T`. 26 | impl Cast for U { 27 | #[inline(always)] 28 | default fn cast(self) -> Result { 29 | Err(self) 30 | } 31 | } 32 | 33 | /// A specialization for when `Self=T` that allows the cast to succeed. 34 | impl Cast for T { 35 | #[inline(always)] 36 | fn cast(self) -> Result { 37 | Ok(self) 38 | } 39 | } 40 | 41 | /// A `Term` is a value that can be mapped or queried. 42 | pub trait Term: Sized { 43 | /// Perform one-layer traversal and transformation of this value's direct 44 | /// children. 45 | fn map_one_transform(self, f: &mut F) -> Self 46 | where 47 | F: GenericTransform; 48 | 49 | /// Perform one-layer traversal and immutable querying of this value's 50 | /// direct children, calling `each` on each of the query result for each 51 | /// direct child. 52 | fn map_one_query(&self, query: &mut Q, each: F) 53 | where 54 | Q: GenericQuery, 55 | F: FnMut(&mut Q, R); 56 | 57 | /// Perform one-layer traversal and mutable querying of this value's direct 58 | /// children, calling `each` on each of the query result for each direct 59 | /// child. 60 | fn map_one_mutation(&mut self, mutation: &mut M, each: F) 61 | where 62 | M: GenericMutate, 63 | F: FnMut(&mut M, R); 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::*; 69 | 70 | #[test] 71 | fn casting() { 72 | assert_eq!(Cast::::cast(1), Err(1)); 73 | assert_eq!(Cast::::cast(true), Ok(true)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/mutation.rs: -------------------------------------------------------------------------------- 1 | use super::{Cast, Term}; 2 | use std::marker::PhantomData; 3 | 4 | /// A similar work around as `GenericTransform`, but mutating in place and 5 | /// optionally returning some query type, rather than taking `self` and 6 | /// returning the same `Self` type. 7 | /// 8 | /// This is roughly equivalent to `for FnMut(&mut T) -> R`. 9 | pub trait GenericMutate { 10 | /// Call the query function on any `T`. 11 | fn mutate(&mut self, t: &mut T) -> R 12 | where 13 | T: Term; 14 | } 15 | 16 | /// A mutation creates some value `R` from mutable references to a `U`. It can 17 | /// be called on values of any type `T`, not just on values of type `U`, so it 18 | /// requires a default `R` value for when it is called on values which are not a 19 | /// `T`. 20 | /// 21 | /// This lifts an `FnMut(&mut U) -> R` into a `for FnMut(&mut T) -> R`. 22 | #[derive(Debug)] 23 | pub struct Mutation 24 | where 25 | M: FnMut(&mut U) -> R, 26 | D: FnMut() -> R, 27 | { 28 | make_default: D, 29 | mutation: M, 30 | phantom: PhantomData R>, 31 | } 32 | 33 | impl Mutation R, R> 34 | where 35 | M: FnMut(&mut U) -> R, 36 | R: Default, 37 | { 38 | /// Construct a new `Mutation`, returning `R::default()` for the cases where we 39 | /// query a value whose type is not `U`. 40 | #[inline] 41 | pub fn new(mutation: M) -> Mutation R, R> { 42 | Mutation { 43 | make_default: Default::default, 44 | mutation, 45 | phantom: PhantomData, 46 | } 47 | } 48 | } 49 | 50 | impl Mutation 51 | where 52 | M: FnMut(&mut U) -> R, 53 | D: FnMut() -> R, 54 | { 55 | /// Construct a new `Mutation`, returning `make_default()` for the cases where 56 | /// we query a value whose type is not `U`. 57 | #[inline] 58 | pub fn or_else(make_default: D, mutation: M) -> Mutation { 59 | Mutation { 60 | make_default, 61 | mutation, 62 | phantom: PhantomData, 63 | } 64 | } 65 | } 66 | 67 | impl GenericMutate for Mutation 68 | where 69 | M: FnMut(&mut U) -> R, 70 | D: FnMut() -> R, 71 | { 72 | #[inline] 73 | fn mutate(&mut self, t: &mut T) -> R 74 | where 75 | T: Term, 76 | { 77 | match Cast::<&mut U>::cast(t) { 78 | Ok(u) => (self.mutation)(u), 79 | Err(_) => (self.make_default)(), 80 | } 81 | } 82 | } 83 | 84 | /// Recursively perform a query in a top-down, left-to-right manner across a 85 | /// data structure. The `M: GenericMutate` queries individual values, while the `F: 86 | /// FnMut(R, R) -> R` joins the results of multiple queries into a single 87 | /// result. 88 | #[derive(Debug)] 89 | pub struct MutateEverything 90 | where 91 | M: GenericMutate, 92 | F: FnMut(R, R) -> R, 93 | { 94 | m: M, 95 | fold: F, 96 | phantom: PhantomData R>, 97 | } 98 | 99 | impl MutateEverything 100 | where 101 | M: GenericMutate, 102 | F: FnMut(R, R) -> R, 103 | { 104 | /// Construct a new `MutateEverything` query traversal. 105 | #[inline] 106 | pub fn with_query(m: M, fold: F) -> MutateEverything { 107 | MutateEverything { 108 | m, 109 | fold, 110 | phantom: PhantomData, 111 | } 112 | } 113 | } 114 | 115 | impl MutateEverything 116 | where 117 | M: GenericMutate<()>, 118 | { 119 | /// Construct a new `MutateEverything` query traversal. 120 | #[inline] 121 | pub fn new(m: M) -> MutateEverything { 122 | #[inline(always)] 123 | fn fold(_: (), _: ()) {} 124 | MutateEverything { 125 | m, 126 | fold, 127 | phantom: PhantomData, 128 | } 129 | } 130 | } 131 | 132 | impl GenericMutate for MutateEverything 133 | where 134 | M: GenericMutate, 135 | F: FnMut(R, R) -> R, 136 | { 137 | #[inline] 138 | fn mutate(&mut self, t: &mut T) -> R 139 | where 140 | T: Term, 141 | { 142 | let mut r = Some(self.m.mutate(t)); 143 | t.map_one_mutation(self, |me, rr| { 144 | r = Some((me.fold)(r.take().unwrap(), rr)); 145 | }); 146 | r.unwrap() 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::*; 153 | 154 | #[test] 155 | fn mutating() { 156 | let mut set_char_to_a = Mutation::new(|c: &mut char| { 157 | *c = 'a'; 158 | 1 159 | }); 160 | let mut char = 'b'; 161 | assert_eq!(set_char_to_a.mutate(&mut char), 1); 162 | assert_eq!(char, 'a'); 163 | 164 | let mut v = vec![1, 2, 3]; 165 | assert_eq!(set_char_to_a.mutate(&mut v), 0); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/query.rs: -------------------------------------------------------------------------------- 1 | use super::{Cast, Term}; 2 | use std::marker::PhantomData; 3 | 4 | /// A similar work around as `GenericTransform`, but returning a query type, rather 5 | /// than the same type. This is roughly equivalent to `for FnMut(&T) -> R`. 6 | pub trait GenericQuery { 7 | /// Call the query function on any `T`. 8 | fn query(&mut self, t: &T) -> R 9 | where 10 | T: Term; 11 | } 12 | 13 | /// A query non-destructively creates some value `R` from references to a 14 | /// `U`. It can be called on values of any type `T`, not just on values of type 15 | /// `U`, so it requires a default `R` value for when it is called on values 16 | /// which are not a `T`. 17 | /// 18 | /// This essentially lifts a `FnMut(&U) -> R` into a `for FnMut(&T) -> R`. 19 | #[derive(Debug)] 20 | pub struct Query 21 | where 22 | Q: FnMut(&U) -> R, 23 | D: FnMut() -> R, 24 | { 25 | make_default: D, 26 | query: Q, 27 | phantom: PhantomData R>, 28 | } 29 | 30 | impl Query R, R> 31 | where 32 | Q: FnMut(&U) -> R, 33 | R: Default, 34 | { 35 | /// Construct a new `Query`, returning `R::default()` for the cases where we 36 | /// query a value whose type is not `U`. 37 | #[inline] 38 | pub fn new(query: Q) -> Query R, R> { 39 | Query { 40 | make_default: Default::default, 41 | query, 42 | phantom: PhantomData, 43 | } 44 | } 45 | } 46 | 47 | impl Query 48 | where 49 | Q: FnMut(&U) -> R, 50 | D: FnMut() -> R, 51 | { 52 | /// Construct a new `Query`, returning `make_default()` for the cases where 53 | /// we query a value whose type is not `U`. 54 | #[inline] 55 | pub fn or_else(make_default: D, query: Q) -> Query { 56 | Query { 57 | make_default, 58 | query, 59 | phantom: PhantomData, 60 | } 61 | } 62 | } 63 | 64 | impl GenericQuery for Query 65 | where 66 | Q: FnMut(&U) -> R, 67 | D: FnMut() -> R, 68 | { 69 | #[inline] 70 | fn query(&mut self, t: &T) -> R 71 | where 72 | T: Term, 73 | { 74 | match Cast::<&U>::cast(t) { 75 | Ok(u) => (self.query)(u), 76 | Err(_) => (self.make_default)(), 77 | } 78 | } 79 | } 80 | 81 | /// Recursively perform a query in a top-down, left-to-right manner across a 82 | /// data structure. The `Q: Query` queries individual values, while the `F: 83 | /// FnMut(R, R) -> R` joins the results of multiple queries into a single 84 | /// result. 85 | #[derive(Debug)] 86 | pub struct Everything 87 | where 88 | Q: GenericQuery, 89 | F: FnMut(R, R) -> R, 90 | { 91 | q: Q, 92 | fold: F, 93 | phantom: PhantomData R>, 94 | } 95 | 96 | impl Everything 97 | where 98 | Q: GenericQuery, 99 | F: FnMut(R, R) -> R, 100 | { 101 | /// Construct a new `Everything` query traversal. 102 | #[inline] 103 | pub fn new(q: Q, fold: F) -> Everything { 104 | Everything { 105 | q, 106 | fold, 107 | phantom: PhantomData, 108 | } 109 | } 110 | } 111 | 112 | impl GenericQuery for Everything 113 | where 114 | Q: GenericQuery, 115 | F: FnMut(R, R) -> R, 116 | { 117 | #[inline] 118 | fn query(&mut self, t: &T) -> R 119 | where 120 | T: Term, 121 | { 122 | let mut r = Some(self.q.query(t)); 123 | t.map_one_query(self, |me, rr| { 124 | r = Some((me.fold)(r.take().unwrap(), rr)); 125 | }); 126 | r.unwrap() 127 | } 128 | } 129 | 130 | #[cfg(test)] 131 | mod tests { 132 | use super::*; 133 | 134 | #[test] 135 | fn querying() { 136 | let mut char_to_u32 = Query::or_else(|| 42, |c: &char| *c as u32); 137 | assert_eq!(char_to_u32.query(&'a'), 97); 138 | assert_eq!(char_to_u32.query(&'b'), 98); 139 | assert_eq!(char_to_u32.query(&vec![1, 2, 3]), 42); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/term_impls.rs: -------------------------------------------------------------------------------- 1 | use super::{GenericMutate, GenericQuery, GenericTransform, Term}; 2 | use std::collections::*; 3 | use std::iter::FromIterator; 4 | 5 | macro_rules! impl_trivial_term { 6 | ( $name:ty ) => { 7 | impl Term for $name { 8 | #[inline] 9 | fn map_one_transform(self, _: &mut F) -> Self 10 | where 11 | F: GenericTransform, 12 | { 13 | self 14 | } 15 | 16 | #[inline] 17 | fn map_one_query(&self, _: &mut Q, _: F) 18 | where 19 | Q: GenericQuery, 20 | F: FnMut(&mut Q, R), 21 | {} 22 | 23 | #[inline] 24 | fn map_one_mutation(&mut self, _: &mut M, _: F) 25 | where 26 | M: GenericMutate, 27 | F: FnMut(&mut M, R), 28 | {} 29 | } 30 | } 31 | } 32 | 33 | impl_trivial_term!(()); 34 | impl_trivial_term!(&'static str); 35 | impl_trivial_term!(bool); 36 | impl_trivial_term!(char); 37 | impl_trivial_term!(f32); 38 | impl_trivial_term!(f64); 39 | impl_trivial_term!(usize); 40 | impl_trivial_term!(u8); 41 | impl_trivial_term!(u16); 42 | impl_trivial_term!(u32); 43 | impl_trivial_term!(u64); 44 | impl_trivial_term!(isize); 45 | impl_trivial_term!(i8); 46 | impl_trivial_term!(i16); 47 | impl_trivial_term!(i32); 48 | impl_trivial_term!(i64); 49 | 50 | macro_rules! impl_tuple_term { 51 | ( $name:ident $( , $names:ident )* ) => { 52 | impl<$name $( , $names )* > Term for ($name $( , $names )* ) 53 | where 54 | $name: Term $(, $names : Term )* 55 | { 56 | #[inline] 57 | #[allow(non_snake_case)] 58 | fn map_one_transform(self, f: &mut FF) -> Self 59 | where 60 | FF: GenericTransform, 61 | { 62 | let ( $name $( , $names )* ) = self; 63 | ( f.transform( $name ) $( , f.transform( $names ) )* ) 64 | } 65 | 66 | #[inline] 67 | #[allow(non_snake_case)] 68 | fn map_one_query(&self, q: &mut Q, mut each: FF) 69 | where 70 | Q: GenericQuery, 71 | FF: FnMut(&mut Q, R), 72 | { 73 | let ( ref $name $( , ref $names )* ) = *self; 74 | let r = q.query( $name ); 75 | each(q, r); 76 | $( 77 | let r = q.query( $names ); 78 | each(q, r); 79 | )* 80 | } 81 | 82 | #[inline] 83 | #[allow(non_snake_case)] 84 | fn map_one_mutation(&mut self, m: &mut M, mut each: FF) 85 | where 86 | M: GenericMutate, 87 | FF: FnMut(&mut M, R), 88 | { 89 | let ( ref mut $name $( , ref mut $names )* ) = *self; 90 | let r = m.mutate( $name ); 91 | each(m, r); 92 | $( 93 | let r = m.mutate( $names ); 94 | each(m, r); 95 | )* 96 | } 97 | } 98 | } 99 | } 100 | 101 | impl_tuple_term!(A, B); 102 | impl_tuple_term!(A, B, C); 103 | impl_tuple_term!(A, B, C, D); 104 | impl_tuple_term!(A, B, C, D, E); 105 | impl_tuple_term!(A, B, C, D, E, F); 106 | impl_tuple_term!(A, B, C, D, E, F, G); 107 | impl_tuple_term!(A, B, C, D, E, F, G, H); 108 | impl_tuple_term!(A, B, C, D, E, F, G, H, I); 109 | impl_tuple_term!(A, B, C, D, E, F, G, H, I, J); 110 | impl_tuple_term!(A, B, C, D, E, F, G, H, I, J, K); 111 | impl_tuple_term!(A, B, C, D, E, F, G, H, I, J, K, L); 112 | 113 | impl Term for Vec 114 | where 115 | T: Term, 116 | { 117 | #[inline] 118 | fn map_one_transform(mut self, f: &mut F) -> Vec 119 | where 120 | F: GenericTransform, 121 | { 122 | self.drain(..).map(|t| f.transform(t)).collect() 123 | } 124 | 125 | #[inline] 126 | fn map_one_query(&self, query: &mut Q, mut each: F) 127 | where 128 | Q: GenericQuery, 129 | F: FnMut(&mut Q, R), 130 | { 131 | self.iter() 132 | .map(|t| { 133 | let r = query.query(t); 134 | each(query, r); 135 | }) 136 | .count(); 137 | } 138 | 139 | #[inline] 140 | fn map_one_mutation(&mut self, mutation: &mut M, mut each: F) 141 | where 142 | M: GenericMutate, 143 | F: FnMut(&mut M, R), 144 | { 145 | self.iter_mut() 146 | .map(|t| { 147 | let r = mutation.mutate(t); 148 | each(mutation, r); 149 | }) 150 | .count(); 151 | } 152 | } 153 | 154 | impl Term for Box 155 | where 156 | T: Sized + Term, 157 | { 158 | #[inline] 159 | fn map_one_transform(self, f: &mut F) -> Box 160 | where 161 | F: GenericTransform, 162 | { 163 | Box::new(f.transform(*self)) 164 | } 165 | 166 | #[inline] 167 | fn map_one_query(&self, query: &mut Q, mut each: F) 168 | where 169 | Q: GenericQuery, 170 | F: FnMut(&mut Q, R), 171 | { 172 | let r = query.query(&**self); 173 | each(query, r); 174 | } 175 | 176 | #[inline] 177 | fn map_one_mutation(&mut self, mutation: &mut M, mut each: F) 178 | where 179 | M: GenericMutate, 180 | F: FnMut(&mut M, R), 181 | { 182 | let r = mutation.mutate(&mut **self); 183 | each(mutation, r); 184 | } 185 | } 186 | 187 | macro_rules! impl_iter_term { 188 | ($iter:ty) => { 189 | impl Term for $iter 190 | where 191 | $iter: IntoIterator + FromIterator, 192 | for <'b> &'b $iter: IntoIterator, 193 | for <'b> &'b mut $iter: IntoIterator, 194 | T: Term 195 | { 196 | fn map_one_transform(self, f: &mut F) -> $iter 197 | where 198 | F: GenericTransform 199 | { 200 | self.into_iter().map(|x| f.transform(x)).collect() 201 | } 202 | 203 | fn map_one_query(&self, query: &mut Q, mut each: F) 204 | where 205 | Q: GenericQuery, 206 | F: FnMut(&mut Q, R) 207 | { 208 | self.into_iter().for_each(|t| { 209 | let r = query.query(t); 210 | each(query, r); 211 | }); 212 | } 213 | 214 | fn map_one_mutation<'a, M, R, F>(&'a mut self, mutation: &mut M, mut each: F) 215 | where 216 | M: GenericMutate, 217 | F: FnMut(&mut M, R) 218 | { 219 | self.into_iter().for_each(|t: &mut T| { 220 | let r = mutation.mutate(t); 221 | each(mutation, r); 222 | }); 223 | } 224 | } 225 | } 226 | } 227 | 228 | impl_iter_term!(LinkedList); 229 | impl_iter_term!(HashSet); 230 | impl_iter_term!(BTreeSet); 231 | impl_iter_term!(BinaryHeap); 232 | impl_iter_term!(VecDeque); 233 | 234 | 235 | macro_rules! impl_iterkey_term { 236 | ($iter:ty) => { 237 | impl Term for $iter 238 | where 239 | $iter: IntoIterator + FromIterator<(K,T)>, 240 | for <'b> &'b $iter: IntoIterator, 241 | for <'b> &'b mut $iter: IntoIterator, 242 | (K,T): Term 243 | { 244 | fn map_one_transform(self, f: &mut F) -> $iter 245 | where 246 | F: GenericTransform 247 | { 248 | self.into_iter().map(|x| f.transform(x)).collect() 249 | } 250 | 251 | fn map_one_query(&self, query: &mut Q, mut each: F) 252 | where 253 | Q: GenericQuery, 254 | F: FnMut(&mut Q, R) 255 | { 256 | self.into_iter().for_each(|t| { 257 | let r = query.query(t); 258 | each(query, r); 259 | }); 260 | } 261 | 262 | fn map_one_mutation<'a, M, R, F>(&'a mut self, mutation: &mut M, mut each: F) 263 | where 264 | M: GenericMutate, 265 | F: FnMut(&mut M, R) 266 | { 267 | self.into_iter().for_each(|t: &mut (K,T)| { 268 | let r = mutation.mutate(t); 269 | each(mutation, r); 270 | }); 271 | } 272 | } 273 | } 274 | } 275 | 276 | impl_iterkey_term!(HashMap); 277 | impl_iterkey_term!(BTreeMap); 278 | 279 | // TODO 280 | // 281 | // Below are all the stable `std` types that implement `Debug`, which I figure 282 | // is either all the stable `std` types, or pretty close to them. We need to 283 | // implement `Term` for all of these :) 284 | // 285 | // enum std::borrow::Cow 286 | // enum std::cmp::Ordering 287 | // enum std::collections::Bound 288 | // enum std::collections::btree_map::Entry 289 | // enum std::env::VarError 290 | // enum std::io::CharsError 291 | // enum std::io::ErrorKind 292 | // enum std::io::SeekFrom 293 | // enum std::net::IpAddr 294 | // enum std::net::Ipv6MulticastScope 295 | // enum std::net::Shutdown 296 | // enum std::net::SocketAddr 297 | // enum std::num::FpCategory 298 | // enum std::option::Option 299 | // enum std::os::raw::c_void 300 | // enum std::path::Component 301 | // enum std::path::Prefix 302 | // enum std::result::Result 303 | // enum std::str::pattern::SearchStep 304 | // enum std::string::ParseError 305 | // enum std::sync::TryLockError 306 | // enum std::sync::atomic::Ordering 307 | // enum std::sync::mpsc::RecvTimeoutError 308 | // enum std::sync::mpsc::TryRecvError 309 | // enum std::sync::mpsc::TrySendError 310 | // enum std::thread::LocalKeyState 311 | // struct std::any::TypeId 312 | // struct std::ascii::EscapeDefault 313 | // struct std::cell::BorrowError 314 | // struct std::cell::BorrowMutError 315 | // struct std::cell::Cell 316 | // struct std::cell::Ref 317 | // struct std::cell::RefCell 318 | // struct std::cell::RefMut 319 | // struct std::cell::UnsafeCell 320 | // struct std::char::CharTryFromError 321 | // struct std::char::DecodeUtf16Error 322 | // struct std::char::DecodeUtf8 323 | // struct std::char::EscapeDebug 324 | // struct std::char::EscapeDefault 325 | // struct std::char::EscapeUnicode 326 | // struct std::cmp::Reverse 327 | // struct std::collections::binary_heap::BinaryHeapPlace 328 | // struct std::collections::binary_heap::Drain 329 | // struct std::collections::binary_heap::IntoIter 330 | // struct std::collections::binary_heap::Iter 331 | // struct std::collections::binary_heap::PeekMut 332 | // struct std::collections::btree_map::IntoIter 333 | // struct std::collections::btree_map::Iter 334 | // struct std::collections::btree_map::IterMut 335 | // struct std::collections::btree_map::Keys 336 | // struct std::collections::btree_map::OccupiedEntry 337 | // struct std::collections::btree_map::Range 338 | // struct std::collections::btree_map::RangeMut 339 | // struct std::collections::btree_map::VacantEntry 340 | // struct std::collections::btree_map::Values 341 | // struct std::collections::btree_map::ValuesMut 342 | // struct std::collections::btree_set::Difference 343 | // struct std::collections::btree_set::Intersection 344 | // struct std::collections::btree_set::IntoIter 345 | // struct std::collections::btree_set::Iter 346 | // struct std::collections::btree_set::Range 347 | // struct std::collections::btree_set::SymmetricDifference 348 | // struct std::collections::btree_set::Union 349 | // struct std::collections::hash_map::DefaultHasher 350 | // struct std::collections::hash_map::Drain 351 | // struct std::collections::hash_map::IterMut 352 | // struct std::collections::hash_map::RandomState 353 | // struct std::collections::hash_map::ValuesMut 354 | // struct std::collections::hash_set::Difference 355 | // struct std::collections::hash_set::Intersection 356 | // struct std::collections::hash_set::SymmetricDifference 357 | // struct std::collections::hash_set::Union 358 | // struct std::collections::linked_list::BackPlace 359 | // struct std::collections::linked_list::FrontPlace 360 | // struct std::collections::linked_list::IntoIter 361 | // struct std::collections::linked_list::Iter 362 | // struct std::collections::linked_list::IterMut 363 | // struct std::collections::vec_deque::Drain 364 | // struct std::collections::vec_deque::IntoIter 365 | // struct std::collections::vec_deque::Iter 366 | // struct std::collections::vec_deque::IterMut 367 | // struct std::collections::vec_deque::PlaceBack 368 | // struct std::collections::vec_deque::PlaceFront 369 | // struct std::env::Args 370 | // struct std::env::ArgsOs 371 | // struct std::env::JoinPathsError 372 | // struct std::env::SplitPaths 373 | // struct std::env::Vars 374 | // struct std::env::VarsOs 375 | // struct std::ffi::CStr 376 | // struct std::ffi::CString 377 | // struct std::ffi::FromBytesWithNulError 378 | // struct std::ffi::IntoStringError 379 | // struct std::ffi::NulError 380 | // struct std::ffi::OsStr 381 | // struct std::ffi::OsString 382 | // struct std::fmt::Arguments 383 | // struct std::fmt::Error 384 | // struct std::fs::DirBuilder 385 | // struct std::fs::DirEntry 386 | // struct std::fs::File 387 | // struct std::fs::FileType 388 | // struct std::fs::Metadata 389 | // struct std::fs::OpenOptions 390 | // struct std::fs::Permissions 391 | // struct std::fs::ReadDir 392 | // struct std::hash::BuildHasherDefault 393 | // struct std::hash::SipHasher 394 | // struct std::hash::SipHasher13 395 | // struct std::hash::SipHasher24 396 | // struct std::io::BufReader 397 | // struct std::io::Empty 398 | // struct std::io::Error 399 | // struct std::io::Repeat 400 | // struct std::io::Sink 401 | // struct std::io::Stderr 402 | // struct std::io::StderrLock 403 | // struct std::io::Stdin 404 | // struct std::io::StdinLock 405 | // struct std::io::Stdout 406 | // struct std::io::StdoutLock 407 | // struct std::iter::Chain 408 | // struct std::iter::Cloned 409 | // struct std::iter::Cycle 410 | // struct std::iter::DeprecatedStepBy 411 | // struct std::iter::Empty 412 | // struct std::iter::Enumerate 413 | // struct std::iter::Filter 414 | // struct std::iter::FilterMap 415 | // struct std::iter::FlatMap 416 | // struct std::iter::Fuse 417 | // struct std::iter::Inspect 418 | // struct std::iter::Map 419 | // struct std::iter::Once 420 | // struct std::iter::Peekable 421 | // struct std::iter::Repeat 422 | // struct std::iter::Rev 423 | // struct std::iter::Scan 424 | // struct std::iter::Skip 425 | // struct std::iter::SkipWhile 426 | // struct std::iter::StepBy 427 | // struct std::iter::Take 428 | // struct std::iter::TakeWhile 429 | // struct std::iter::Zip 430 | // struct std::marker::PhantomData 431 | // struct std::mem::Discriminant 432 | // struct std::net::AddrParseError 433 | // struct std::net::Incoming 434 | // struct std::net::Ipv4Addr 435 | // struct std::net::Ipv6Addr 436 | // struct std::net::LookupHost 437 | // struct std::net::SocketAddrV4 438 | // struct std::net::SocketAddrV6 439 | // struct std::net::TcpListener 440 | // struct std::net::TcpStream 441 | // struct std::net::UdpSocket 442 | // struct std::num::ParseFloatError 443 | // struct std::num::ParseIntError 444 | // struct std::num::TryFromIntError 445 | // struct std::num::Wrapping 446 | // struct std::ops::Range 447 | // struct std::ops::RangeFrom 448 | // struct std::ops::RangeFull 449 | // struct std::ops::RangeInclusive 450 | // struct std::ops::RangeTo 451 | // struct std::ops::RangeToInclusive 452 | // struct std::option::IntoIter 453 | // struct std::option::Iter 454 | // struct std::option::IterMut 455 | // struct std::os::unix::net::Incoming 456 | // struct std::os::unix::net::SocketAddr 457 | // struct std::os::unix::net::UnixDatagram 458 | // struct std::os::unix::net::UnixListener 459 | // struct std::os::unix::net::UnixStream 460 | // struct std::panic::Location 461 | // struct std::panic::PanicInfo 462 | // struct std::path::Components 463 | // struct std::path::Display 464 | // struct std::path::Iter 465 | // struct std::path::Path 466 | // struct std::path::PathBuf 467 | // struct std::path::PrefixComponent 468 | // struct std::path::StripPrefixError 469 | // struct std::process::Child 470 | // struct std::process::ChildStderr 471 | // struct std::process::ChildStdin 472 | // struct std::process::ChildStdout 473 | // struct std::process::Command 474 | // struct std::process::ExitStatus 475 | // struct std::process::Output 476 | // struct std::process::Stdio 477 | // struct std::rc::Rc 478 | // struct std::rc::Weak 479 | // struct std::result::IntoIter 480 | // struct std::result::Iter 481 | // struct std::result::IterMut 482 | // struct std::slice::Chunks 483 | // struct std::slice::ChunksMut 484 | // struct std::slice::Iter 485 | // struct std::slice::IterMut 486 | // struct std::slice::RSplit 487 | // struct std::slice::RSplitMut 488 | // struct std::slice::RSplitN 489 | // struct std::slice::RSplitNMut 490 | // struct std::slice::Split 491 | // struct std::slice::SplitMut 492 | // struct std::slice::SplitN 493 | // struct std::slice::SplitNMut 494 | // struct std::slice::Windows 495 | // struct std::str::Bytes 496 | // struct std::str::CharIndices 497 | // struct std::str::Chars 498 | // struct std::str::EncodeUtf16 499 | // struct std::str::Lines 500 | // struct std::str::LinesAny 501 | // struct std::str::MatchIndices 502 | // struct std::str::Matches 503 | // struct std::str::ParseBoolError 504 | // struct std::str::RMatchIndices 505 | // struct std::str::RMatches 506 | // struct std::str::RSplit 507 | // struct std::str::RSplitN 508 | // struct std::str::RSplitTerminator 509 | // struct std::str::Split 510 | // struct std::str::SplitN 511 | // struct std::str::SplitTerminator 512 | // struct std::str::Utf8Error 513 | // struct std::str::pattern::CharPredicateSearcher 514 | // struct std::str::pattern::CharSearcher 515 | // struct std::str::pattern::CharSliceSearcher 516 | // struct std::str::pattern::StrSearcher 517 | // struct std::string::Drain 518 | // struct std::string::FromUtf16Error 519 | // struct std::string::FromUtf8Error 520 | // struct std::string::Splice 521 | // struct std::string::String 522 | // struct std::sync::Arc 523 | // struct std::sync::Barrier 524 | // struct std::sync::BarrierWaitResult 525 | // struct std::sync::Condvar 526 | // struct std::sync::Once 527 | // struct std::sync::OnceState 528 | // struct std::sync::PoisonError 529 | // struct std::sync::WaitTimeoutResult 530 | // struct std::sync::Weak 531 | // struct std::sync::atomic::AtomicBool 532 | // struct std::sync::atomic::AtomicI16 533 | // struct std::sync::atomic::AtomicI32 534 | // struct std::sync::atomic::AtomicI64 535 | // struct std::sync::atomic::AtomicI8 536 | // struct std::sync::atomic::AtomicIsize 537 | // struct std::sync::atomic::AtomicPtr 538 | // struct std::sync::atomic::AtomicU16 539 | // struct std::sync::atomic::AtomicU32 540 | // struct std::sync::atomic::AtomicU64 541 | // struct std::sync::atomic::AtomicU8 542 | // struct std::sync::atomic::AtomicUsize 543 | // struct std::sync::mpsc::Receiver 544 | // struct std::sync::mpsc::RecvError 545 | // struct std::sync::mpsc::Select 546 | // struct std::sync::mpsc::SendError 547 | // struct std::sync::mpsc::Sender 548 | // struct std::sync::mpsc::SyncSender 549 | // struct std::thread::Builder 550 | // struct std::thread::JoinHandle 551 | // struct std::thread::LocalKey 552 | // struct std::thread::Thread 553 | // struct std::thread::ThreadId 554 | // struct std::time::Duration 555 | // struct std::time::Instant 556 | // struct std::time::SystemTime 557 | // struct std::time::SystemTimeError 558 | // struct std::vec::Drain 559 | // struct std::vec::IntoIter 560 | // struct std::vec::PlaceBack 561 | // struct std::vec::Splice 562 | // trait std::any::Any 563 | // trait std::fmt::Debug 564 | // trait std::io::Write 565 | // trait std::marker::Send 566 | // trait std::marker::Sized 567 | // union std::mem::ManuallyDrop 568 | -------------------------------------------------------------------------------- /src/transform.rs: -------------------------------------------------------------------------------- 1 | use super::{Cast, GenericQuery, Term}; 2 | use std::marker::PhantomData; 3 | 4 | /// Work around Rust's lack of higher-rank type polymorphism with a trait that 5 | /// has a generic `fn transform` method. Essentially, we'd really prefer 6 | /// taking arguments of type `F: for FnMut(T) -> T` rather than `F: 7 | /// GenericTransform` but Rust doesn't support them yet (ever?). 8 | pub trait GenericTransform { 9 | /// Call the transform function on any `T`. 10 | fn transform(&mut self, t: T) -> T 11 | where 12 | T: Term; 13 | } 14 | 15 | /// A transformation takes some value `U` and returns a new, transformed version 16 | /// of it. It can be called on values of *any* type `T`, not just on values of 17 | /// type `U`, in which case it is simply the identity function. 18 | /// 19 | /// This essentially lifts a `FnMut(U) -> U` into a `for FnMut(T) -> T`. 20 | #[derive(Debug)] 21 | pub struct Transformation 22 | where 23 | F: FnMut(U) -> U, 24 | { 25 | f: F, 26 | phantom: PhantomData U>, 27 | } 28 | 29 | impl Transformation 30 | where 31 | F: FnMut(U) -> U, 32 | { 33 | /// Construct a new `Transformation` from the given function. 34 | #[inline] 35 | pub fn new(f: F) -> Transformation { 36 | Transformation { 37 | f, 38 | phantom: ::std::marker::PhantomData, 39 | } 40 | } 41 | } 42 | 43 | impl GenericTransform for Transformation 44 | where 45 | F: FnMut(U) -> U, 46 | { 47 | #[inline] 48 | fn transform(&mut self, t: T) -> T { 49 | match Cast::::cast(t) { 50 | Ok(u) => match Cast::::cast((self.f)(u)) { 51 | Ok(t) => t, 52 | Err(_) => unreachable!( 53 | "If T=U, then U=T. Cast isn't pub, so there aren't any \ 54 | future specializations that could wreck this for us." 55 | ), 56 | }, 57 | Err(t) => t, 58 | } 59 | } 60 | } 61 | 62 | /// Recursively perform a transformation in a bottom up manner across a complete 63 | /// data structure. 64 | #[derive(Debug)] 65 | pub struct Everywhere 66 | where 67 | F: GenericTransform, 68 | { 69 | f: F, 70 | } 71 | 72 | impl Everywhere 73 | where 74 | F: GenericTransform, 75 | { 76 | /// Construct a new transformation traversal. 77 | #[inline] 78 | pub fn new(f: F) -> Everywhere { 79 | Everywhere { f } 80 | } 81 | } 82 | 83 | impl GenericTransform for Everywhere 84 | where 85 | F: GenericTransform, 86 | { 87 | #[inline] 88 | fn transform(&mut self, t: T) -> T 89 | where 90 | T: Term, 91 | { 92 | let t = t.map_one_transform(self); 93 | self.f.transform(t) 94 | } 95 | } 96 | 97 | /// Recursively perform a transformation in a bottom up manner across a 98 | /// data structure, ignoring branches where the given query 99 | /// evaluates to false 100 | #[derive(Debug)] 101 | pub struct EverywhereBut 102 | where 103 | F: GenericTransform, 104 | P: GenericQuery, 105 | { 106 | p: P, 107 | f: F, 108 | } 109 | 110 | impl EverywhereBut 111 | where 112 | F: GenericTransform, 113 | P: GenericQuery, 114 | { 115 | /// Construct a new transformation traversal. 116 | #[inline] 117 | pub fn new(p: P, f: F) -> EverywhereBut { 118 | EverywhereBut { p, f } 119 | } 120 | } 121 | 122 | impl GenericTransform for EverywhereBut 123 | where 124 | F: GenericTransform, 125 | P: GenericQuery, 126 | { 127 | #[inline] 128 | fn transform(&mut self, t: T) -> T 129 | where 130 | T: Term, 131 | { 132 | if self.p.query(&t) { 133 | let t = t.map_one_transform(self); 134 | self.f.transform(t) 135 | } else { 136 | t 137 | } 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use super::*; 144 | 145 | #[test] 146 | fn transformation() { 147 | let mut not = Transformation::new(|b: bool| !b); 148 | assert_eq!(not.transform(true), false); 149 | assert_eq!(not.transform("string"), "string"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /tests/company.rs: -------------------------------------------------------------------------------- 1 | extern crate scrapmetal; 2 | 3 | #[macro_use] 4 | extern crate scrapmetal_derive; 5 | 6 | use scrapmetal::*; 7 | use std::cmp; 8 | use std::collections::LinkedList; 9 | 10 | // Some toy types to test and bench with, taken from the paper. 11 | 12 | #[derive(Clone, Debug, PartialEq, Term)] 13 | pub struct Company(pub Vec); 14 | 15 | #[derive(Clone, Debug, PartialEq, Term)] 16 | pub struct Department(pub Name, pub Manager, pub Vec); 17 | 18 | #[derive(Clone, Debug, PartialEq, Term)] 19 | pub enum SubUnit { 20 | Person(Employee), 21 | Department(Box), 22 | Group(LinkedList), 23 | } 24 | 25 | #[derive(Clone, Debug, PartialEq, Term)] 26 | pub struct Employee(pub Person, pub Salary); 27 | 28 | #[derive(Clone, Debug, PartialEq, Term)] 29 | pub struct Person(pub Name, pub Address); 30 | 31 | #[derive(Clone, Debug, Default, PartialEq, PartialOrd, Term)] 32 | pub struct Salary(pub f64); 33 | 34 | pub type Manager = Employee; 35 | pub type Name = &'static str; 36 | pub type Address = &'static str; 37 | 38 | impl Default for Company { 39 | fn default() -> Company { 40 | let ralf = Employee(Person("Ralf", "Amsterdam"), Salary(8000.0)); 41 | let joost = Employee(Person("Joost", "Amsterdam"), Salary(1000.0)); 42 | let marlow = Employee(Person("Marlow", "Cambridge"), Salary(2000.0)); 43 | let blair = Employee(Person("Blair", "London"), Salary(100000.0)); 44 | let jim = Employee(Person("Jim", "Portland"), Salary(3.0)); 45 | let mut group = LinkedList::new(); 46 | group.push_front(Employee(Person("Joe", "Vancouver"), Salary(22.2))); 47 | group.push_front(Employee(Person("Mike", "Toronto"), Salary(33.3))); 48 | Company(vec![ 49 | Department( 50 | "Research", 51 | ralf, 52 | vec![ 53 | SubUnit::Group(group), 54 | SubUnit::Person(joost), 55 | SubUnit::Person(marlow), 56 | SubUnit::Department(Box::new(Department("Funsies", jim, vec![]))), 57 | ], 58 | ), 59 | Department("Strategy", blair, vec![]), 60 | ]) 61 | } 62 | } 63 | 64 | impl cmp::Eq for Salary {} 65 | 66 | impl cmp::Ord for Salary { 67 | fn cmp(&self, rhs: &Salary) -> cmp::Ordering { 68 | assert!(!self.0.is_nan()); 69 | assert!(!rhs.0.is_nan()); 70 | 71 | if self.0 < rhs.0 { 72 | cmp::Ordering::Less 73 | } else if self.0 > rhs.0 { 74 | cmp::Ordering::Greater 75 | } else { 76 | cmp::Ordering::Equal 77 | } 78 | } 79 | } 80 | 81 | // Boilerplate version of `increase` /////////////////////////////////////////// 82 | 83 | pub trait Increase: Sized { 84 | fn increase(self, k: f64) -> Self; 85 | } 86 | 87 | impl Increase for Company { 88 | fn increase(self, k: f64) -> Company { 89 | Company(self.0.into_iter().map(|d| d.increase(k)).collect()) 90 | } 91 | } 92 | 93 | impl Increase for Department { 94 | fn increase(self, k: f64) -> Department { 95 | Department( 96 | self.0, 97 | self.1.increase(k), 98 | self.2.into_iter().map(|s| s.increase(k)).collect(), 99 | ) 100 | } 101 | } 102 | 103 | impl Increase for SubUnit { 104 | fn increase(self, k: f64) -> SubUnit { 105 | match self { 106 | SubUnit::Group(g) => SubUnit::Group(g.into_iter().map(|e| e.increase(k)).collect()), 107 | SubUnit::Person(e) => SubUnit::Person(e.increase(k)), 108 | SubUnit::Department(d) => SubUnit::Department(Box::new(d.increase(k))), 109 | } 110 | } 111 | } 112 | 113 | impl Increase for Employee { 114 | fn increase(self, k: f64) -> Employee { 115 | Employee(self.0, self.1.increase(k)) 116 | } 117 | } 118 | 119 | impl Increase for Salary { 120 | fn increase(self, k: f64) -> Salary { 121 | Salary(self.0 + k) 122 | } 123 | } 124 | 125 | // Boilerplate version of `increase_in_place` ////////////////////////////////// 126 | 127 | pub trait IncreaseInPlace { 128 | fn increase_in_place(&mut self, k: f64); 129 | } 130 | 131 | impl IncreaseInPlace for Company { 132 | fn increase_in_place(&mut self, k: f64) { 133 | self.0.iter_mut().map(|d| d.increase_in_place(k)).count(); 134 | } 135 | } 136 | 137 | impl IncreaseInPlace for Department { 138 | fn increase_in_place(&mut self, k: f64) { 139 | self.1.increase_in_place(k); 140 | self.2.iter_mut().map(|s| s.increase_in_place(k)).count(); 141 | } 142 | } 143 | 144 | impl IncreaseInPlace for SubUnit { 145 | fn increase_in_place(&mut self, k: f64) { 146 | match *self { 147 | SubUnit::Person(ref mut e) => e.increase_in_place(k), 148 | SubUnit::Group(ref mut g) => g.into_iter().for_each(|e| e.increase_in_place(k)), 149 | SubUnit::Department(ref mut d) => d.increase_in_place(k), 150 | } 151 | } 152 | } 153 | 154 | impl IncreaseInPlace for Employee { 155 | fn increase_in_place(&mut self, k: f64) { 156 | self.1.increase_in_place(k); 157 | } 158 | } 159 | 160 | impl IncreaseInPlace for Salary { 161 | fn increase_in_place(&mut self, k: f64) { 162 | self.0 += k; 163 | } 164 | } 165 | 166 | // Boilerplate version of `highest_salary` ///////////////////////////////////// 167 | 168 | pub trait HighestSalary { 169 | fn highest_salary(&self) -> Option; 170 | } 171 | 172 | impl HighestSalary for Company { 173 | fn highest_salary(&self) -> Option { 174 | self.0 175 | .iter() 176 | .map(|d| d.highest_salary()) 177 | .fold(None, cmp::max) 178 | } 179 | } 180 | 181 | impl HighestSalary for Department { 182 | fn highest_salary(&self) -> Option { 183 | let mgr_salary = self.1.highest_salary(); 184 | let units_highest = self.2 185 | .iter() 186 | .map(|u| u.highest_salary()) 187 | .fold(None, cmp::max); 188 | cmp::max(mgr_salary, units_highest) 189 | } 190 | } 191 | 192 | impl HighestSalary for SubUnit { 193 | fn highest_salary(&self) -> Option { 194 | match *self { 195 | SubUnit::Person(ref e) => e.highest_salary(), 196 | SubUnit::Group(ref g) => g.into_iter().map(|e| e.highest_salary()).max().unwrap(), 197 | SubUnit::Department(ref d) => d.highest_salary(), 198 | } 199 | } 200 | } 201 | 202 | impl HighestSalary for Employee { 203 | fn highest_salary(&self) -> Option { 204 | Some(self.1.clone()) 205 | } 206 | } 207 | 208 | // Tests /////////////////////////////////////////////////////////////////////// 209 | 210 | #[test] 211 | fn increase_with_boilerplate() { 212 | let company = Company::default(); 213 | let company = company.increase(1.0); 214 | let mut group = LinkedList::new(); 215 | group.push_front(Employee(Person("Joe", "Vancouver"), Salary(23.2))); 216 | group.push_front(Employee(Person("Mike", "Toronto"), Salary(34.3))); 217 | assert_eq!( 218 | company, 219 | Company(vec![ 220 | Department( 221 | "Research", 222 | Employee(Person("Ralf", "Amsterdam"), Salary(8001.0)), 223 | vec![ 224 | SubUnit::Group(group), 225 | SubUnit::Person(Employee(Person("Joost", "Amsterdam"), Salary(1001.0))), 226 | SubUnit::Person(Employee(Person("Marlow", "Cambridge"), Salary(2001.0))), 227 | SubUnit::Department(Box::new(Department( 228 | "Funsies", 229 | Employee(Person("Jim", "Portland"), Salary(4.0)), 230 | vec![], 231 | ))), 232 | ], 233 | ), 234 | Department( 235 | "Strategy", 236 | Employee(Person("Blair", "London"), Salary(100001.0)), 237 | vec![], 238 | ), 239 | ]) 240 | ); 241 | } 242 | 243 | #[test] 244 | fn increase_scrapping_boilerplate() { 245 | let transformation = Transformation::new(|s: Salary| Salary(s.0 + 1.0)); 246 | let mut increase = Everywhere::new(transformation); 247 | let mut group = LinkedList::new(); 248 | group.push_front(Employee(Person("Joe", "Vancouver"), Salary(23.2))); 249 | group.push_front(Employee(Person("Mike", "Toronto"), Salary(34.3))); 250 | let company = Company::default(); 251 | let company = increase.transform(company); 252 | assert_eq!( 253 | company, 254 | Company(vec![ 255 | Department( 256 | "Research", 257 | Employee(Person("Ralf", "Amsterdam"), Salary(8001.0)), 258 | vec![ 259 | SubUnit::Group(group), 260 | SubUnit::Person(Employee(Person("Joost", "Amsterdam"), Salary(1001.0))), 261 | SubUnit::Person(Employee(Person("Marlow", "Cambridge"), Salary(2001.0))), 262 | SubUnit::Department(Box::new(Department( 263 | "Funsies", 264 | Employee(Person("Jim", "Portland"), Salary(4.0)), 265 | vec![], 266 | ))), 267 | ], 268 | ), 269 | Department( 270 | "Strategy", 271 | Employee(Person("Blair", "London"), Salary(100001.0)), 272 | vec![], 273 | ), 274 | ]) 275 | ); 276 | } 277 | 278 | #[test] 279 | fn increase_in_place_with_boilerplate() { 280 | let mut company = Company::default(); 281 | let mut group = LinkedList::new(); 282 | group.push_front(Employee(Person("Joe", "Vancouver"), Salary(23.2))); 283 | group.push_front(Employee(Person("Mike", "Toronto"), Salary(34.3))); 284 | company.increase_in_place(1.0); 285 | assert_eq!( 286 | company, 287 | Company(vec![ 288 | Department( 289 | "Research", 290 | Employee(Person("Ralf", "Amsterdam"), Salary(8001.0)), 291 | vec![ 292 | SubUnit::Group(group), 293 | SubUnit::Person(Employee(Person("Joost", "Amsterdam"), Salary(1001.0))), 294 | SubUnit::Person(Employee(Person("Marlow", "Cambridge"), Salary(2001.0))), 295 | SubUnit::Department(Box::new(Department( 296 | "Funsies", 297 | Employee(Person("Jim", "Portland"), Salary(4.0)), 298 | vec![], 299 | ))), 300 | ], 301 | ), 302 | Department( 303 | "Strategy", 304 | Employee(Person("Blair", "London"), Salary(100001.0)), 305 | vec![], 306 | ), 307 | ]) 308 | ); 309 | } 310 | 311 | #[test] 312 | fn increase_in_place_scrapping_boilerplate() { 313 | let mutation = Mutation::new(|s: &mut Salary| s.0 += 1.0); 314 | let mut increase_in_place = MutateEverything::new(mutation); 315 | 316 | let mut company = Company::default(); 317 | let mut group = LinkedList::new(); 318 | group.push_front(Employee(Person("Joe", "Vancouver"), Salary(23.2))); 319 | group.push_front(Employee(Person("Mike", "Toronto"), Salary(34.3))); 320 | increase_in_place.mutate(&mut company); 321 | assert_eq!( 322 | company, 323 | Company(vec![ 324 | Department( 325 | "Research", 326 | Employee(Person("Ralf", "Amsterdam"), Salary(8001.0)), 327 | vec![ 328 | SubUnit::Group(group), 329 | SubUnit::Person(Employee(Person("Joost", "Amsterdam"), Salary(1001.0))), 330 | SubUnit::Person(Employee(Person("Marlow", "Cambridge"), Salary(2001.0))), 331 | SubUnit::Department(Box::new(Department( 332 | "Funsies", 333 | Employee(Person("Jim", "Portland"), Salary(4.0)), 334 | vec![], 335 | ))), 336 | ], 337 | ), 338 | Department( 339 | "Strategy", 340 | Employee(Person("Blair", "London"), Salary(100001.0)), 341 | vec![], 342 | ), 343 | ]) 344 | ); 345 | } 346 | 347 | #[test] 348 | fn query_highest_salary_with_boilerplate() { 349 | let company = Company::default(); 350 | assert_eq!(company.highest_salary(), Some(Salary(100000.0))); 351 | } 352 | 353 | #[test] 354 | fn query_highest_salary_scrapping_boilerplate() { 355 | let query = Query::new(|e: &Employee| Some(e.1.clone())); 356 | let mut highest_salary = Everything::new(query, cmp::max); 357 | 358 | let company = Company::default(); 359 | assert_eq!(highest_salary.query(&company), Some(Salary(100000.0))); 360 | } 361 | -------------------------------------------------------------------------------- /tests/derive_edge_cases.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![deny(unused_variables)] 3 | 4 | extern crate scrapmetal; 5 | 6 | #[macro_use] 7 | extern crate scrapmetal_derive; 8 | 9 | #[derive(Term)] 10 | struct UnitStruct; 11 | 12 | #[derive(Term)] 13 | struct EmptyTupleStruct(); 14 | 15 | #[derive(Term)] 16 | struct EmptyStruct {} 17 | 18 | #[derive(Term)] 19 | enum EmptyEnum {} 20 | --------------------------------------------------------------------------------