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

Transmogrification fails if common field implements LabelledGeneric #143

Open
bossmc opened this issue Jan 22, 2019 · 6 comments
Open

Transmogrification fails if common field implements LabelledGeneric #143

bossmc opened this issue Jan 22, 2019 · 6 comments

Comments

@bossmc
Copy link
Contributor

bossmc commented Jan 22, 2019

A basic (working) example of transmogrification might look like:

use frunk_core::labelled::Transmogrifier;

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Foo {
    pub bar: Bar,
}

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Foo2 {
    pub bar: Bar2,
}

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Bar {
    pub index: i64,
}

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Bar2 {
    pub index: i64,
}

fn main() {
    let foo = Foo {
        bar: Bar { index: 4 }
    };
    let foo: Foo2 = foo.transmogrify();
    println!("{:?}", foo);
}

This compiles and works just fine but, if you change the definition of Foo2 to be[1]:

#[derive(Debug, frunk_derives::LabelledGeneric)]
struct Foo2 {
    pub bar: Bar,
}

Then the compile fails with the error:

error[E0282]: type annotations needed
  --> src/main.rs:27:25
   |
27 |     let foo: Foo2 = foo.transmogrify();
   |                         ^^^^^^^^^^^^ cannot infer type for `TransMogSourceHeadValueIndices`

Strangely, if Bar does not implement LabelledGeneric then the code compiles and works as expected. (Have you accidentally tricked the compiler into implementing specialization on stable?!)

This makes it hard to mix types in structs, since no two LabelledGeneric fields can ever be common between two LabelledGeneric structs.

[1](Note that this means that Foo and Foo2 are identical, though this need not be the case in general, as other fields may be present and differ up to transmogrifiability without affecting the result)

@bossmc
Copy link
Contributor Author

bossmc commented Jan 22, 2019

Ah, I've worked out why this is failing, the compiler has hit the decision of whether to use the T -> T implementation or the T -> Repr -> T implementation and it can't decide which to use 😞.

Not sure what can be done here (especially if one wanted to move between these two Foo's and a third type that didn't contain an exact Bar...).

@ExpHP
Copy link
Collaborator

ExpHP commented Jan 22, 2019

Yep, this issue was known since before the feature was added, and to be honest is something I regard as a crippling limitation. I really don't know what can be done about it in the confines of rust's trait system.

@lloydmeta
Copy link
Owner

Indeed, this was known and is noted in the Transmogrification section as well.

It's a fairly annoying limitation, true, but I decided to add it because (1) it's still got some use and (2) when specialisation is done we can hit the ground running :)

@bossmc
Copy link
Contributor Author

bossmc commented Jan 22, 2019

Ah, yes, I missed that warning text. Two follow on questions:

  1. Do you need more than specialisation? I think you need the "lattice impl" extension (maybe that's going to be part of specialisation when it lands?) since you have overlapping but non-containing impls:

    • The two types are the same type
    • The two types implement LabelledGeneric (with convertibility bounds)
  2. Is there a way today to "steer" the type-inference machine? I'm thinking something like:

     <foo as Transmogrifier<Bar, (_, _ ,_ Identity, _, _)>>::transmogrify()
    

    Except the types in the index field are incredibly hard to describe... Maybe some extra macros to create the steering for the user?

@lloydmeta
Copy link
Owner

Ah, yes, I missed that warning text. Two follow on questions:

  1. Do you need more than specialisation? I think you need the "lattice impl" extension (maybe that's going to be part of specialisation when it lands?) since you have overlapping but non-containing impls:

    • The two types are the same type
    • The two types implement LabelledGeneric (with convertibility bounds)

I'll have to admit that I don't have an up-to-date view of what specialisation will bring in Rust, so you could well be right :D. In Scala, something like this works:

@ {
  trait Foo[A] {
    def go(o: A): Unit
  }

  object Foo {
    implicit val intFoo: Foo[Int] = new Foo[Int] {
      def go(o: Int): Unit = println(s"int foo [$o]")
    }

    implicit def allFoo[A]: Foo[A] = new Foo[A] {
      def go(o: A): Unit = println(s"all foo [$o]")
    }
  }
  }
defined trait Foo
defined object Foo

@ def fooIt[A: Foo](o: A) = implicitly[Foo[A]].go(o)
defined function fooIt

@ fooIt(3)
int foo [3]

@ fooIt("hello")
all foo [hello]
  1. Is there a way today to "steer" the type-inference machine? I'm thinking something like:
     <foo as Transmogrifier<Bar, (_, _ ,_ Identity, _, _)>>::transmogrify()
    
    Except the types in the index field are incredibly hard to describe... Maybe some extra macros to create the steering for the user?

Yeah, I'm pretty sure that it is possible to do something like that to steer the inference (at the very least, I did this during at least one of the implementations); but yeah, as you said, without something like a procedural macro it would be hard to do right. Definitely worth exploring though !

@ExpHP
Copy link
Collaborator

ExpHP commented Jan 26, 2019

I lack confidence that specialization will help.

In the current form of the trait, specialization is ineffectual. Index is a type parameter, and will invariably be different for the two conflicting impls (the Self -> LabelledGeneric -> Self index must encode information about how to sculpt the fields, while the Self -> Self impl must be unitlike). Hence, the impls technically do not overlap.

This means Index must become an associated type. For that to work, it must also become an associated type of the hlist traits. This leaves us to ask a much simpler question:

  • Can specialization enable the Index type of Plucker to become an associated type? (or even removed?)

And... well... I dunno! I really don't understand how specialization works or how much of it is even implemented, to be honest. I gave it a try, sprinking default all over the place, but whatever I do rust still simply complains that there are overlapping impls of Plucker<_> for HCons<_, _>.

bors bot added a commit to Metaswitch/swagger-rs that referenced this issue May 6, 2020
116: Revert "Add frunk support for swagger::Nullable" r=richardwhiuk a=richardwhiuk

This reverts commit 92f6f85.

This reverts #114 which caused problems due to lloydmeta/frunk#143

Co-authored-by: Richard Whitehouse <[email protected]>
richardwhiuk added a commit to Metaswitch/swagger-rs that referenced this issue May 6, 2020
116: Revert "Add frunk support for swagger::Nullable" r=richardwhiuk a=richardwhiuk

This reverts commit 92f6f85.

This reverts #114 which caused problems due to lloydmeta/frunk#143

Co-authored-by: Richard Whitehouse <[email protected]>
richardwhiuk added a commit to Metaswitch/swagger-rs that referenced this issue May 6, 2020
116: Revert "Add frunk support for swagger::Nullable" r=richardwhiuk a=richardwhiuk

This reverts commit 92f6f85.

This reverts #114 which caused problems due to lloydmeta/frunk#143

Co-authored-by: Richard Whitehouse <[email protected]>
Signed-off-by: Richard Whitehouse <[email protected]>
bors bot added a commit to Metaswitch/swagger-rs that referenced this issue May 6, 2020
117: Revert "Add frunk support for swagger::Nullable" r=richardwhiuk a=richardwhiuk

This reverts #115 which caused problems due to lloydmeta/frunk#143 by merging forward #116

Co-authored-by: bors[bot] <26634292+bors[bot]@users.noreply.github.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

No branches or pull requests

3 participants