Continuing on from a previous question of mine, I am attempting to implement Scrap Your Boilerplate in scala 3 and am running into an issue now with the mkT function described in the paper. Given the following definition of cast:
trait Cast[A, B]:
def apply(a: A): Option[B]
object Cast:
given cSome[A, B](using t: A =:= B): Cast[A, B] with
def apply(a: A) = Some(t(a))
given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] with
def apply(a: A) = None
def cast[A, B](a: A)(using c: Cast[A, B]): Option[B] = c(a)
I have tried to make mkT as follows:
class MakeTransform[A] (val f: A => A) {
def apply[B](b: B)(using c: Cast[A => A, B => B]): B = c(f) match {
case Some(fb) => fb(b)
case _ => b
}
}
def mkT[A](f: A => A): MakeTransform[A] = MakeTransform(f)
And this seems to work fine with the boolean example:
def not(a: Boolean): Boolean = !a
mkT(not)(true) // false, function is clearly called on input value
mkT(not)('a') // 'a'
However, when I try it with the company model objects, I can only get it to function as expected when I provide an explicit type call and the parameter matches that type. So given the following Salary definition:
sealed trait Salary
case class S(amt: Float) extends Salary
def incS(amt: Float): Salary => Salary = {
case S(a) => S(a * (1 + amt))
}
val ralf: Employee = E(P("Ralf", "Amsterdam"), S(8000))
I attempt to raise a Salary:
inc(.1)(S(8000)) // S(8000) <= no change
Unless, however, I am explicit with the type:
inc(.1)[Salary](S(8000)) // S(8800.0)
But when I do that, I can only pass objects of the specified type as input:
inc(.1)[Salary](ralf) // does not compile
which obviously defeats the purpose.
My thought was, that because MakeTransform's apply method takes a type parameter, that the input type would be inferred by the value passed to it, but that doesn't seem to always be the case. Even more baffling to me is the inconsistent behavior between the Boolean and Salary examples. Any ideas why? Also, while debugging things like this, is there a way to see what types are being inferred? The debugger shows the runtime type of the variables, but it would be helpful if there was a way to see what type parameters are at runtime.
UPDATE: new thought, does this have to do with the fact that S <: Salary and not S =:= Salary?
You seem to again miss an implicit parameter (constraint in Haskell terms)
inc :: Typeable a => Float -> a -> a
-- ^^^^^^^^^^
inc k = mkT (incS k)
Confer
def inc[A](amt: Float): A => A = mkT(incS(amt))(_)
inc(.1)(S(8000)) // S(8000.0) -- not changed
with
def inc[A](amt: Float)(using c: Cast[Salary => Salary, A => A]): A => A = mkT(incS(amt))(_)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
inc(.1)(S(8000)) // S(8800.0) -- changed
The whole code
https://scastie.scala-lang.org/DmytroMitin/v82LGbOtRieGmJX7gCb99A/1
Regarding debugging you can switch on
scalacOptions ++= Seq("-Xprint:typer", "-Xprint-types")
in build.sbt.
Your mkT looks quite different from what's in the paper. Here is my take at it:
import util.NotGiven
case class Cast[A, B](ev: Option[A =:= B])
object Cast:
given cSome[A, B](using t: A =:= B): Cast[A, B] = Cast(Some(t))
given cNone[A, B](using t: NotGiven[A =:= B]): Cast[A, B] = Cast(None)
def cast[A, B](a: A)(using c: Cast[A, B]): Option[B] = c.ev.map(e => e(a))
def mkT[A, B](f: B => B)(a: A)(using c: Cast[A, B]): A =
c.ev match
case Some(aToB) => aToB.flip(f(aToB(a)))
case None => a
def not(a: Boolean): Boolean = !a
println(mkT(not)(true)) // false
println(mkT(not)('a')) // 'a'
sealed trait Salary
case class S(amt: Float) extends Salary
def incS(amt: Float): S => S = {
case S(a) => S(a * (1 + amt))
}
def inc[A](k: Float)(a: A)(using c: Cast[A, S]): A = mkT(incS(k))(a)
println(inc(.1)(S(8000))) // increased to `S(8800.0)`
println(inc(.1)('a')) // left as-is
It works just fine when you change the type of incS from Salary => Salary to S => S, because in your case, S is a subtype of Salary that's not equal to Salary.
Related
I am trying to create a function, which takes a tuple of higher-kinded types and applies a function to the types within the higher-kinded types.
In the example below, there is a trait Get[A] which is our higher-kinded type. There is also a tuple of Get's: (Get[String],Get[Int]) as well as function from (String,Int) => Person.
Scala-3 has a Match-Type called InverseMap which converts the type (Get[String], Get[Int]) into what is essentially the type (String,Int).
So the ultimate goal is to write a function which can take a tuple with any number of Get[_] types and a function whose input matches the InserveMap types and finally return a Get[_], where the wrapped type is the result of the function.
I have attempted to create a function called genericF below to show the desired behavior, though it may not be correct -- but I think it does at least show the proper intent.
case class Person(name: String, age: Int)
trait Get[A] {
def get: A
}
case class Put[A](get: A) extends Get[A]
val t: (Get[String], Get[Int]) = (Put("Bob"), Put(42))
val fPerson: (String,Int) => Person = Person.apply _
def genericF[T<:Tuple,I<:Tuple.InverseMap[T,Get],B](f: I => B, t: T): Get[B] = ???
val person: Get[Person] = genericF(fPerson, t)
I have set up a Scastie here: https://scastie.scala-lang.org/OleTraveler/QIyNHPLHQIKPv0lgsYbujA/23
Your code is almost compiling already - the only thing is that fPerson is of type (String, Int) => Person instead of ((String, Int)) => Person (taking a tuple instead of 2 separate parameters).
The solution below this one is not nice, although it is perhaps more efficient for TupleXXL's. Here's a nicer version with typeclasses (Scastie):
val fPerson: ((String, Int)) => Person = Person.apply _
opaque type Extract[GT <: Tuple, RT <: Tuple] = GT => RT
given Extract[EmptyTuple, EmptyTuple] = Predef.identity
given [A, PG <: Tuple, PR <: Tuple](using p: Extract[PG, PR])
as Extract[Get[A] *: PG, A *: PR] = {
case h *: t => h.get *: p(t)
}
def genericF[GT <: Tuple, RT <: Tuple, B](
f: RT => B,
t: GT
)(using extract: Extract[GT, RT]): Get[B] = Put(f(extract(t)))
Here's one way you could implement genericF using Tuple.InverseMap (note that I switched the two parameters to genericF:
val fPerson: ((String, Int)) => Person = Person.apply _
type ExtractG = [G] =>> G match {
case Get[a] => a
}
type AllGs[T <: Tuple] = T match {
case EmptyTuple => DummyImplicit
case Get[_] *: t => AllGs[t]
case _ => Nothing
}
def extract[T <: Tuple](t: T)(using AllGs[T]): Tuple.InverseMap[T, Get] =
t.map {
[G] => (g: G) => g.asInstanceOf[Get[_]].get.asInstanceOf[ExtractG[G]]
}.asInstanceOf[Tuple.InverseMap[T, Get]]
def genericF[B](
t: Tuple,
f: Tuple.InverseMap[t.type, Get] => B
)(using AllGs[t.type]): Get[B] = Put(f(extract(t)))
val person: Get[Person] = genericF(t, fPerson)
ExtractG is to make the PolyFunction compile, because it requires you apply a type constructor to its type parameter.
AllGs is to verify that the tuple consists only of Gets, because as pointed out by Dmytro Mitin, it isn't typesafe otherwise. If it's all Gets, the type becomes DummyImplicit, which Scala provides for us. Otherwise, it's Nothing. I guess it could conflict with other implicit/given Nothings in scope, but if you do have one already, you're screwed anyways.
Note that this will work only when you have Get and will need some modification if you also want it to work for tuples like (Put[String], GetSubclass[Int]).
Travis Stevens, the OP, has managed to get the solution above this one to work without creating AllGs, by using IsMappedBy. This is what they got (Scastie):
val fPerson: ((String, Int)) => Person = Person.apply _
type ExtractG = [G] =>> G match {
case Get[a] => a
}
def extract[T <: Tuple, I <: Tuple.InverseMap[T, Get]](
t: T
)(using Tuple.IsMappedBy[Get][T]): I =
t.map {
[G] => (g: G) => g.asInstanceOf[Get[_]].get.asInstanceOf[ExtractG[G]]
}.asInstanceOf[I]
def genericF[T <: Tuple, I <: Tuple.InverseMap[T, Get], B](
t: T,
f: I => B
)(using Tuple.IsMappedBy[Get][T]): Get[B] = Put(f(extract(t)))
And here's one using dependent types, just for fun (Scastie):
type Extract[T <: Tuple] <: Tuple = T match {
case EmptyTuple => EmptyTuple
case Get[a] *: t => a *: Extract[t]
}
type AllGs[T <: Tuple] = T match {
case EmptyTuple => DummyImplicit
case Get[_] *: t => AllGs[t]
case _ => Nothing
}
def genericF[T <: Tuple : AllGs, B](
t: T,
f: Extract[t.type] => B
): Get[B] = {
def extract[T <: Tuple](t: T): Extract[T] = t match {
case _: EmptyTuple => EmptyTuple
case (head *: tail): (Get[_] *: _) => head.get *: extract(tail)
}
Put(f(extract(t)))
}
I was hoping Extract wouldn't compile for tuples like (Put("foo"), 3), but unfortunately, AllGs is still necessary.
Note - the operation described below now exists in the standard library as partitionMap but I believe it's still a valid question as to how to achieve more general ends
Question regarding scala 2.13 - how do I consume/construct collections of specific types when adding custom collections operations where I need to restrict the element types of the input collections? e.g. how do I define:
def split[CC[_], A, B](coll: CC[Either[A, B]]): (CC[A], CC[B])
Following the documentation I've managed to achieve this as follows:
import collection.generic.IsIterable
import scala.collection.{BuildFrom, Factory}
class SplitOperation[Repr, S <: IsIterable[Repr]](coll: Repr, itr: S) {
def split[A, B, AS, BS](
implicit bfa: BuildFrom[Repr, A, AS],
bfb: BuildFrom[Repr, B, BS],
ev: itr.A =:= Either[A, B]): (AS, BS) = {
val ops = itr(coll)
val as = bfa.fromSpecific(coll)(ops.iterator.map(ev).collect { case Left(a) => a })
val bs = bfb.fromSpecific(coll)(ops.iterator.map(ev).collect { case Right(b) => b })
(as, bs)
}
}
implicit def SplitOperation[Repr](coll: Repr)(implicit itr: IsIterable[Repr]): SplitOperation[Repr, itr.type] =
new SplitOperation(coll, itr)
However, I need to supply types at the use-site otherwise I get diverging implicit expansion.
scala> List(Left("bah"), Right(1), Left("gah"), Right(2), Right(3))
res1: List[scala.util.Either[String,Int]] = List(Left(bah), Right(1), Left(gah), Right(2), Right(3))
scala> res1.split
^
error: diverging implicit expansion for type scala.collection.BuildFrom[List[scala.util.Either[String,Int]],A,AS]
But the following works:
scala> res1.split[String, Int, List[String], List[Int]]
res4: (List[String], List[Int]) = (List(bah, gah),List(1, 2, 3))
EDIT
class SplitOperation[X, CC[_], S <: IsIterable[CC[X]]](coll: CC[X], itr: S) {
def split[A, B](implicit bfa: BuildFrom[CC[X], A, CC[A]], bfb: BuildFrom[CC[X], B, CC[B]], ev: itr.A =:= Either[A, B]): (CC[A], CC[B]) = {
val ops = itr(coll)
val as = bfa.fromSpecific(coll)(ops.iterator.map(ev).collect { case Left(a) => a })
val bs = bfb.fromSpecific(coll)(ops.iterator.map(ev).collect { case Right(b) => b })
(as, bs)
}
}
implicit def SplitOperation[A, B, CC[_]](coll: CC[Either[A, B]])(implicit itr: IsIterable[CC[Either[A, B]]]): SplitOperation[Either[A, B], CC, itr.type] =
new SplitOperation(coll, itr)
Gives me a slight improvement. Now I only need to provide type parameters A and B at the call site:
scala> l.split[String, Int]
res2: (List[String], List[Int]) = (List(bah, gah),List(1, 2))
This seems to work:
class SplitOperation[A, B, CC[_], S <: IsIterable[CC[Either[A, B]]]](coll: CC[Either[A, B]], itr: S) {
def split(implicit bfa: BuildFrom[CC[Either[A, B]], A, CC[A]], bfb: BuildFrom[CC[Either[A, B]], B, CC[B]], ev: itr.A =:= Either[A, B]): (CC[A], CC[B]) = {
val ops = itr(coll)
val as = bfa.fromSpecific(coll)(ops.iterator.map(ev).collect { case Left(a) => a })
val bs = bfb.fromSpecific(coll)(ops.iterator.map(ev).collect { case Right(b) => b })
(as, bs)
}
}
implicit def SplitOperation[A, B, CC[_]](coll: CC[Either[A, B]])(implicit itr: IsIterable[CC[Either[A, B]]]): SplitOperation[A, B, CC, itr.type] =
new SplitOperation(coll, itr)
In your case you don’t want to abstract over the “kind” of the collection type constructor (CC[_] vs CC[_, _], etc.), you always use the CC[_] kind, so you don’t need to use IsIterable.
I think it is also not necessary to support “Sorted” collections (eg, SortedSet) because there is no Ordering instance for Either, so you don’t need to use BuildFrom.
implicit class SplitOperation[A, B, CC[X] <: IterableOps[X, CC, CC[X]]](coll: CC[Either[A, B]]) {
def split: (CC[A], CC[B]) = {
val as = coll.iterableFactory.from(coll.iterator.collect { case Left(a) => a })
val bs = coll.iterableFactory.from(coll.iterator.collect { case Right(b) => b })
(as, bs)
}
}
https://scastie.scala-lang.org/64QxHwteQN2i3udSxCa3yw
I have a trait that is extended by multiple subclasses
trait Sup
case class Sub[A, B](a: A, f: B => B)(implicit val ev: A =:= B) extends Sup
case class Sub2[A, B](a: A, f: B => Unit)(implicit val ev: A =:= B) extends Sup
And two functions:
def foo[A, B](a: A, f: B => B)(implicit ev: A =:= B) = f(a)
def bar[A, B](a: A, f: B => Unit)(implicit ev: A =:= B) = f(a)
Now I can do some form of dynamic dispatching and call foo if the object is a Sub and bar if the object is a Sub2.
def dispatch(obj: Sup) = {
obj match {
case Sub(a, f) => foo(a, f)
case Sub2(a, f) => bar(a, f) // type mismatch: found: Nothing => Unit. required: B => Unit
}
}
I've also tried to pass the evidence explicitly but it results in the same error:
case o # Sub2(a, f) => bar(a, f)(o.ev) // type mismatch
It is very weird that f: B => B works (I can call foo), but f: B => Unit doesn't work (I can't call bar).
Not an answer but something to think about:
case class Sub1[A, B](a: A, f: B => B)
case class Sub2[A, B](a: A, f: B => Unit)
def foo[A, B](a: A, f: B => B)(implicit ev: A =:= B) = f(a)
def bar[A, B](a: A, f: B => Unit)(implicit ev: A =:= B) = f(a)
def dispatch(obj: Any) = obj match {
case Sub1(a, f) => foo(a, f)
case Sub2(a, f) => bar(a, f) // type mismatch: found: Nothing => Unit. required: B => Unit
}
This code have the same problem as yours but Sub1 and Sub2 case classes don't even have implicit blocks.
implicit section in case class doesn't effect pattern resolution. This section is just syntax sugar for calling apply(a: A, f: B => B)(implicit val ev: A =:= B) method on Sub1/2's companion objects. Pattern matching use unapply method to match the pattern at runtime and this unapply don't even know about evidences.
But I'm still wondering why first case is compiled without having this evidence.
Edit: Adding helpful comment from #AlexeyRomanov
More type inference than type erasure. But yes, the compiler infers type Any for a and Any => Any for f and then produces and uses evidence that Any =:= Any. In the second case it infers Nothing => Unit for f, because B => Unit is contravariant in B, and fails to find Any =:= Nothing.
You can actually make it work using type variable patterns:
def dispatch(obj: Sup) = {
obj match {
case obj: Sub[a, b] => foo(obj.a, obj.f)(obj.ev)
case obj: Sub2[a, b] => bar(obj.a, obj.f)(obj.ev)
}
}
This part is an answer to the comments, because it doesn't really fit in there:
Btw, there is still one subtlety I do not get: why is B => Unit contravariant in B
what is compiler's logic for this Nothing => Unit inference staff
You need to start with function variance. X => Y is a subtype of X1 => Y1 if and only if X is a supertype of X1 and Y is a subtype of Y1. We say it's contravariant in X and covariant in Y.
So if you fix Y = Unit, what remains is just contravariant in X. Any => Unit is a subtype of String => Unit, which is a subtype of Nothing => Unit. In fact, Nothing => Unit is the most general of all B => Unit, and that's why it gets inferred in the Sub2 case.
and B => B not (since it infers Any => Any) ?
The situation with B => B is different: String => String is neither a subtype nor a supertype of Any => Any, or of Nothing => Nothing. That is, B => B is invariant. So there is no principled reason to infer any specific B, and in this case the compiler uses the upper bound for B (Any), and B => B becomes Any => Any.
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))