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

Ipld selector traversals implementation #408

Merged
merged 22 commits into from
May 12, 2020
Merged

Conversation

austinabell
Copy link
Contributor

Summary of changes
Changes introduced in this pull request:

  • Implements walk functions for selectors, which is the last missing part needed for GraphSync

    • The part that still can be done is Focus impls, which is just being able to traverse ipld by path, which isn't needed for any use case we have (afaik)
  • I removed exposing the selector operation since there should be no external use of those (if there is it can just be easily updated when needed)

Test suite are json files that will probably be moved out to some shared repo, but for now I will leave as is

Reference issue to close (if applicable)

Closes #241

Other information and links

};
self.clone()
.traverse_node(ipld, selector.clone(), callback, ps, v)
.await?
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use a filter_map + map here? interests.filter_map.(...).map(..)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm a bit confused by this, can you explain? Do you mean filtering based on the available paths of the ipld?

Copy link
Contributor

Choose a reason for hiding this comment

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

filtering based on the ipld.look_up_segment with a filtermap essentially. would save us from having the continue clause there since filter_map is based on options. I guess the issue though is that you might have to unwrap v in the map clause but it should be already filtered out

Copy link
Contributor Author

@austinabell austinabell May 12, 2020

Choose a reason for hiding this comment

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

yeah I had considered, I think the reason I hadn't was because integer path segments can be used ambiguously as fields of a map, but let me revisit to remember why I didn't go through with it

Edit: yeah it very quickly gets more unreadable, I didn't try very in depth though. Do you have a suggestion on how this can be done? I think it's possible, there just isn't an idiomatic call that fits this need well

Copy link
Contributor

Choose a reason for hiding this comment

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

The trouble here is that calling filter_map with ipld.lookup_segment will give you an iterator of Iplds without the paths (which you still need) so it does indeed become pretty verbose:

for (ps, ipld) in interests
    .into_iter()
    .filter_map(|ps| ipld.lookup_segment(&ps).map(|ipld| (ps, ipld)))
{
    // ...
}

Not really an improvement. Unless there's a way to improve this which I haven't thought of

@austinabell
Copy link
Contributor Author

austinabell commented May 12, 2020

There is a bit of a bug in the go impl and by extension this right now. I'm writing a test for the edge case and will restructure to fix so hold off on reviewing

Edit: good to go now

ipld/src/path.rs Outdated Show resolved Hide resolved
pub async fn walk_all<L, F>(
self,
ipld: &Ipld,
resolver: Option<L>,
Copy link
Contributor

Choose a reason for hiding this comment

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

I suggest we just make this L instead of Option<L>, and implement LinkResolver for Option<T> whenever T implements it. Option's implementation of the trait can then handle a missing resolver (i.e. return Ok(None)), rather than baking it into this logic here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

for what benefit? That just seems less readable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Like, what I mean by this is when you don't specify a link resolver the type inference is either more verbose or you pass in something other than None into the function and that be less readable. I think usability is more important than possibly some checks removed in the code (and less prone to unexpected behaviour if expanded on)

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess what I'm saying is that it's double that the link resolver is optional, and the ipld it returns is also optional. if you require an L and use () or some other type instead of None then the code becomes simpler, I've tried it — I'm also happy to make this change later if my description isn't really clear

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah but then it's not obvious that the resolver is optional and a little janky to read when you are passing () or otherwise in as a parameter. That is specifically what Option is for, plus also you are auto deriving the trait impl for every linkresolver impl.

Maybe I'm misunderstanding a detail of what you are saying. I am open to considering, it just seems like an opinionated and less idiomatic API

Copy link
Contributor

Choose a reason for hiding this comment

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

That is specifically what Option is for

I would definitely agree in the case of a concrete type; the annoying thing about Option in combination with generics though is that the None is still generic over some T that you don't really care about when you don't want to pass a link resolver at all.

Anyway, this whole API is inherently not very idiomatic Rust because of how directly it was ported from Go, so it's not a big deal whether this part is idiomatic or not. This is definitely not a blocker, but rather something to think about when we decide to refactor it.

ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
Comment on lines +66 to +68
async fn load_link(&self, link: &Cid) -> Result<Option<Ipld>, String> {
Err("load_link not implemented on the LinkResolver".into())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
async fn load_link(&self, link: &Cid) -> Result<Option<Ipld>, String> {
Err("load_link not implemented on the LinkResolver".into())
}
async fn load_link(&self, link: &Cid) -> Result<Option<Ipld>, String>;

Seems to me like you'd want to require conforming types to implement this? They can always return an error themselves if that's appropriate behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nah there will be other methods added to this if/when they are needed, makes sense to just default to not implemented so someone implementing their own resolver doesn't have to specify the functions if they don't want to use

Copy link
Contributor

Choose a reason for hiding this comment

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

Should it return an error if you don't implement it though, rather than returning Ok(None)?

Copy link
Contributor Author

@austinabell austinabell May 12, 2020

Choose a reason for hiding this comment

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

Yeah so I'm not a huge fan of the setup, but the intention is either you don't specify a resolver and those will just be skipped, or you do specify a resolver and if it requires that function for resolving, then it would error.

The idea being if you specify a resolver, it shouldn't quietly skip if it's not configured correctly, thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

If quietly skipping links is not deemed acceptable then I would generally speaking rather have the compiler tell me about it than seeing some error at runtime. But I'm having a hard time seeing how this API will evolve exactly so I can't say for sure which approach is the most logical

Copy link
Contributor Author

@austinabell austinabell May 12, 2020

Choose a reason for hiding this comment

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

Ah ya mb I forgot about this and had merged in, definitely open to switching it to not default, just for now doesn't matter because this isn't an API used outside of our codebase yet

For some reason I thought I already made this change

ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
ipld/src/selector/walk.rs Outdated Show resolved Hide resolved
@austinabell austinabell merged commit 4c95043 into master May 12, 2020
@austinabell austinabell deleted the austin/ipld/traversal branch May 12, 2020 19:36
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.

IPLD: Implement Selectors and Associated Logic
6 participants