无需包装器即可轻松实现此 "Magma" 特性
Implement this "Magma" trait easily without a wrapper
我的目标是创建一个函数,将 Couple<Couple<Couple<T>>>
类型的元素转换为 T
类型的元素
大多数时候,接缝真的很容易做到。事实上,如果你有一个运算符或函数将 2 T
组合成 1 T
,你可以递归地做。
If I want to fusion ((1 2) (3 4)) into a single number,
I can use the "+" operator recursively:
(1 + 2) + (3 + 4) = 10
所以我创建了一个名为 Magma 的特征(具有组合操作的类型),以及一个递归融合具有该特征的对的函数。
// simple couple type
struct Couple<T>(T, T);
trait Magma {
// a magma is just a type with a function (S, S) -> S (for ex. the "+" operation)
fn combine(a: Self, b: Self) -> Self;
}
// fn fusion<T>(Couple<Couple<Couple<T>>>) -> T where T: Magma {}
但问题是,要将此 fusion
函数与 Couple<Couple<bool>>
类型一起使用,我必须为 bool
实现 Magma
impl Magma for bool {
fn combine(a: bool, b: bool) -> bool {
a && b
}
}
但是您可以通过多种方式将 2 个布尔值组合成 1 个:“或”、“与”、“异或”...
而且我无法为这些功能中的每一个实现 Magma
!
所以我目前的方法是使用 bools 的包装器:
struct OrWrapper(bool);
impl Magma for OrWrapper {
fn combine(a: Self, b:Self) -> Self {
OrWrapper(a.0 || b.0)
}
}
struct AndWrapper(bool);
impl Magma for AndWrapper{
fn combine(a: Self, b:Self) -> Self {
AndWrapper(a.0 && b.0)
}
}
但是写起来真的很重复很痛苦,想知道有没有更优雅的解决方案
有什么想法吗?
包装器类型几乎肯定是可行的方法。岩浆被定义为一对:一个集合(即类型)和一个运算符,你必须以某种方式捕获它们。
您可以更改您的 Magma
特征以使其更易于使用,因此它可以接受内部类型并在内部转换为包装器:
trait Magma: Sized {
fn combine(self, b: impl Into<Self>) -> Self;
}
如果您担心重复定义这些包装器类型,那么您可以使用宏来生成它们:
macro_rules! magma {
($($ty: ty as $wrapper: ident => $op: path),* $(,)?) => {
$(
// a new wrapper type
#[derive(Copy, Clone, Debug)]
pub struct $wrapper($ty);
impl Magma for $wrapper {
fn combine(self, b: impl Into<Self>) -> $wrapper {
$wrapper($op(&self.0, &b.into().0))
}
}
)*
}
}
magma! {
bool as BoolAnd => std::ops::BitAnd::bitand,
bool as BoolOr => std::ops::BitOr::bitor,
u32 as U32Add => std::ops::Add::add,
u32 as U32Mul => std::ops::Mul::mul,
}
为了进一步方便,您可能还想为这些类型实现 From
转换、Deref
以及可能的其他特征,例如 Display
:
macro_rules! magma {
($($ty: ty as $wrapper: ident => $op: path),* $(,)?) => {
$(
// a new wrapper type
#[derive(Copy, Clone, Debug)]
pub struct $wrapper($ty);
// conversion from the raw type to the wrapper
impl From<$ty> for $wrapper {
fn from(x: $ty) -> $wrapper { $wrapper(x) }
}
// conversion from the wrapper type to the inner type
impl From<$wrapper> for $ty {
fn from(w: $wrapper) -> $ty { w.0 }
}
// Deref to the inner type for convenience
impl std::ops::Deref for $wrapper {
type Target = $ty;
fn deref(&self) -> &$ty { &self.0 }
}
// Delegate to the inner type for display
impl std::fmt::Display for $wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Magma for $wrapper {
fn combine(self, b: impl Into<Self>) -> $wrapper {
$wrapper($op(&self.0, &b.into().0))
}
}
)*
}
}
用法:
magma! {
bool as BoolAnd => std::ops::BitAnd::bitand,
bool as BoolOr => std::ops::BitOr::bitor,
u32 as U32Add => std::ops::Add::add,
u32 as U32Mul => std::ops::Mul::mul,
}
fn main() {
println!("{}", BoolOr(true).combine(false)); // true
}
Peter Hall 的另一种方法是创建一个结合了值和操作的类型 - 并为操作使用零大小的类型。这意味着减少了对宏的需求。
使用像 Peters 这样的特征,这可能看起来像:
trait Magma: Sized {
fn combine(self, b: impl Into<Self>) -> Self;
}
trait ExternalMagma<T> {
fn combine(a: T, b: T) -> T;
}
#[derive(Copy, Clone, Debug)]
struct ValueWithOp<T,F: ExternalMagma<T>>(T, F);
impl<T, F> Magma for ValueWithOp<T, F>
where
F: ExternalMagma<T>
{
fn combine(self, b: impl Into<Self>) -> Self {
let f = self.1;
ValueWithOp(F::combine(self.0, b.into().0), f)
}
}
impl<T,F> From<T> for ValueWithOp<T,F>
where F: Default + ExternalMagma<T>
{
fn from(v:T) -> ValueWithOp<T,F> {
ValueWithOp(v, F::default())
}
}
impl<T,F> ValueWithOp<T,F>
where F: ExternalMagma<T> {
fn unwrap(self) -> T {
self.0
}
}
#[derive(Copy, Clone, Debug, Default)]
struct BoolOrOp;
impl ExternalMagma<bool> for BoolOrOp {
fn combine(a: bool, b: bool) -> bool { a || b }
}
#[derive(Copy, Clone, Debug, Default)]
struct MulOp;
impl ExternalMagma<f32> for MulOp {
fn combine(a: f32, b: f32) -> f32 { a * b }
}
impl ExternalMagma<i32> for MulOp {
fn combine(a: i32, b: i32) -> i32 { a * b }
}
fn main() {
println!("{}", ValueWithOp::<bool, BoolOrOp>::from(true).combine(false).unwrap() ); // true
println!("{}", ValueWithOp::<i32, MulOp>::from(2).combine(3).unwrap() ); // 6
}
您可以在playground上看到这个:
我的目标是创建一个函数,将 Couple<Couple<Couple<T>>>
类型的元素转换为 T
大多数时候,接缝真的很容易做到。事实上,如果你有一个运算符或函数将 2 T
组合成 1 T
,你可以递归地做。
If I want to fusion ((1 2) (3 4)) into a single number,
I can use the "+" operator recursively:
(1 + 2) + (3 + 4) = 10
所以我创建了一个名为 Magma 的特征(具有组合操作的类型),以及一个递归融合具有该特征的对的函数。
// simple couple type
struct Couple<T>(T, T);
trait Magma {
// a magma is just a type with a function (S, S) -> S (for ex. the "+" operation)
fn combine(a: Self, b: Self) -> Self;
}
// fn fusion<T>(Couple<Couple<Couple<T>>>) -> T where T: Magma {}
但问题是,要将此 fusion
函数与 Couple<Couple<bool>>
类型一起使用,我必须为 bool
Magma
impl Magma for bool {
fn combine(a: bool, b: bool) -> bool {
a && b
}
}
但是您可以通过多种方式将 2 个布尔值组合成 1 个:“或”、“与”、“异或”...
而且我无法为这些功能中的每一个实现 Magma
!
所以我目前的方法是使用 bools 的包装器:
struct OrWrapper(bool);
impl Magma for OrWrapper {
fn combine(a: Self, b:Self) -> Self {
OrWrapper(a.0 || b.0)
}
}
struct AndWrapper(bool);
impl Magma for AndWrapper{
fn combine(a: Self, b:Self) -> Self {
AndWrapper(a.0 && b.0)
}
}
但是写起来真的很重复很痛苦,想知道有没有更优雅的解决方案
有什么想法吗?
包装器类型几乎肯定是可行的方法。岩浆被定义为一对:一个集合(即类型)和一个运算符,你必须以某种方式捕获它们。
您可以更改您的 Magma
特征以使其更易于使用,因此它可以接受内部类型并在内部转换为包装器:
trait Magma: Sized {
fn combine(self, b: impl Into<Self>) -> Self;
}
如果您担心重复定义这些包装器类型,那么您可以使用宏来生成它们:
macro_rules! magma {
($($ty: ty as $wrapper: ident => $op: path),* $(,)?) => {
$(
// a new wrapper type
#[derive(Copy, Clone, Debug)]
pub struct $wrapper($ty);
impl Magma for $wrapper {
fn combine(self, b: impl Into<Self>) -> $wrapper {
$wrapper($op(&self.0, &b.into().0))
}
}
)*
}
}
magma! {
bool as BoolAnd => std::ops::BitAnd::bitand,
bool as BoolOr => std::ops::BitOr::bitor,
u32 as U32Add => std::ops::Add::add,
u32 as U32Mul => std::ops::Mul::mul,
}
为了进一步方便,您可能还想为这些类型实现 From
转换、Deref
以及可能的其他特征,例如 Display
:
macro_rules! magma {
($($ty: ty as $wrapper: ident => $op: path),* $(,)?) => {
$(
// a new wrapper type
#[derive(Copy, Clone, Debug)]
pub struct $wrapper($ty);
// conversion from the raw type to the wrapper
impl From<$ty> for $wrapper {
fn from(x: $ty) -> $wrapper { $wrapper(x) }
}
// conversion from the wrapper type to the inner type
impl From<$wrapper> for $ty {
fn from(w: $wrapper) -> $ty { w.0 }
}
// Deref to the inner type for convenience
impl std::ops::Deref for $wrapper {
type Target = $ty;
fn deref(&self) -> &$ty { &self.0 }
}
// Delegate to the inner type for display
impl std::fmt::Display for $wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl Magma for $wrapper {
fn combine(self, b: impl Into<Self>) -> $wrapper {
$wrapper($op(&self.0, &b.into().0))
}
}
)*
}
}
用法:
magma! {
bool as BoolAnd => std::ops::BitAnd::bitand,
bool as BoolOr => std::ops::BitOr::bitor,
u32 as U32Add => std::ops::Add::add,
u32 as U32Mul => std::ops::Mul::mul,
}
fn main() {
println!("{}", BoolOr(true).combine(false)); // true
}
Peter Hall 的另一种方法是创建一个结合了值和操作的类型 - 并为操作使用零大小的类型。这意味着减少了对宏的需求。
使用像 Peters 这样的特征,这可能看起来像:
trait Magma: Sized {
fn combine(self, b: impl Into<Self>) -> Self;
}
trait ExternalMagma<T> {
fn combine(a: T, b: T) -> T;
}
#[derive(Copy, Clone, Debug)]
struct ValueWithOp<T,F: ExternalMagma<T>>(T, F);
impl<T, F> Magma for ValueWithOp<T, F>
where
F: ExternalMagma<T>
{
fn combine(self, b: impl Into<Self>) -> Self {
let f = self.1;
ValueWithOp(F::combine(self.0, b.into().0), f)
}
}
impl<T,F> From<T> for ValueWithOp<T,F>
where F: Default + ExternalMagma<T>
{
fn from(v:T) -> ValueWithOp<T,F> {
ValueWithOp(v, F::default())
}
}
impl<T,F> ValueWithOp<T,F>
where F: ExternalMagma<T> {
fn unwrap(self) -> T {
self.0
}
}
#[derive(Copy, Clone, Debug, Default)]
struct BoolOrOp;
impl ExternalMagma<bool> for BoolOrOp {
fn combine(a: bool, b: bool) -> bool { a || b }
}
#[derive(Copy, Clone, Debug, Default)]
struct MulOp;
impl ExternalMagma<f32> for MulOp {
fn combine(a: f32, b: f32) -> f32 { a * b }
}
impl ExternalMagma<i32> for MulOp {
fn combine(a: i32, b: i32) -> i32 { a * b }
}
fn main() {
println!("{}", ValueWithOp::<bool, BoolOrOp>::from(true).combine(false).unwrap() ); // true
println!("{}", ValueWithOp::<i32, MulOp>::from(2).combine(3).unwrap() ); // 6
}
您可以在playground上看到这个: