Open bossmc opened 5 years ago
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
...).
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.
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 :)
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?
Ah, yes, I missed that warning text. Two follow on questions:
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]
- 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 !
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:
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<_, _>
.
A basic (working) example of transmogrification might look like:
This compiles and works just fine but, if you change the definition of
Foo2
to be[1]:Then the compile fails with the error:
Strangely, if
Bar
does not implementLabelledGeneric
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 twoLabelledGeneric
structs.[1](Note that this means that
Foo
andFoo2
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)