lloydmeta / frunk

Funktional generic type-level programming in Rust: HList, Coproduct, Generic, LabelledGeneric, Validated, Monoid and friends.
https://beachape.com/frunk/
MIT License
1.24k stars 56 forks source link

Map a single field of the struct #217

Open dima-starosud opened 1 year ago

dima-starosud commented 1 year ago

I am guessing SO is probably a better place to ask this, but my case is so trivial, that I would expect to see an example of it in README or docs.

There is a generic struct:

struct A<T> {
    a: i32,
    b: i32,
    c: String,
    ph: PhantomData<T>
}

and I want to avoid boilerplate doing the following:

fn into_unit<T>(a: A<T>) -> A<()> {
    A { a, b, c, ph:_ } = a;
    A { a, b, c, ph: PhantomData }
}

What would be the best way to do this with frunk?

Also, it would be great to add an example to the docs.

I've tried these:

  1. transmogrify doesn't work probably because types of PhantomData<T> and PhantomData<()> are different;

  2. map - need overlapping instances:

    fn a_1<T>(a: A<T>) -> A<()> {
        let a = frunk::into_generic(a).map(poly_fn![
            [T] |_q: PhantomData<T>| -> PhantomData<()> { PhantomData },
            [T] |x: T| -> x { x },
        ]);
        frunk::from_generic(a)
    }
  3. pluck - not sure why it doesn't work:

    fn a_2<T>(a: A<T>) -> A<()> {
        let (x, tail) = frunk::into_labelled_generic(a).pluck::<Field<_, PhantomData<T>>, _>();
    
        fn map_field<L, V>(_: Field<L, PhantomData<V>>) -> Field<L, PhantomData<()>> {
            field![L, PhantomData]
        }
    
        let hl = h_cons(map_field(x), tail);
    
        frunk::from_labelled_generic(hl)
    }
dima-starosud commented 1 year ago

OK. Found the solution:

fn a_5<T>(a: A<T>) -> A<()> {
    let a = frunk::into_labelled_generic(a);
    frunk::from_labelled_generic(hlist![field![_, PhantomData::<()>], ...a].sculpt().0)
}

But is that the best way to do this?

lloydmeta commented 1 year ago

Thanks for putting up this question. To be honest, I don't consider this to be a trivial thing at all, so it's great that you figured it out. The final solution looks good, and I'm not sure I could come up with something better: building the toolkit is one thing, figuring out how to to "best" use it is something the community generally figures out better, so I'd recommend sending this to SO to see what smart ppl can figure out 😄

I'm actually quite amazed to see how much the compiler is actually able to "figure out" for you in your solution in that last line ... (placeholder field name type....sculpting...then into labelled generic). Since IME, the compiler optimises out all the intermediates states when run in release mode, my only thought is whether this going to cause you compile time woes if you have much more complex structs that you're planning to do this with. e.g. if need be (compiling takes long, or it can't figure things out), it could be worth trying to help it along with types..

fn a_5<T>(a: A<T>) -> A<()> {
    use frunk::labelled::chars::*;
    type ph = (p, h);
    let a = frunk::into_labelled_generic(a);
    frunk::from_labelled_generic(hlist![field![ph, PhantomData::<()>], ...a].sculpt().0)
}
dima-starosud commented 1 year ago

Thank you! I appreciate the clarification.

There is an unstable feature type changing struct update syntax which should support this out of the box, and I thought that frunk could have this as a kind of killer feature while std one is unstable.

That reminded me a quote by Alan Kay "Simple things should be simple, complex things should be possible.". And to me mapping a single field from T to () doesn't seem to be a complex thing 🤔

Probably with overlapping instances, it could be more straightforward:

let a = frunk::into_generic(a);
let a = a.map(poly_fn![[T] |_: PhantomData<T>| PhantomData::<()>]);
frunk::from_generic(a)

And I think I saw some utility which does into/from for us, so the final could be:

with_generic(a, |a| a.map(poly_fn![|_: [T] PhantomData<T>| PhantomData::<()>]))

But I think you're right, there is a place for this in SO.

lloydmeta commented 1 year ago

@dima-starosud gotcha. It might make sense to have this in example or tests so that it can be checked + documented ?

dima-starosud commented 1 year ago

Sure 👍 would be great to have this in the example folder I think. I can make a PR if needed.

dima-starosud commented 1 year ago

@lloydmeta Could you assign this to me?

dima-starosud commented 1 year ago

Posted the question https://stackoverflow.com/questions/76612428/stable-analogues-of-type-changing-struct-update-syntax.

Will proceed with the ticket once get some good answers to it.