Generic conversion of real constants into ComplexFloat
In generic complex code it is common to have to convert real values into T: ComplexFloat. For this, I find the following trait very handy:
pub trait IntoComplex<T: ComplexFloat> {
fn into_complex(self) -> T;
}
impl<T> IntoComplex<T> for f32
where
T: ComplexFloat,
f32: Into<T::Real>,
T::Real: Into<T>,
{
fn into_complex(self) -> T {
self.into().into()
}
}
It allows to write
fn some_calculation<T>(x: T) -> T
where
T: ComplexFloat + NumAssign,
f32: IntoComplex<T>,
{
let a = 2.0.into_complex();
let b = 3.0.into_complex();
let mut sum: T = 0.0.into_complex();
sum += x * a + b;
sum *= b;
sum
}
instead of
fn some_calculation<T>(x: T) -> T
where
T: ComplexFloat + NumAssign,
f32: Into<T::Real>,
T::Real: Into<T>,
{
let a = 2.0.into().into();
let b = 3.0.into().into();
let mut sum: T = 0.0.into().into();
sum += x * a + b;
sum *= b;
sum
}
I am aware of num_traits::cast but those casts can fail and they can involve information loss.
In contrast, the IntoComplex conversion always works and preserves all information. This is also why using it involves less boilerplate.
How about adding something along these lines to this crate?
I just realized that the impl can be made more generic
impl<D, S> IntoComplex<D> for S
where
D: ComplexFloat,
S: Into<D::Real>,
D::Real: Into<D>,
{
fn into_complex(self) -> D {
self.into().into()
}
}
Now it also works for, say, i16 as source type.
Or perhaps a blanket implementation like impl<S, D: From<S> + Clone + Num> From<S> for Complex<D> would make sense? This might be too generic, though.
It can't be that generic because it conflicts with the broad impl<T> From<T> for T -- the compiler sees that it is possible to have S = Complex<D>, no matter that this is unlikely in practice.
Overall, I think it's not a good idea to have multiple implicit conversions, because the compiler will often give up on type inference in that situation. But for your example, I think a simple ComplexFloat::from_real(Self::Real) -> Self method would work pretty well? Then you can write things like:
fn some_calculation<T>(x: T) -> T
where
T: ComplexFloat + NumAssign,
i8: Into<T::Real>,
{
let a = T::from_real(2.into());
let b = T::from_real(3.into());
let mut sum: T = T::from_real(0.into()); // or T::zero()
sum += x * a + b;
sum *= b;
sum
}
It can't be that generic because it conflicts with the broad
impl<T> From<T> for T-- the compiler sees that it is possible to haveS = Complex<D>, no matter that this is unlikely in practice.
I see – interesting!
Overall, I think it's not a good idea to have multiple implicit conversions, because the compiler will often give up on type inference in that situation.
Yes, and the double .into() is also too complicated for what it does.
But for your example, I think a simple
ComplexFloat::from_real(Self::Real) -> Selfmethod would work pretty well? Then you can write things like:fn some_calculation<T>(x: T) -> T where T: ComplexFloat + NumAssign, i8: Into<T::Real>, { let a = T::from_real(2.into()); let b = T::from_real(3.into()); let mut sum: T = T::from_real(0.into()); // or T::zero() sum += x * a + b; sum *= b; sum }
This is good. I think that it would be a very useful addition to num-complex!
The IntoComplex trait that I proposed above looks cleaner in use (it’s only a suffix to a constant), but it’s perhaps a case of trait overuse.
For now, what do you suggest as a stop-gap measure in real code?
I tried to implement your suggestion as a trait and came up with
pub trait FromReal: ComplexFloat {
fn from_real(x: Self::Real) -> Self;
}
impl<T> FromReal for Complex<T>
where
Complex<T>: ComplexFloat,
i8: Into<T>,
Self::Real: Into<T>,
{
fn from_real(x: Self::Real) -> Self {
Self::new(x.into(), 0.into())
}
}
But (compared to calling .into().into() or to adding from_real to ComplexFloat) this has the disadvantage of polluting the API of my library with the FromReal trait.