【问题标题】:Handling f64 or Complex64 return types. Generics? Either?处理 f64 或 Complex64 返回类型。泛型?任何一个?
【发布时间】:2017-05-29 02:56:49
【问题描述】:

我有一个功能正常的 Rust 程序,它使用真正的双精度 (f64) 作为基础类型,并希望扩展系统以便它也可以处理复杂值 (num::complex::Complex64)。

一个(缩减示例)函数采用一些配置结构config,并根据该输入在索引idx 处生成一个潜在值:

fn potential(config: &Config, idx: &Index3) -> Result<f64, Error> {
    let num = &config.grid.size;
    match config.potential {
        PotentialType::NoPotential => Ok(0.0),
        PotentialType::Cube => {
            if (idx.x > num.x / 4 && idx.x <= 3 * num.x / 4) &&
               (idx.y > num.y / 4 && idx.y <= 3 * num.y / 4) &&
               (idx.z > num.z / 4 && idx.z <= 3 * num.z / 4) {
                Ok(-10.0)
            } else {
                Ok(0.0)
            }
        }
        PotentialType::Coulomb => {
            let r = config.grid.dn * (calculate_r2(idx, &config.grid)).sqrt();
            if r < config.grid.dn {
                Ok(-1. / config.grid.dn)
            } else {
                Ok(-1. / r)
            }
        }
    }
}

我现在希望添加一个 ComplexCoulomb 匹配,它返回一个 Complex64 值:

PotentialType::ComplexCoulomb => {
    let r = config.grid.dn * (calculate_r2(idx, &config.grid)).sqrt();
    if r < config.grid.dn {
        Ok(Complex64::new(-1. / config.grid.dn, 1.))
    } else {
        Ok(Complex64::new(-1. / r, 1.))
    }
}

这个函数是我程序中的一个早期入口点,它填充了一个ndarray::Array3;目前我正在处理许多ndarray::Array3&lt;f64&gt; 类型的变量 - 所以我需要概括整个程序,而不仅仅是这个函数。

如何根据config 的输入扩展此程序以使用这两种类型? 该结构来自解析磁盘上的配置文件,并将匹配多个PotentialType::Complex* 值。

我知道两种可能的选择,但不确定是否符合我的标准。

  1. 使用类似于Either 的东西并返回Left 用于实数,Right 用于复数;然后使用额外的逻辑在其他函数中分别处理这些值。
  2. 使用泛型类型。这不是我以前做过太多的事情,generalisation over many types 似乎是对我当前代码库的相当大的复杂更改。有没有办法降低这里的复杂性?

如果您有任何其他建议,我很乐意听取他们的意见!

【问题讨论】:

  • 这个问题的“建议”性质很棘手(在我看来,它触及了“太宽泛”的界限)。特别是,您列出了两个选项,但没有显示将它们转换为代码的尝试。即使我们想要,我们也无法展示“如何降低复杂性”。显示更多代码可以提高问题的质量。无论如何,我们已经可以摆出一些事实:即使函数返回 Result&lt;T, Error&gt;(其中 T 可能是 f64Complex64,...),我们也不能在其中包含运行时字段config 选择 T。在最坏的情况下,您可能需要 2 个函数实例。
  • 我们不能在config 中拥有运行时字段,选择T ...您可能需要2 个函数实例

标签: rust generic-programming either


【解决方案1】:

可能会有很多代码更改,但使用泛型参数可能是最灵活的方法,并且不会影响性能。传递enum 的性能会降低,部分原因是枚举会更大(较大变体的大小加上区分它们的标签),部分原因是必须经常检查枚举变体。

可能会变得很麻烦的一件事是可能会限制您的类型参数的特征列表很长。这可以在impl 级别上完成,而不是在每个函数上完成,以节省重复。目前还没有一种方法可以为一组特征设置别名,这会使这更符合人体工程学,但有一个 RFC 批准用于此。

我发了一个很similar change in the Euclid library。那是一年多以前的事了,从那时起,Rust 和那个库都发生了很大的变化,但是快速浏览一下那个提交应该仍然能让你了解必要的更改量。

这是同一(重命名)实现的current state

impl <T, Src, Dst> TypedTransform3D<T, Src, Dst>
where T: Copy + Clone +
         Add<T, Output=T> +
         Sub<T, Output=T> +
         Mul<T, Output=T> +
         Div<T, Output=T> +
         Neg<Output=T> +
         ApproxEq<T> +
         PartialOrd +
         Trig +
         One + Zero {

  // methods of TypedTransform3D defined here...

}

其中一些特征(TrigOneZero)实际上是 defined inside the crate,因为它们不在标准库中。

【讨论】:

  • 谢谢彼得。我将查看您的代码并尝试以类似的方式获得我的 MWE。一旦我验证了某些内容,就会将您的答案标记为正确。
猜你喜欢
  • 2021-12-25
  • 2015-08-18
  • 1970-01-01
  • 2014-01-15
  • 2016-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多