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.
Related
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 }
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
def isEven(x: Int) =
if (x % 2 == 0) x.successNel else "not even".failureNel
In the above code, the return type of isEven is inferred as scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]
Whereas explicitly specifying the return type works,
def isEven(x: Int): ValidationNel[String, Int] =
if (x % 2 == 0) x.successNel else "not even".failureNel
Can someone explain what's going on behind the scenes here? Is this a limitation of Scala's type inference system?
I'm trying to use the above function in the following expression,
(isEven(4) |#| isEven(6) |#| isEven(8) |#| isEven(10)) {_ + _ + _ + _}
and only the second variant (with explicit return type) works.
scalaz.NonEmptyList[String] is a sub type of scalaz.NonEmptyList[_ <: String], meaning scalaz.ValidationNEL[String,Int] is a sub type of scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int]. The compiler just give you a more precise type in this case...
Edit: the raisin bread operator requires an Applicative instance. In this case I suppose that scalaz provide such instance for ValidationNel[String, ?], but not for the more precise type inferred for your isEven.
A simpler manifestation of that can be observed with Option:
val some = Some(1)
val none = None
(some |#| none)(_ + _) // Cannot find implicit Applicative[Some] :(
You can view that as an intersect limitation of local type inference. You could also say that FP doesn't always play well with OO. I would suggest following good practices and annotate all your public method and implicits with explicit types.
I will try to explain more details about :
why scalaz.Validation[scalaz.NonEmptyList[_ <: String],Int] this can not work with |#|?
isEven(4) |#| isEven(6) ...
First of all isEven(4) is trying to implicitly convert it from Validation[+E, +A] type to ApplyOps[F0.M,F0.A]type with |#| operator. as the implicit method in ApplySyntax:
implicit def ToApplyOpsUnapply[FA](v: FA)(implicit F0: Unapply[Apply, FA]) =
new ApplyOps[F0.M,F0.A](F0(v))(F0.TC)
As the above code, for this implicit conversion, we also need an implicitly F0: Unapply[Apply, FA] variable. for UnApply implicits, it's in the UnApply:
implicit def unapplyMFA[TC[_[_]], M0[_[_], _], F0[_], A0](implicit TC0: TC[M0[F0, ?]]): Unapply[TC, M0[F0, A0]] {
type M[X] = M0[F0, X]
type A = A0
} =
new Unapply[TC, M0[F0, A0]] {
type M[X] = M0[F0, X]
type A = A0
def TC = TC0
def leibniz = refl
}
As the Validation type, it's using the unapplyMFA implicits variable. In there we also found it's looking for another implicits TC0: TC[M0[F0, ?]] variable. as the before ToApplyOpsUnapply, In there TC0 will be Apply type, that's also can be Applicative type. so it will try to look for the Validation to Applicative implicits, it's in the Validation.scala
implicit def ValidationApplicative[L: Semigroup]: Applicative[Validation[L, ?]] =
new Applicative[Validation[L, ?]] {
override def map[A, B](fa: Validation[L, A])(f: A => B) =
fa map f
def point[A](a: => A) =
Success(a)
def ap[A, B](fa: => Validation[L, A])(f: => Validation[L, A => B]) =
fa ap f
}
for the Semigroup definition:
trait Semigroup[F] { self => //F is invariant, unlike Option[+A]
So the problem is: F is type invariant, not like the Option[+A], compiler could not find an appropriate Applicative implicits for this type Validaiton.
There is a small demo for how to enable type variant for this:
def isEven(x: Int): Validation[NonEmptyList[_ <: String], Int] =
if (x % 2 == 0) x.successNel else "not even".failureNel
val res: String = foo(isEven(4))
def foo[FA](v: FA)(implicit F0: Unapply[Apply, FA]): String = "foo bar"
implicit def ValidationApplicative[L]: Applicative[Validation[L, ?]] =
new Applicative[Validation[L, ?]] {
override def map[A, B](fa: Validation[L, A])(f: A => B) =
fa map f
def point[A](a: => A) =
Success(a)
def ap[A, B](fa: => Validation[L, A])(f: => Validation[L, A => B]) =
//fa ap f
null
}
In there we just unbind L from Semigroup, so now this is type variant. just for fun;).
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))
The general goal:
Suppose for instance I want to develop a very pluggable issue tracker. Its core implementation might only support a ticket id and a description. Other extensions might add support for various other fields, yet those fields might exist in the database on the same table. Even if not, the number of queries to the database should not need to increase along with the number of extensions. They should be able to contribute to the definition of the query.
Item[A, B, R[_]] would represent a column, with A for the table type (has the column representations), B as the data type, and R as the type constructor representing a column of type B. So R[B] might be ScalaQuery's NamedColumn[String], for example.
Right now I'm trying to make a typeclass to handle building the "query".
The problem:
The line starting with val q (at the end) should read simply val q = query(items) and still compile. Various attempts yield either an error that inferred type arguments don't conform to expected type arguments, due to defaultNext inferring B0 and/or B to Nothing, or a "diverging implicit expansion" error, or other errors. I think the implicit error is triggered by incorrect type inference though.
I've already wasted quite a few days on this (it's for an open-source project of mine) so if someone could kindly help out I would really really appreciate it.
class CanBuildQuery[A, B, R[_], Q, I <: Item[A, B, R]](val apply: I => A => Q)
trait Low {
implicit def defaultNext[A, B, R[_], B0, P <: Item[A, B0, R], I <: NextItem[A, B, B0, R, P], PQ](
implicit cbq: CanBuildQuery[A, B0, R, PQ, P]
): CanBuildQuery[A, B, R, (PQ, R[B]), I] =
new CanBuildQuery[A, B, R, (PQ, R[B]), I](sys.error("todo"))
}
object CanBuildQuery extends Low {
implicit def defaultFirst[A, B, R[_]]:
CanBuildQuery[A, B, R, R[B], FirstItem[A, B, R]] =
new CanBuildQuery[A, B, R, R[B], FirstItem[A, B, R]](_.get)
}
def query[A, B, R[_], Q, I <: Item[A, B, R]](
i: I with Item[A, B, R]
)(
implicit cbq: CanBuildQuery[A, B, R, Q, I]
): A => Q =
cbq apply i
trait Item[A, B, +R[_]] {
def get: A => R[B]
}
trait FirstItem[A, B, +R[_]] extends Item[A, B, R] {
def get: A => R[B]
}
trait NextItem[A, B, B0, +R[_], I <: Item[A, B0, R]] extends Item[A, B, R] {
val prev: I
def get: A => R[B]
}
val items =
new NextItem[Boolean, String, Long, Option, FirstItem[Boolean, Long, Option]]{
val get = { _:Boolean => "hello" }
val prev = new FirstItem[Boolean, Long, Option] {
val get = { _:Boolean => 73 }
}
}
val q = query(items)(CanBuildQuery.defaultNext(CanBuildQuery.defaultFirst))
With G-d's help, including some insights and suggestions from Josh Seureth, I got it to work:
trait Item[A] {
type B
type R[_]
def get: A => R[B]
}
object Item {
def apply[A, B, R[_]](get: A => R[B])(render: B => String => String) = {
val get0 = get
type B0 = B
type R0[T] = R[T]
new FirstItem[A] {
type B = B0
type R[T] = R0[T]
def get = get0
}
}
}
trait FirstItem[A] extends Item[A] {
type B
def get: A => R[B]
def &(item: Item[A]) =
new NextItem[A] {
type P = FirstItem.this.type
type B = item.B
type R[T] = item.R[T]
val prev = FirstItem.this: FirstItem.this.type
def get = item.get
}
}
trait NextItem[A] extends Item[A] {
type B
type P <: Item[A]
type _P = P
val prev: P
def get: A => R[B]
def &(item: Item[A]) =
new NextItem[A] {
type P = NextItem.this.type
type B = item.B
type R[T] = item.R[T]
val prev = NextItem.this: NextItem.this.type
def get = item.get
}
}
class CanBuildQuery[A, +Q, -I](val apply: I => A => Q)
class CanBuildQueryImplicits {
def apply[A, ]
implicit def defaultNext[A, I <: NextItem[A], PQ](implicit cbq: CanBuildQuery[A, PQ, I#P]): CanBuildQuery[A, (PQ, I#R[I#B]), I] =
new CanBuildQuery[A, (PQ, I#R[I#B]), I](ni => a => query(ni.prev)(cbq)(a) -> ni.get(a).asInstanceOf[I#R[I#B]])
implicit def defaultFirst[A, B, I <: FirstItem[A]]: CanBuildQuery[A, I#R[I#B], I] =
new CanBuildQuery[A, I#R[I#B], I](i => i.get.asInstanceOf[A => I#R[I#B]])
}
def query[A, Q, I <: Item[A]](i: I with Item[A])(implicit cbq: CanBuildQuery[A, Q, I]): A => Q = cbq apply i
}
val items =
Item((_: Field.type).id)(x => _ + " " + x) &
Item((_: Field.type).name)(x => _ + " " + x) &
Item((_: Field.type).allowMultiple)(x => _ + " " + x)
val q = query(items) apply Field
println(q)