-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Broadcasting Proposal #42
base: main
Are you sure you want to change the base?
Conversation
geopolars/src/geoseries.rs
Outdated
@@ -30,8 +30,52 @@ pub enum TransformOrigin { | |||
Point(Point), | |||
} | |||
|
|||
pub enum BroadcastableParameter<'a, T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still wrapping my head around lifetimes, so I might have a couple dumb questions.
Here we need a lifetime on the enum because the enum takes a reference to the series right? And otherwise the pointer from the enum to the series could outlive the memory contained by the series?
If a function takes a &Series
as an input parameter directly, would it still need a lifetime? Or no because the series wouldn't be stored inside an object like an enum?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still pretty new to me to but yeah I believe that's the case. When passed a &Series object directly, I think the compiler is able to infer the lifetime for the parameter given the calling context and the function that it is passed to. You might still get a lifetime warning if you do something in that function, like assign the pointer to a property of another struct that you return for instance, but most of the time the compiler is able to infer the lifetime
geopolars/src/geoseries.rs
Outdated
} | ||
} | ||
|
||
impl<'a> From<u64> for BroadcastableParameter<'a, u64> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess in the future if we had lots of different types of scalar parameters we could make a macro here, but for this many it's definitely clearer without a macro
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah for sure. I tried to implement this using trait bounds, for example with the num crates ToPrimative (https://docs.rs/num/latest/num/trait.ToPrimitive.html) you can do something like:
impl<'a> From<T> for BroadcastParameter<'a,T>
where T : impl ToPrimative
{
...
}
impl<'a> From<Series> for BroadcastParameter<'a,Series>
{
...
}
The problem is that the compiler disallows this because we dont own the Series struct and the owner of that Struct might in a future release implement the trait ToPrimative for Series, breaking the code above.
So we can either be explicit or create a macro.
but there is an issue with then also implementing it for Series as it's from a foreign crate
geopolars/src/geoseries.rs
Outdated
|
||
pub fn test_fn<'a>( | ||
x: impl Into<BroadcastableParameter<'a, i64>>, | ||
y: impl Into<BroadcastableParameter<'a, f32>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe another dumb question but how does this work with BroadcastableParameter<'a, f32>
, when you only defined From
on f64
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I hadn't noticed that myself, I think I had probably meant to put f64 there. Presumably it works because f32 can be coerced in to an f64? If we had defined it the other way around, implemented the From for f32 and try a impl Into<BroadcastableParameter<'a,f64>
it would have failed
use geo::algorithm::bounding_rect::BoundingRect; | ||
use geo::algorithm::centroid::Centroid; | ||
match origin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this could get messy quickly, where we have three different enum values for TransformOrigin
and two different enum values for BroadcastableParameter
, though I don't immediately have a better idea
|
||
let apply_rotation: Box<dyn Fn(&Geometry<f64>, f64) -> Geometry<f64>> = match origin { | ||
TransformOrigin::Centroid => Box::new(|geom: &Geometry<f64>, angle: f64| { | ||
let centroid = geom.centroid().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated: At some point we should get more serious about error handling and ban .unwrap
and .expect
, but fine for now
geopolars/src/geoseries.rs
Outdated
.map(|(index, geom)| match angle { | ||
BroadcastableParameter::Val(v) => apply_rotation(&geom, v), | ||
BroadcastableParameter::Series(s) => { | ||
apply_rotation(&geom, s.f64().unwrap().get(index).unwrap()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question here: if we're now taking series
as input, then how should we handle its inherent dynamic typing? Here the code would panic if the series contained any data type other than f64
. What if the series contained f32
...? Seems like a situation where we'd want several numeric data types to work seamlessly
TransformOrigin::Point(point) => Box::new({ | ||
move |geom: &Geometry, angle: f64| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the extra {
after Box::new(
intentional?
Why do you have move
here, when you don't in the two cases above?
38fd39b
to
5d27e8e
Compare
Updated this to allow the Broadcastable parameters to be iterated over. If they are a series, we return the next value in the series iterator and if they are a single value we just return that value over and over again. This lets us use them in a cleaner way when there are multiple potential broadcastable parameters passed to a function, we can just zip them with the series we are operating on and use the values regardless. For example let angle: BroadcastableParameter<'a, f64> = angle.into();
let rotated_geoms: Vec<Geometry<f64>> = iter_geom(self)
.zip(angle.into_iter())
.map(|(geom, angle)| apply_rotation(&geom, angle.extract::<f64>().unwrap()))
.collect(); The iterator returns an AnyValue to be consistent with the output of SeriesIter from polars. We will want to make that unwrap actually return a Result if the type cant be converted to one we need. That will require making these functions also return a result . Next up is to try and remove the f64 type from the BroadcastableParameter and just have it be anything that AnyType supports. Having issues with this kind of bug when trying to do this with generics and Impls though. For example: impl <'a,T> From<T> for BroadcastableParameter<'a>
where T : Into<AnyValue<'a>>
{
fn from(val: T) -> Self{
BroadcastableParameter::Val(val.into())
}
}
impl<'a> From<&'a Series> for BroadcastableParameter<'a>
{
fn from(series: &'a Series) -> BroadcastableParameter {
BroadcastableParameter::Series(series)
}
} Gives the error error[E0119]: conflicting implementations of trait `std::convert::From<&polars::prelude::Series>` for type `broadcasting::BroadcastableParameter<'_>`
--> geopolars/src/broadcasting.rs:55:1
|
46 | / impl <'a,T> From<T> for BroadcastableParameter<'a>
47 | | where T : Into<AnyValue<'a>>
48 | | {
49 | | fn from(val: T) -> Self{
50 | | BroadcastableParameter::Val(val.into())
51 | | }
52 | | }
| |_- first implementation here
...
55 | / impl<'a> From<&'a Series> for BroadcastableParameter<'a>
56 | | where
57 | | {
58 | | fn from(series: &'a Series) -> BroadcastableParameter {
59 | | BroadcastableParameter::Series(series)
60 | | }
61 | | }
| |_^ conflicting implementation for `broadcasting::BroadcastableParameter<'_>`
|
= note: upstream crates may add a new impl of trait `std::convert::From<&polars::prelude::Series>` for type `polars::prelude::AnyValue<'_>` in future versions which seems like it's hard to get around. |
One worry is that this won't error if the series argument isn't the same size as the underlying series data? Seems like we'd want to assert that. But I agree it's clean.
Ah, my favorite error. I hit this (and haven't figured out a solution) when trying to update |
Hey @kylebarron putting this here for discussion. I think this might be a good pattern for functions that can take either a value or a series as their arguments.
I have implemented it for rotate so we can get a feel of how it works.
Will move the BroadcastableParameter enum to its own file if we are happy with this approach