#boilerplate #pub #transformation #mutation #idea #scrapmetal #scrap

scrapmetal-derive

为scrapmetal提供自定义派生支持

1个不稳定版本

使用旧的Rust 2015

0.1.0 2017年8月3日

#24#pub


scrapmetal 中使用

Apache-2.0/MIT

17KB
421

scrapmetal:删除Rust样板代码

Build Status scrapmetal on crates.io scrapmetal on docs.rs

为Rust提供无样板代码的泛型转换、查询和突变。

将Lämmel和Peyton Jones在"Scrap Your Boilerplate: A Practical Design Pattern for Generic Programming" by Lämmel and Peyton Jones一文中的一些想法和代码移植到Rust。

⚠ 依赖于特化的夜间Rust功能。 ⚠


假设我们正在开发一些模拟公司、部门、子部门、员工和工资的软件。我们可能会有一些类似这样的类型定义

pub struct Company(pub Vec<Department>);

pub struct Department(pub Name, pub Manager, pub Vec<SubUnit>);

pub enum SubUnit {
    Person(Employee),
    Department(Box<Department>),
}

pub struct Employee(pub Person, pub Salary);

pub struct Person(pub Name, pub Address);

pub struct Salary(pub f64);

pub type Manager = Employee;
pub type Name = &'static str;
pub type Address = &'static str;

我们的一家公司最近遇到了士气问题,我们想将其转换成一个新的公司,让每个员工每周一至周五早上都充满激情。但我们无法真正改变工作的本质,所以我们认为我们可以给整个公司加10%的工资,这样就可以算是足够接近了。这需要编写许多具有类似以下类型签名的函数 fn(self, k: f64) -> Self,对于组成Company的每个类型,因为我们已经识别出这种模式,所以我们应该是好的Rustaceans,并使用特质来形式化它

pub trait Increase: Sized {
    fn increase(self, k: f64) -> Self;
}

提高员工工资的公司是通过提高其各部门员工的工资来实现的

impl Increase for Company {
    fn increase(self, k: f64) -> Company {
        Company(
            self.0
                .into_iter()
                .map(|d| d.increase(k))
                .collect()
        )
    }
}

提高员工工资的部门是通过提高其经理的工资和其子单位的每个员工的工资来实现的

impl Increase for Department {
    fn increase(self, k: f64) -> Department {
        Department(
            self.0,
            self.1.increase(k),
            self.2
                .into_iter()
                .map(|s| s.increase(k))
                .collect(),
        )
    }
}

子单位要么是一个单独的员工,要么是一个子部门,所以要么提高员工的工资,要么分别提高子部门中所有人的工资

impl Increase for SubUnit {
    fn increase(self, k: f64) -> SubUnit {
        match self {
            SubUnit::Person(e) => {
                SubUnit::Person(e.increase(k))
            }
            SubUnit::Department(d) => {
                SubUnit::Department(Box::new(d.increase(k)))
            }
        }
    }
}

工资提高的员工是工资提高的同一个员工

impl Increase for Employee {
    fn increase(self, k: f64) -> Employee {
        Employee(self.0, self.1.increase(k))
    }
}

最后,一个孤立的工资可以增加

impl Increase for Salary {
    fn increase(self, k: f64) -> Salary {
        Salary(self.0 * (1.0 + k))
    }
}

相当直接。

但是与此同时,这确实有很多模板代码。唯一与实际增加工资有关的有意思的部分是implIncrease for Salary。其余的代码仅仅是数据结构的遍历。如果我们编写一个重命名公司中所有员工的函数,大部分代码会保持不变。当然,一定有方法可以提取所有这些模板代码,这样我们就不必每次都手动编写它们了?

欢迎来到scrapmetal

// Imports
#[macro_use]
extern crate scrapmetal_derive;
extern crate scrapmetal;
use scrapmetal::{Everywhere, Transformation};

// Add derive(Term) to type definitions
#[derive(Term)]
pub struct Company(pub Vec<Department>);
// Etc...

// Define the `increase` transformation
let increase = |s: Salary| Salary(s.0 * 1.1);
let mut increase = Everywhere::new(Transformation::new(increase));

// Use the `increase` transformation
let new_company = increase.transform(old_company);

无需更多!

依赖项

~1.5MB
~41K SLoC