Skip to content
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

Implement a MapCoordsFallible trait #191

Merged
merged 7 commits into from
Mar 19, 2018
Merged

Conversation

urschrei
Copy link
Member

This trait is identical to MapCoords, but is intended for use when mapping fallible functions over geometries.

It also adds Failure as a dependency, but I think that's OK.

Copy link
Member

@amandasaurus amandasaurus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the point of using a new dependency (failure)? I've seen that crate around in Rust news, but I've never used it nor looked into it. Rust has an Err option, which is what I'd use if I was implementing this.

If there's a 100 point LineString, and you call map_coords_inplace_failible, which is OK for first 10 points, and then fails, won't this make the LineString wrong because only the first 10 will have changed? The only way to work around this is to create a new LineString and std::mem::replace it in, right?

@urschrei
Copy link
Member Author

If there's a 100 point LineString, and you call map_coords_inplace_failible, which is OK for first 10 points, and then fails, won't this make the LineString wrong because only the first 10 will have changed? The only way to work around this is to create a new LineString and std::mem::replace it in, right?

I should probably write a test that simulates this, but my intent is that each map call Result is checked. If it's an Error, that bubbles up to the main function via early return.

@urschrei
Copy link
Member Author

urschrei commented Mar 15, 2018

What's the point of using a new dependency (failure)? I've seen that crate around in Rust news, but I've never used it nor looked into it. Rust has an Err option, which is what I'd use if I was implementing this.

Failure is a lot more flexible than the built-in Err. Using it means we can choose between several different error-handling strategies, and even mix them as necessary. For instance, it's easy to return a generic Error with a String as the cause (which is what I'm doing in #190) if it's coming from an external C lib. But we can also specify Geo specific errors, and because they can be converted into Error, the function signature wouldn't change. Failure's errors can also contain backtrace information, and nest, and it's possible to iterate over nested causes to see exactly what went wrong, and where.
Geo's error-handling story is (imo, and I'm certainly guilty of exacerbating it with unwrap() everywhere) under-specified and lacking at the moment, and this PR (and hopefully #190) both do things that are assumed to break – it seemed like a good time to get the ball rolling on the use of a library which will more than likely become the de-facto error-handling library in Rust.

@amandasaurus
Copy link
Member

my intent is that each map call Result is checked. If it's an Error, that bubbles up to the main function via early return.

Sure, but since you're modifying the object in place, won't the modifications to the first 10 points still be there? Those can't be undone.

You could change the docs to say "Check the returned error of this, and if it's an error the object is now in an inconsistant state and shouldn't be used"

@urschrei
Copy link
Member Author

You could change the docs to say "Check the returned error of this, and if it's an error the object is now in an inconsistant state and shouldn't be used"

Ah, I see what you mean. I had assumed that an Err return value would signal that the operation was unsuccessful, and the result should be discarded, but it's better to be explicit about what the Err means in this context.

@urschrei
Copy link
Member Author

Hmm the more I think about the fallible in-place version, the less I like what I've done. @rory is correct here I think – mem::replace is a better idea. If any of the operations do fail, it seems much better to return the unchanged version as an Err. But there's now very little difference to MapCoordsFallible.

This trait is identical to MapCoords, but is intended for use when
mapping fallible functions over geometries
Also add note on the consistency of geometries upon which failed
operations have been carried out.
@@ -59,16 +109,37 @@ impl<T: CoordinateType, NT: CoordinateType> MapCoords<T, NT> for Point<T> {
}
}

impl<T: CoordinateType> MapCoordsInplace<T> for Point<T> {
impl<T: CoordinateType, NT: CoordinateType> MapCoordsFallible<T, NT> for Point<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of having all these explicit MapCoordsFallible impls, what if we had a blanket impl like this (i haven't tried compiling it so it might be wrong):

impl<T: CoordinateType, NT: CoordinateType, G: MapCoords<T, NT>> MapCoordsFallible<T, NT> for G {
    type Output = G::Output;

    fn map_coords_fallible(
        &self,
        func: &Fn(&(T, T)) -> Result<(NT, NT), Error>,
    ) -> Result<Self::Output, Error> {
        Ok(self.map_coords(func))
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can implement it in terms of map_coords, because of the different input function signatures:
map_coords is func: &Fn(&(T, T)) -> (NT, NT)
map_coords_fallible is func: &Fn(&(T, T)) -> Result<(NT, NT), Error>

And (I think) because we're also missing a try! or ? to check for an early return, somewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, you're right 👍

i don't think there's a way to make this work since we can't return from the outer function within the closure if func returs an error

@amandasaurus
Copy link
Member

amandasaurus commented Mar 18, 2018 via email

@urschrei
Copy link
Member Author

MapcoordsFaillible is just a clone & MapCoordsInplaceFalliable. Mapcoordsinplace is just a wrapping the function to always return Ok() and then call MapCoordsInplaceFalliable etc etc.

Can you sketch this out (or send a PR to my branch)?

As for MapCoordsinplaceFallible, I've been tying myself in knots trying to get it right, and I don't know whether it's worth it – the best case scenario is that it's hiding a clone operation which means that someone (probably one of us) will end up complaining about memory usage and/or slowness at some point.

@amandasaurus
Copy link
Member

amandasaurus commented Mar 18, 2018 via email

@urschrei
Copy link
Member Author

I agree, I'll get rid of it.

More trouble than it's worth
@frewsxcv
Copy link
Member

looks good to me. a little skeptical about incorporating failure, but it seems fine for now and we can always restart that discussion in the future

bors r+

@bors
Copy link
Contributor

bors bot commented Mar 19, 2018

👎 Rejected by code reviews

@frewsxcv
Copy link
Member

i guess i should wait for @rory to dismiss their review (or approve the pr). any other concerns?

@frewsxcv
Copy link
Member

bors r+

bors bot added a commit that referenced this pull request Mar 19, 2018
191: Implement a MapCoordsFallible trait r=frewsxcv a=urschrei

This trait is identical to MapCoords, but is intended for use when mapping fallible functions over geometries.

It also adds Failure as a dependency, but I think that's OK.
@bors
Copy link
Contributor

bors bot commented Mar 19, 2018

Build succeeded

@bors bors bot merged commit 11f4c60 into georust:master Mar 19, 2018
bors bot added a commit that referenced this pull request Apr 8, 2018
192: Implement rust-proj as an optional feature within geo r=frewsxcv a=urschrei

Continuing the discussion from #190 and #32.

## Introduction
`rust-proj` is currently (as of `0.3.0`) using PROJ v4.9.x, and depends upon `Geo` for its `Point` type. However, we now have a `proj-sys` crate, with low-level bindings to PROJ v5.0.0. Unfortunately, `rust-proj's` dependency on `Geo` means that we can't directly use its functions within `Geo`, as this introduces a circular dependency (see #190).
### This PR:
- Re-implements `rust-proj`'s functionality as an optional feature ("`proj`") within `Geo` using `proj-sys`
    - it has to be optional, as it requires PROJ v5.0.0 to be present
- Makes two new traits –`Project` and `Convert` – available to any geometries implementing the `MapCoordsFallible` trait (see #191), allowing them to be (inverse) projected to and from Geodetic coordinates, and converted between projections. This functionality is a slightly more granular version (in keeping with the new PROJ API, which makes the same distinction) of what's possible using `pyproj` and `Shapely` geometries (see [here](http://shapely.readthedocs.io/en/stable/manual.html#other-transformations) for the official example):
```python
from shapely.geometry import Point, LineString
import pyproj
# LineString in projected coordinates (OSGB36)
ls = LineString([(548295.39, 182498.46), (548296.39, 182499.46)])
osgb36=pyproj.Proj("+init=EPSG:27700")
eastings, northings = list(zip(*ls.coords))
# inverse-project to geodetic lon, lat coordinates (WGS 84)
conv_ls = zip(*osgb36(eastings, northings, inverse=True))
# compare to command-line proj output
assert conv_ls == [(0.13774816464635564, 51.52142699952049), (0.1377629902608001, 51.5214357231341)]
```

I propose that we leave `rust-proj` at 0.3.0, clearly marking it as deprecated, directing users to install `Geo` with the `proj` feature if they want v5.0.x features, and linking to `proj-sys` for implementers who don't wish to use `Geo`, for whatever reason.

We can also (as per my and @turboladen's discussion in #32) highlight the availability of this feature more clearly, if need be.

Co-authored-by: Stephan Hügel <urschrei@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants