Covariance between 3 types in Scala - scala

I'm trying to see if there is a way to find a type W2 that is the super type of 2 types W and E.
In my solution E represents errors, and W represents Warnings.
What I'm trying to accomplish is a method or that if this fails runs that and moves the error to the warning type.
Here is a simplified example of what I'm doing.
sealed trait Validator[-I, +E, +W, +A] extends Product with Serializable
this type has several cases, which aren't really important here, so instead I'll go over an example usage:
case class MyObj(coords: GeoCoords)
case class GeoCoords(lat: String, long: String)
// Getters
val getLatitude: Validator[GeoCoords, Nothing, Nothing, String] = from[GeoCoords].map(_.lat)
val getLongitude: Validator[GeoCoords, Nothing, Nothing, String] = from[GeoCoords].map(_.long)
val parseLatitude: Validator[GeoCoords, Exception, Nothing, Double] = getLatitude andThen nonEmptyString andThen convertToDouble
val parseLongitude: Validator[GeoCoords, Exception, Nothing, Double] = getLongitude andThen convertToDouble
Now this is a bad example, but what I'm looking to do is that because parseLatitude has an error type of Exception, perhaps I want to give a default instead, but still understand that it failed. I'd like to move the Exception from the E error param to the W warning param like this:
val defaultLatitude: Validator[GeoCoords, Nothing, Nothing, Double] = success(0)
val finalLatitude: Validator[GeoCoords, Nothing, Exception, Double] = parseLatitude or defaultLatitude
But as well, if instead of supplying default, the other action I take after the or may fail as well, therefore this should also be the case:
val otherAction: Validator[GeoCoords, Throwable, Nothing, Double] = ???
val finalLatitude: Validator[GeoCoords, Throwable, Exception, Double] = parseLatitude or otherAction
I've tried implementing or in several ways on the Validator type but everytime it gives me an issue, basically casting things all the way up to Any.
def or[I2 <: I, E2 >: E, W2 >: W, B >: A](that: Validator[I2, E2, W2, B]): Validator[I2, E2, W2, B] = Validator.conversion { (i: I2) =>
val aVal: Validator[Any, E, W, A] = this.run(i)
val bVal: Validator[Any, E2, W2, B] = that.run(i)
val a: Vector[W2] = aVal.warnings ++ bVal.warnings
// PROBLEM HERE
val b: Vector[Any] = a ++ aVal.errors
Result(
aVal.warnings ++ aVal.errors ++ bVal.warnings,
bVal.value.toRight(bVal.errors)
)
}
I need to be able to say that W2 is both a supertype of W and of E so that I can concatenate the Vectors together and get type W2 at the end.
A super simplified self contained example is:
case class Example[+A, +B](a: List[A], b: List[B]) {
def or[A2 >: A, B2 >: B](that: Example[A2, B2]): Example[A2, Any] = {
Example(that.a, this.a ++ this.b ++ that.a)
}
}
object stackoverflow extends App {
val example1 = Example(List(1, 2), List(3, 4))
val example2 = Example(List(5, 6), List(7, 8))
val example3 = example1 or example2
}
Where I want the output type of or to be Example[A2, B2] instead of Example[A2, Any]

Actually you want the concatenation of a List[A] and a List[B] to produce a List[A | B], where A | B is union type.
How to define "type disjunction" (union types)?
In Scala 2 union types are absent but we can emulate them with type classes.
So regarding "super simplified example" try a type class LUB (least upper bound)
case class Example[A, B](a: List[A], b: List[B]) {
def or[A2 >: A, B2, AB](that: Example[A2, B2])(
implicit
lub: LUB.Aux[A, B, AB],
lub1: LUB[AB, A2]
): Example[A2, lub1.Out] = {
Example(that.a, (this.a.map(lub.coerce1) ++ this.b.map(lub.coerce2)).map(lub1.coerce1(_)) ++ that.a.map(lub1.coerce2(_)))
}
}
val example1: Example[Int, Int] = Example(List(1, 2), List(3, 4))
val example2: Example[Int, Int] = Example(List(5, 6), List(7, 8))
val example3 = example1 or example2
// example3: Example[Int, Any] // doesn't compile
example3: Example[Int, Int] // compiles
trait LUB[A, B] {
type Out
def coerce1(a: A): Out
def coerce2(b: B): Out
}
trait LowPriorityLUB {
type Aux[A, B, Out0] = LUB[A, B] { type Out = Out0 }
def instance[A, B, Out0](f: (A => Out0, B => Out0)): Aux[A, B, Out0] = new LUB[A, B] {
override type Out = Out0
override def coerce1(a: A): Out0 = f._1(a)
override def coerce2(b: B): Out0 = f._2(b)
}
implicit def bSubtypeA[A, B <: A]: Aux[A, B, A] = instance(identity, identity)
}
object LUB extends LowPriorityLUB {
implicit def aSubtypeB[A <: B, B]: Aux[A, B, B] = instance(identity, identity)
implicit def default[A, B](implicit ev: A <:!< B, ev1: B <:!< A): Aux[A, B, Any] =
instance(identity, identity)
}
// Testing:
implicitly[LUB.Aux[Int, AnyVal, AnyVal]]
implicitly[LUB.Aux[AnyVal, Int, AnyVal]]
implicitly[LUB.Aux[Int, String, Any]]
implicitly[LUB.Aux[Int, Int, Int]]
// implicitly[LUB.Aux[Int, AnyVal, Any]] // doesn't compile
<:!< is from here:
Scala: Enforcing A is not a subtype of B
https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/package.scala#L48-L52
If you want Example to be covariant
case class Example[+A, +B]...
make LUB and LUB.Aux contravariant
trait LUB[-A, -B]...
type Aux[-A, -B, Out0] = LUB[A, B] { type Out = Out0 }

Related

Implicit Conversion from tuple to custom type parameterized by size of tuple

I have a type X[D <: Int] that I would like to convert to implicitly from a tuple.
Specifically a tuple of length D whose members are all Int should be convertable to an X[D].
I am not able to write a Conversion[A, B] to properly perform this.
Scastie playground with my attempt
https://scastie.scala-lang.org/R3BEymtWSaqTCtDTsirTKw
import scala.language.implicitConversions
// tuple types
type TupleAllAre[A] = [T <: Tuple] =>> T =:= TupleElementsAre[T, A]
type TupleElementsAre[T <: Tuple, A] <: Tuple = T match
case A *: rest => A *: TupleElementsAre[rest, A]
case EmptyTuple => EmptyTuple
// tuple types work
val s1 = summon[TupleAllAre[Int][(Int, Int)]]
// Expected error
// val s2 = summon[TupleAllAre[Int][(Int, String, Int)]]
// target classes / methods
class X[D <: Int](x: Seq[Int]):
def size: Int = x.size
def definition[D <: Int](x: X[D]): Unit = ???
def def_with_conversion[T <: Tuple: TupleAllAre[Int]] (tuple: T) (using c: Conversion[T, X[Tuple.Size[T]]]): X[Tuple.Size[T]] = c(tuple)
class M[D <: Int]:
def apply(x: X[D]): Unit = ???
val m1 = M[2]()
extension [T <: Tuple: TupleAllAre[Int]] (tuple: T)
def ext(using c: Conversion[T, X[Tuple.Size[T]]]): X[Tuple.Size[T]] = c(tuple)
// conversion
given [T <: Tuple: TupleAllAre[Int]]: Conversion[T, X[Tuple.Size[T]]] = ???
// works with a non generic conversion
// given Conversion[(Int, Int), X[2]] = ???
// should all work
val x1: X[2] = (16, 16)
val d1 = definition[2](2, 2)
val d2: X[2] = def_with_conversion(2, 2)
val a1 = m1(2, 2)
val e1: X[2] = (2, 2).ext
If what you are trying to achieve is to have a type that X[D] that represent a tuple of D elements all Integer this is a way to achieve it:
import scala.compiletime.ops.int.*
type S = Singleton & Int
type X[D <: S] = D match
case 0 => EmptyTuple
case _ => Int *: X[-[D, 1]]
val x: X[2] = (1,2)
def definition[D <: S](x: X[D]): Unit = ()
val x1: X[2] = (16, 16)
println(x1)
val d1 = definition[2]((2, 2))
https://scastie.scala-lang.org/alfonsorr/pk2Avg4YToKaWtKUKxinJA

Scala compiler expand types

Consider this code:
trait TypeOr[E, F] {
type T
}
implicit def noneq2[E, F](implicit ev: E =!= F): TypeOr[E, F] = new TypeOr[E, F] {
type T = (E, F)
}
sealed trait Error[+E, +A]
case class Err[E, A](e: Error[E, A]) {
def combine[B, F](f: A => Error[F, B])(implicit ev: TypeOr[E, F]): Error[ev.T, B] = ???
}
val result = Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
So far so good. From the definitions above, I concluded, that the expanded type of the result is following:
val itsType: Error[(Int, String), String] = result
But apparently it is not, since the compiler replies with:
found : returnerror.Comb.Error[returnerror.Comb.TypeOr[Int,String]#T,String]
required: returnerror.Comb.Error[(Int, String),String]
val itsType: Error[(Int, String), String] = result
Is it possible to find out the simplified - expanded type of the expression? I can't get this information from compiler, I tried to print the AST before the erasure phase, but the expanded type is still not there.
Firstly, when you write that implicit noneq2 has type TypeOr[E, F] you lost type refinement https://typelevel.org/blog/2015/07/19/forget-refinement-aux.html . Correct is
implicit def noneq2[E, F](implicit ev: E =:!= F) = new TypeOr[E, F] {
type T = (E, F)
}
or better with explicit type
implicit def noneq2[E, F](implicit ev: E =:!= F): TypeOr[E, F] { type T = (E, F) } = new TypeOr[E, F] {
type T = (E, F)
}
That's the reason why usually type Aux is introduced
object TypeOr {
type Aux[E, F, T0] = TypeOr[E, F] { type T = T0 }
implicit def noneq2[E, F](implicit ev: E =:!= F): Aux[E, F, (E, F)] = new TypeOr[E, F] {
type T = (E, F)
}
}
Secondly, automatically inferred type of result i.e.Error[TypeOr[Int, String]#T, String] (type projection TypeOr[Int,String]#T is a supertype of (y.T forSome { val y: TypeOr[Int, String] }) and moreover of x.T) is too rough https://typelevel.org/blog/2015/07/23/type-projection.html
It's better to write path-dependent type for result.
But
val x = implicitly[TypeOr[Int, String]]
val result: Error[x.T, String] =
Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
doesn't compile.
The thing is that implicitly can damage type refinements https://typelevel.org/blog/2014/01/18/implicitly_existential.html
That's the reason why there exists macro shapeless.the.
val x = the[TypeOr[Int, String]]
val result: Error[x.T, String] = Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
val itsType: Error[(Int, String), String] = result
Alternatively, custom materializer can be defined
object TypeOr {
//...
def apply[E, F](implicit typeOr: TypeOr[E, F]): Aux[E, F, typeOr.T] = typeOr
}
val x = TypeOr[Int, String]
val result: Error[x.T, String] =
Err(null.asInstanceOf[Error[Int, Int]]).combine(_ => null.asInstanceOf[Error[String, String]])
val itsType: Error[(Int, String), String] = result

Scala: abstracting over a path-dependent type in impilicit parameter

Let's say I have a class:
abstract class NumericCombine[A:Numeric,B:Numeric]{
type AB <: AnyVal
}
I want to define a function that returns a value of type NumericCombine[A,B].AB. for instance:
def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B].AB
but the compiler doesn't let me reference .AB in plus.
FYI, this is the context of this question.
I want to provide:
implicit object IntFloat extends NumericCombine[Int,Float]{override type AB = Float}
implicit object FloatInt extends NumericCombine[Float,Int]{override type AB = Float}
and its other 44 friends (7*6-2) so that I can define my plus as below:
def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B].AB =
{
type AB = Numeric[NumericCombine[A,B].AB]
implicitly[AB].plus(x.asInstanceOf[AB],y.asInstanceOf[AB])
}
plus(1f,2)//=3f
plus(1,2f)//=3f
I am aware of the fact that value conversions in Scala allows me to define
def plus[T](a: T, b: T)(implicit ev:Numeric[T]): T = ev.plus(a,b)
and achieve the behaviour above as suggested here, but since I want to use this function as part of a bigger function (which is described in the link mentioned as the context of this question), I need to parametrize the function with both A and B.
Update:
I made some good progress with this.
My NumericCombine now looks like this:
abstract class NumericCombine[A: Numeric, B: Numeric] {
type AB <: AnyVal
def fromA(x: A): AB
def fromB(y: B): AB
val numeric: Numeric[AB]
def plus(x: A, y: B): AB = numeric.plus(fromA(x), fromB(y))
def minus(x: A, y: B): AB = numeric.minus(fromA(x), fromB(y))
def times(x: A, y: B): AB = numeric.times(fromA(x), fromB(y))
}
and My plus function looks like:
def plus[A: Numeric, B: Numeric](x: A, y: B)(implicit ev:NumericCombine[A,B])
: ev.AB = ev.plus(x, y)
The weighted average function requiring plus ended up becoming a bit more complicated:
def accumulateWeightedValue[A: Numeric,B: Numeric]
(accum: (A, NumericCombine[A, B]#AB), ValueWithWeight: (A, B))
(implicit combine: NumericCombine[A, B], timesNumeric: Numeric[NumericCombine[A, B]#AB])
:(A,NumericCombine[A, B]#AB)=
this is a function that takes (A,AB),(A,B) and returns (A,AB). I use it internally inside weightedSum which just aggregates over this:
def weightedSum[A: Numeric,B: Numeric](weightedValues: GenTraversable[(A, B)])
(implicit numericCombine: NumericCombine[A, B], plusNumeric: Numeric[NumericCombine[A, B]#AB])
: (A, NumericCombine[A, B]#AB)
Now, this compiles fine. It does seem to have a problem with the second implicit parameter. ie Numeric[AB] when I run it with implicit values for say NumericCombine[Int,Float] present. It gives me:
could not find implicit value for parameter plusNumeric:
Numeric[NumericCombine[Int,Float]#AB]
note that in NumericCombine, I have a Numeric[AB] which should be available for implicit look-up. storing it locally, in the case of [Int,Float]:
val lst: Seq[(Int, Float)] =List((1,3f),(1,4f))
implicit val num: Numeric[Float] = IntFloat.numeric //IntFloat extends NumericCombine[Int,Float]
weightedSum(lst)
in a local variable before invoking the function needing it doesn't seem to have any impact. So why is it being picked up by the implicit system.
Just use
def plus[A: Numeric,B:Numeric](x: A, y: B): NumericCombine[A,B]#AB
Note the # (hash) instead of . (dot). This is called "type projection". Dot notation is called "path dependent type". I'm telling you these names so that you can google for more info easily. Simply put, # is used for accessing types from classes/traits, and . is used for accessing types from objects/values.
Example:
trait Foo {
type T
}
val fooObj: Foo = new Foo {
type T = Int
}
type t1 = fooObj.T
type t2 = Foo#T
* 18 Apr 2017: updated base on the latest code from author *
* 19 Apr 2017 *
Add NumericCombine#Implicits for convinence
Remove AnyVal constraints to support any Numeric types e.g. BigInt
Refactor NumericCombine
You need Aux pattern:
import scala.collection.GenSeq
trait NumericCombine[A, B] {
type AB
def fromA(x: A): AB
def fromB(y: B): AB
val numericA: Numeric[A]
val numericB: Numeric[B]
val numericAB: Numeric[AB]
// For convenience, caller can 'import combine.Implicits._'
// to bring the Numeric's into the current scope
object Implicits {
implicit def implicitNumericA = numericA
implicit def implicitNumericB = numericB
implicit def implicitNumericAB = numericAB
}
def plus(x: A, y: B): AB = numericAB.plus(fromA(x), fromB(y))
def minus(x: A, y: B): AB = numericAB.minus(fromA(x), fromB(y))
def times(x: A, y: B): AB = numericAB.times(fromA(x), fromB(y))
}
object NumericCombine {
type Aux[A, B, _AB] = NumericCombine[A, B] {
type AB = _AB
}
private def combine[A, B, _AB](fa: A => _AB, fb: B => _AB)
(implicit
_numericA: Numeric[A],
_numericB: Numeric[B],
_numericAB: Numeric[_AB]
): NumericCombine[A, B] = new NumericCombine[A, B] {
override type AB = _AB
override def fromA(x: A): AB = fa(x)
override def fromB(y: B): AB = fb(y)
override val numericA: Numeric[A] = _numericA
override val numericB: Numeric[B] = _numericB
override val numericAB: Numeric[AB] = _numericAB
}
implicit lazy val IntFloat = combine[Int, Float, Float](_.toFloat, identity)
implicit lazy val BigIntBigDecimal = combine[BigInt, BigDecimal, BigDecimal](i => BigDecimal(i), identity)
}
implicit class ValuesWithWeight[A, B](val weightedValue: (A, B)) {
def weight: A = weightedValue._1
def value: B = weightedValue._2
}
def weightedSum[A, B, AB]
(valuesWithWeight: GenSeq[(A, B)])
(implicit combine: NumericCombine.Aux[A, B, AB]):
(A, AB) = {
import combine.Implicits._
val z: (A, AB) =
(combine.numericA.zero, combine.numericAB.zero)
def accumulateWeightedValue(accum: (A, AB), valueWithWeight: (A, B)): (A, AB) = {
val weightedValue = combine.times(valueWithWeight.weight, valueWithWeight.value)
(
combine.numericA.plus(accum.weight, valueWithWeight.weight),
combine.numericAB.plus(accum.value, weightedValue)
)
}
valuesWithWeight.aggregate(z)(
accumulateWeightedValue,
// dataOps.tuple2.plus[A,AB]
{
case ((a1, ab1), (a2, ab2)) =>
(combine.numericA.plus(a1, a2) ->
combine.numericAB.plus(ab1, ab2))
}
)
}
weightedSum(Seq(1 -> 1.5f, 2 -> 1f, 3 -> 1.7f))
weightedSum(Seq(BigInt(1) -> BigDecimal("1.5"), BigInt(2) -> BigDecimal("1"), BigInt(3) -> BigDecimal("1.7")))
An alternative to #slouc's answer is
def plus[A, B](x: A, y: B)(implicit ev: NumericCombine[A, B]): ev.AB
I'd also enhance NumericCombine:
trait NumericCombine[A, B] {
type AB <: AnyVal
def fromA(a: A): AB
def fromB(b: B): AB
val num: Numeric[AB]
}
abstract class NumericCombineImpl[A, B, R](implicit val num: Numeric[R], f1: A => R, f2: B => R) {
type AB = R
def fromA(a: A) = f1(a)
def fromB(b: B) = f2(b)
}
implicit object IntFloat extends NumericCombineImpl[Int,Float,Float]
...
This would allow to actually implement plus, no casts required:
def plus[A, B](x: A, y: B)(implicit ev: NumericCombine[A, B]): ev.AB =
ev.num.plus(ev.fromA(x), ev.fromB(y))

Scala upper type bound

class P(name: String)
class E(_name: String, role: String) extends P(_name)
def testF[T <: P](x: List[T]): List[T] = x
val le = List(new E("Henry", "Boss"))
class Test[R <: E](l: List[R]) {
def r[O <: P] (): List[O] = testF(l)
}
I get:
Error:(8, 38) type mismatch;
found : List[R]
required: List[O]
def r[O <: P] (): List[O] = testF(l)
My intuition suggests that this should have worked because T has a tighter upper type bound than O.
**** EDIT ****
def findNN[A, B <: A, C <: A, T] (seq: Seq[B], n: Int, center: C, distance: (A, A) => T)
(implicit ord: Ordering[T]): Seq[B] = {
import ord._
val ds = seq map ( (a: A) => distance(a, center))
val uds = ds.distinct
//#TODO: replace quickSelect with median-of algorithm if necessary
val kel = quickSelect(uds, n)
val z = seq zip ds
val (left, _) = z partition Function.tupled((_, d: T) => d <= kel)
left map {t => t._1}
}
OK, let's have a look at the example above.
The superclass A provides the method distance.
I would like to use the function findNN on a seq[B] having a center in a class C. distance should work because it works on A
Based on feedback provided, there's no way to simplify the type signatures above.
You made misprinting, you need >: rather then :<
class P(name: String)
class E(_name: String, role: String) extends P(_name)
def testF[T >: P](): List[T] = List(new P("Henry"))
You are trying to limit the type of the result using a type parameter R (with an upper bound type E), while you are not using the type R in your function.
An example of a correct use of a type parameter (with an upper bound):
def testF[T <: P](list: List[T]): List[T] = list
testF(List(new P("Tom")))
// List[P] = List(P#43bc21f0)
testF(List(new E("Jerry", "Mouse")))
// List[E] = List(E#341c1e65)
An incorrect use of a type parameter:
// does not compile, what is A ??
def foo[A]: List[A] = List("bar")

Iso macro in Scala

If I want to implicitly convert two objects from one to another, is there anyway to do this using something like an Iso macro?
For example, if I have this:
implicit def listToMap[A, B](l: List[(A, B)]): Map[A, B] = l.toMap
implicit def mapToList[A, B](m: Map[A, B]): List[(A, B)] = m.toList
I want to simply write:
implicit def[A, B] listMapIso = Iso[List[(A, B)], Map[A, B]] {_.toMap, _.toList}
Note: As noted below, I plan to use this in my web framework where I convert my database models to middleware/front-end models.
You seem to be confusing several different concepts. Iso, implicit conversions, and macros are all quite different from each other.
We can certainly define an equivalent of Iso for parameterized types, though the syntax becomes a little more cumbersome:
import scalaz._, Scalaz._
case class BiIso[F[_, _], G[_, _]](left: F ~~> G,
right: G ~~> F)
type PairList[A, B] = List[(A, B)]
val listToMap = new (PairList ~~> Map) {
def apply[A, B](l: PairList[A, B]) = l.toMap
}
val mapToList = new (Map ~~> PairList) {
def apply[A, B](m: Map[A, B]) = m.toList
}
val listMapIso = BiIso(listToMap, mapToList)
We can of course make parts of this implicit, though this is an orthogonal concern. We can build the BiIso implicitly:
implicit val listToMap = new (PairList ~~> Map) {
def apply[A, B](l: PairList[A, B]) = l.toMap
}
implicit val mapToList = new (Map ~~> PairList) {
def apply[A, B](m: Map[A, B]) = m.toList
}
implicit def biIso[F[_, _], G[_, _]](implicit left: F ~~> G, right: G ~~> F) =
BiIso(left, right)
implicitly[BiIso[PairList, Map]]
And we can make any BiIso act as an implicit conversion, though I would recommend against it. The only tricky part is guiding the type inference correctly. This is most of the way there, but for some reason the GAB parameter isn't inferred (a correction would be very welcome):
sealed trait BiAny[F[_, _]] {}
object BiAny {
implicit def any[F[_, _]] = new BiAny[F] {}
}
sealed trait ApplyBiIso[FAB, GAB] {
type A1
type B1
type F[_, _]
type G[_, _]
type Required = BiIso[F, G]
val unapplyL: Unapply2[BiAny, FAB] {
type A = A1; type B = B1;
type M[C, D] = F[C, D]
}
val unapplyR: Unapply2[BiAny, GAB] {
type A = A1; type B = B1;
type M[C, D] = G[C, D]
}
def liftBI(bi: Required): Iso[FAB, GAB] =
Iso({ fab: FAB =>
val f: F[A1, B1] = Leibniz.witness(unapplyL.leibniz)(fab)
val g: G[A1, B1] = bi.left(f)
Leibniz.witness(Leibniz.symm[⊥, ⊤, GAB, G[A1, B1]](unapplyR.leibniz))(g): GAB
},
{ gab: GAB =>
val g: G[A1, B1] = Leibniz.witness(unapplyR.leibniz)(gab)
val f: F[A1, B1] = bi.right(g)
Leibniz.witness(Leibniz.symm[⊥, ⊤, FAB, F[A1, B1]](unapplyL.leibniz))(f): FAB
}
)
}
object ApplyBiIso {
implicit def forFG[FAB, A2, B2, GAB, A3, B3](
implicit u1: Unapply2[BiAny, FAB] { type A = A2; type B = B2 },
u2: Unapply2[BiAny, GAB] { type A = A3; type B = B3 }) = new ApplyBiIso[FAB, GAB] {
type A1 = A2
type B1 = B2
type F[C, D] = u1.M[C, D]
type G[C, D] = u2.M[C, D]
//Should do the conversion properly with Leibniz but I can't be bothered
val unapplyL = u1.asInstanceOf[Unapply2[BiAny, FAB] {
type A = A1; type B = B1;
type M[C, D] = F[C, D]
}]
val unapplyR = u2.asInstanceOf[Unapply2[BiAny, GAB] {
type A = A1; type B = B1;
type M[C, D] = G[C, D]
}]
}
type Aux[FAB, GAB, Required1] = ApplyBiIso[FAB, GAB] { type Required = Required1 }
def apply[FAB, GAB](implicit abi: ApplyBiIso[FAB, GAB]): Aux[FAB, GAB, abi.Required] = abi
}
sealed trait AppliedBiIso[FAB, GAB] {
val iso: Iso[FAB, GAB]
}
object AppliedBiIso {
implicit def applyAndIso[FAB, GAB, Required1](
implicit ap: ApplyBiIso.Aux[FAB, GAB, Required1],
iso1: Required1) = new AppliedBiIso[FAB, GAB] {
//Should do the conversion properly with Leibniz but I can't be bothered
val iso = ap.liftBI(iso1.asInstanceOf[BiIso[ap.F, ap.G]])
}
}
implicit def biIsoConvert[FAB, GAB](
f: FAB)(implicit ap: AppliedBiIso[FAB, GAB]): GAB =
ap.iso.left(f)
val map: Map[String, Int] = Map("Hello" -> 4)
val list: PairList[String, Int] =
biIsoConvert[Map[String, Int], PairList[String, Int]](map)
I've no doubt it's possible to make this work correctly.
That still leaves macros, which are again a more or less orthogonal concern. One place I can see they might be relevant is that it's impossible to abstract over kind in scala without using macros. Do you want an equivalent of Iso that works for any "shape", not just F[_, _]? That would be a good use case for a macro - though having written this kind of macro before I don't envy anyone trying to implement it.