Обработка возвращаемых типов f64 или Complex64. Дженерики? Либо?

У меня есть работающая программа на 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<f64>, поэтому мне нужно обобщить всю программу, а не только эту функцию.

Как я могу расширить эту программу, чтобы использовать оба типа на основе входных данных от config? Эта структура получена в результате синтаксического анализа файла конфигурации на диске и будет соответствовать ряду значений PotentialType::Complex*.

Я знаю о двух возможных вариантах, но не уверен, соответствует ли один из них моим критериям.

  1. Используйте что-то похожее на Either и возвращайте Left для реального и Right для сложного; затем используйте дополнительную логику для отдельной обработки значений в других функциях.
  2. Используйте универсальные типы. Это не то, чем я занимался раньше, и обобщение по многим типам кажется довольно сложным изменением моей текущей кодовой базы. Есть ли способ уменьшить сложность здесь?

Если у вас есть другие предложения, я хотел бы услышать их!


person Geodesic    schedule 28.05.2017    source источник
comment
Этот вопрос сложен из-за его характера предложений (на мой взгляд, он выходит за слишком широкую границу). В частности, вы указали два варианта, не показывая попытки превратить их в код. Даже если бы мы захотели, мы не смогли бы показать, как уменьшить сложность. Отображение большего количества кода может улучшить качество вопроса. Несмотря на это, мы уже можем положить некоторые факты на стол: даже если функция возвращает Result<T, Error> (где T может быть f64, Complex64, ...), у нас не может быть поля времени выполнения в config, выбирающего T. В худшем случае вам может понадобиться 2 экземпляра функции.   -  person E_net4 the curator    schedule 28.05.2017
comment
у нас не может быть поля времени выполнения в config, выбрав T ... вам может понадобиться 2 экземпляра функции ‹- Это действительно может быть мой ответ на самом деле. Изучу это и скоро отредактирую свой вопрос, если другие варианты все еще подходят. Спасибо!   -  person Geodesic    schedule 28.05.2017


Ответы (1)


Может быть много изменений в коде, но использование универсальных параметров, вероятно, является наиболее гибким подходом, и это не повлияет на производительность. Обход enum будет менее производительным, отчасти потому, что перечисление будет больше (размер большего варианта плюс тег для их различения), а отчасти потому, что вариант перечисления придется часто проверять.

Одна вещь, которая может стать громоздкой, — это потенциально длинный список признаков, которые ограничивают ваш параметр типа. Это можно сделать на уровне impl, а не на каждой функции, чтобы избежать повторения. В настоящее время нет способа связать набор признаков, что сделало бы его более эргономичным, но есть RFC одобрен для этого.

Я сделал очень подобное изменение в библиотеке Euclid. Это было больше года назад, с тех пор так много изменилось как в Rust, так и в этой библиотеке, но беглый просмотр этого коммита все равно должен дать вам представление о количестве необходимых изменений.

Это текущее состояние того же (переименованная) реализация:

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...

}

Некоторые из этих трейтов (Trig, One, Zero) на самом деле определены внутри crate, так как их нет в стандартной библиотеке.

person Peter Hall    schedule 28.05.2017
comment
Спасибо, Питер. Я посмотрю на ваш код и попытаюсь получить свой MWE аналогичным образом. Отмечу ваш ответ как правильный, как только я что-то проверю. - person Geodesic; 28.05.2017