-
-
Save rkuhn/2870fcee4937dda2cad5 to your computer and use it in GitHub Desktop.
import scala.annotation.unchecked.uncheckedVariance | |
object Trial2712 { | |
trait NotUsed | |
import language.higherKinds | |
/* | |
* The following are simplified versions of FlowOps / FlowOpsMat and their | |
* concrete subtypes. | |
*/ | |
trait FO[+Out, +Mat] { | |
type Repr[+O] <: FO[O, Mat] { | |
type Repr[+OO] = FO.this.Repr[OO] | |
} | |
def map[T](f: Out => T): Repr[T] = ??? | |
} | |
trait FOM[+Out, +Mat] extends FO[Out, Mat] { | |
type Repr[+O] <: FOM[O, Mat] { | |
type Repr[+OO] = FOM.this.Repr[OO] | |
type ReprMat[+OO, +MM] = FOM.this.ReprMat[OO, MM] | |
} | |
type ReprMat[+O, +M] <: FOM[O, M] { | |
type Repr[+OO] = FOM.this.ReprMat[OO, M @uncheckedVariance] | |
type ReprMat[+OO, +MM] = FOM.this.ReprMat[OO, MM] | |
} | |
def mapMat[T](f: Mat => T): ReprMat[Out, T] = ??? | |
} | |
class Source[+O, +M] extends FOM[O, M] { | |
type Repr[+OO] = Source[OO, M @uncheckedVariance] | |
type ReprMat[+OO, +MM] = Source[OO, MM] | |
} | |
class Flow[-I, +O, +M] extends FOM[O, M] { | |
type Repr[+OO] = Flow[I @uncheckedVariance, OO, M @uncheckedVariance] | |
type ReprMat[+OO, +MM] = Flow[I @uncheckedVariance, OO, MM] | |
} | |
class SubSource[+O, +M] extends FO[O, M] { | |
type Repr[+OO] = SubSource[OO, M @uncheckedVariance] | |
} | |
class SubFlow[-I, +O, +M] extends FO[O, M] { | |
type Repr[+OO] = SubFlow[I @uncheckedVariance, OO, M @uncheckedVariance] | |
} | |
/* | |
* These are helpers to work around SI-2712 which could be provided by Akka Streams. | |
*/ | |
trait U[X] { | |
type Out | |
type Mat | |
type R[+o] <: FO[o, Mat] { | |
type Repr[+O] = R[O] | |
} | |
def ch(x: X): R[Out] | |
} | |
implicit def uOne[O, M, TC[+o, +m] <: FO[o, m] { type Repr[+O] = TC[O, m @uncheckedVariance] }] = | |
new U[TC[O, M]] { | |
type Out = O | |
type Mat = M | |
type R[+O] = TC[O, Mat] | |
def ch(x: TC[O, M]) = x | |
} | |
implicit def uTwo[I, O, M, TC[i, +o, +m] <: FO[o, m] { type Repr[+O] = TC[i, O, m @uncheckedVariance] }] = | |
new U[TC[I, O, M]] { | |
type Out = O | |
type Mat = M | |
type R[+O] = TC[I, O, Mat] | |
def ch(x: TC[I, O, M]) = x | |
} | |
trait Umat[X] { | |
type Out | |
type Mat | |
type R[+o, +m] <: FOM[o, m] { | |
type Repr[+O] = R[O, m @uncheckedVariance] | |
type ReprMat[+O, +M] = R[O, M] | |
} | |
def ch(x: X): R[Out, Mat] | |
} | |
implicit def uOneMat[O, M, TC[+o, +m] <: FOM[o, m] { type Repr[+O] = TC[O, m @uncheckedVariance]; type ReprMat[+O, +M] = TC[O, M] }] = | |
new Umat[TC[O, M]] { | |
type Out = O | |
type Mat = M | |
type R[+o, +m] = TC[o, m] | |
def ch(x: TC[O, M]) = x | |
} | |
implicit def uTwoMat[I, O, M, TC[i, +o, +m] <: FOM[o, m] { type Repr[+O] = TC[i, O, m @uncheckedVariance]; type ReprMat[+O, +M] = TC[i, O, M] }] = | |
new Umat[TC[I, O, M]] { | |
type Out = O | |
type Mat = M | |
type R[+o, +m] = TC[I, o, m] | |
def ch(x: TC[I, O, M]) = x | |
} | |
/* | |
* This is how client code can now use the helpers to write extension methods that | |
* work across all FlowOps subtypes. | |
*/ | |
implicit class x[X](val x: X) extends AnyVal { | |
def xx(i: Int)(implicit u: U[X]) = u.ch(x).map(identity).map(identity) | |
def xy(implicit u: U[X]) = u.ch(x).map(_.toString).map(identity) | |
def m[T1, T2](f: T1 => T2)(implicit u: Umat[X] { type Mat <: T1 }) = | |
u.ch(x).mapMat(f) | |
} | |
/* | |
* These don’t work as intended: it seems impossible to get the types | |
* out there. This results in that an expression like | |
* | |
* f.mm(_ => "buh") | |
* | |
* might compile, but its returned type will not longer unify with | |
* Flow et al. It seems that the path-dependent type leaks out in the | |
* form of an existential. | |
*/ | |
class Y[X](val x: X)(implicit val u: Umat[X]) { | |
def mm[T](f: u.Mat => T) = u.ch(x).mapMat(f) | |
def xz[T](f: u.Out => T) = u.ch(x).map(f) | |
} | |
implicit def y[X](x: X)(implicit u: Umat[X]) = new Y(x) | |
/* | |
* All these compile successfully. Type ascriptions to lambda arguments are unfortunately | |
* required, the attempt presented in class Y above does not work. | |
*/ | |
val s1 = new Source[Int, NotUsed].xx(12).m((_: NotUsed) => "").map(_ + 12) | |
val s2: Source[Int, String] = s1 | |
val f1 = new Flow[Int, Int, NotUsed].xx(12) | |
val f2: Flow[Int, Int, NotUsed] = f1 | |
val f3 = f1.m((_: NotUsed) => 42).xy | |
val f4: Flow[Int, String, Int] = f3 | |
val subs1 = new SubSource[Int, NotUsed].xy | |
val subs2: SubSource[String, NotUsed] = subs1 | |
val subf1 = new SubFlow[Int, Int, NotUsed].xy | |
val subf2: SubFlow[Int, String, NotUsed] = subf1 | |
} |
ok I see why you have to do that... wan't even aware it was possible to write that in this way and make it compile ;)
You're encountering the exact issues that Miles has been trying to solve (and others & me too)...
in UMat
, you do not manipulate a R[_]
but a R[_, _]
and as soon as you try to fix one of the type params to obtain a R[_]
, scalac isn't able to unify types and fails...
The solution of Miles is to force scalac to "reify" types R[_]
using Unapply (as you've done) plus Aux
technique (to clearly reify types) plus a macro to brute-force scalac to become aware of type unification.
Have a look at Generic1
in shapeless, this is where those tricks come from...
That's what I've done in a simpler way in Precepte too https://github.com/MfgLabs/precepte/blob/master/precepte-core-cats/src/main/scala/HackSI2712.scala#L39 ... I know what I'm looking for so the macro is much simpler...
This could work in your case too as you know you're manipulating Source/Flow/Sink
...
But if scalac could do that in a generic way, it would help so many of us that now use higher-kinds a lot in their code :D
fixed compilation errors and removed Akka dependency