Add more clarifying examples to Sculptor documentation#251
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an HList “projection” API to borrow multiple elements by type (without moving them), returning a new HList of shared references. This complements existing selection/sculpting capabilities by providing a generalized multi-get operation.
Changes:
- Introduce
HCons::projectinherent method for projecting an HList into a target “shape” as shared references. - Add the
Projector<Targets, Indices>trait with recursive implementations built on top ofSelector. - Add unit tests covering simple, multi-type, reordered, and “borrow doesn’t move” scenarios.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// `project` allows us to select many components of a structure without moving or removing them, | ||
| /// but instead borrowing them from the original. | ||
| /// That is, provided that the requested types are contained wihtin the currnt HList. | ||
| /// | ||
| /// It also does not require the specified types to be in the same order as they are in the current hlist. |
| // fn project_non_existant() { | ||
| // let h = hlist![1u32, "hello world", 42i64, true]; | ||
| // type S = HList![i64, f32]; // <--- There is not 'f32' in our example HList | ||
|
|
||
| // let projection = h.project_ref_ext::<S, _>(); // <--- therefore this method will not work |
| let projection = h.project::<S, _>(); | ||
| let hlist_pat![usize_ref, isize_ref] = projection; | ||
|
|
||
| assert_eq!(*usize_ref, 1u32); | ||
| assert_eq!(*isize_ref, 42i64); |
| let projection = h.project::<S, _>(); | ||
| let hlist_pat![isize_ref, usize_ref] = projection; | ||
|
|
||
| assert_eq!(*isize_ref, 42i64); | ||
| assert_eq!(*usize_ref, 1u32); |
| let projection = h.project::<S, _>(); | ||
| let hlist_pat![string, bool, int64, un32, float64] = projection; | ||
|
|
||
| assert_eq!(*string, "hello world".to_string()); | ||
| assert_eq!(*bool, true); | ||
| assert_eq!(*int64, 99i64); | ||
| assert_eq!(*un32, 1u32); | ||
| assert_eq!(*float64, 12f64); |
|
Very cool stuff ; I'm intrigued and think this would fit in the library yes. I'm going to take a closer look when I have some more time, and maybe have a think about the name (I've tried hard to pick names that are extremely concrete, and obvious), though at the moment I don't have anything better than what you came up with :) thanks for the PR! |
|
Sorry that I didn't remember this (been a while since I've done much work here) but I think this accomplishes something similar and is supported today (takes an extra Added in #[test]
fn test_sculpt_after_ref() {
let non_ref = hlist![9000, "joe", 41f32];
let h = non_ref.to_ref();
let (reshaped, remainder): (HList!(&f32, &i32), _) = h.sculpt();
assert_eq!(reshaped, hlist![&41f32, &9000]);
assert_eq!(remainder, hlist![&"joe"])
}
#[test]
fn test_sculpt_after_ref_yours() {
let non_ref = hlist![123u32, "hello world", true];
let h = non_ref.to_ref();
let (reshaped, remainder): (HList!(&bool, &u32), _) = h.sculpt();
assert_eq!(reshaped, hlist![&true, &123]);
assert_eq!(remainder, hlist![&"hello world"])
} |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
|
Ah I see... My main concern was whether or not the original non_ref would be moved. |
#[test]
fn test_sculpt_after_ref_yours() {
let non_ref = hlist![123u32, "hello world", true];
let h = non_ref.to_ref();
let (reshaped, remainder): (HList!(&bool, &u32), _) = h.sculpt();
assert_eq!(reshaped, hlist![&true, &123]);
assert_eq!(remainder, hlist![&"hello world"]);
assert_eq!(non_ref.head, 123u32);
}I think that works, because Lines 3 to 19 in a95af41 Just now, I was confused by this as well (why did I name it |
|
I guess this is bit of a facepalm for both of us... Though the exposure to type-level programming has been a great experience so far. Therefore, I think moving forward it would be nice to have something like this explicitly documented as I think its actually quite a nice feature. |
++ happy to have this documented; there's so many ways to compose this stuff that it certainly can be good to have it written down if you consider it useful. |
…h Sculptor already acheive the same effect
|
Alright I've removed the |
fix typo Co-authored-by: Lloyd <lloydmeta@users.noreply.github.com>
Usage example:
Description:
HList projection is an operation that views a heterogeneous list through a type-level “lens” of sorts, producing a new HList of shared references to selected elements. This is similar to how
Sculptoris like a generalized versionPlucker.Projectoraims to be a more generalized version ofSelector. It works by recursively callinggeton the target types and accumulating their references in a HList. Though, this does come at a bit of a caveat...We are calling
getmultiple times, which is fine because its a shared reference. The problem is that this locks us out of a possiblefn project_mutin the future because Rust disallows multiple mutable references. Though, there could create a separateProjectMuttrait in the future, but the question of how to safely handle multiple dangling mutable references is still at large. I have documented a few possible workarounds in the footnotes of my crate: https://crates.io/crates/rust-hlist-projection/0.1.0There is also another factor to consider. Right now the associated type
Projection<'a>is generic over some lifetime'a, so the trait as a whole is inherently not dyn compatible. This could be a limitation in some use cases, so a potential solution might be to move the lifetime to the trait generics. I didn't opt for it initially as it might not fit the style considering thatSelectordoesn't specify a lifetime on the trait generics even though its function is to borrow values.Motivation:
I'm fairly new to functional programming, so when I stumbled upon your article on BeachApe I was quite intrigued by the type recursion. Looking into the code base I noticed that there is a missing link of abstraction of sorts. As mentioned before, one could think that
Sculptoris a generalized version ofPlucker. However, I couldn't find any clean or explicitly documented "generalized"Selector, which is where this PR comes into play. I hope some people find this interesting and useful as I don't really have any decent real-world use cases for something like this yet, though having the functionality available for the future wouldn't be a bad thing.Also:
Is
projecta good name for this? I wouldn't mind if it was changed.