I am currently coding in scala and I consider my self a newbie.
I have 3 classes that are out of my control, that means that I can't change them.
class P
class A {
def m(p: P): A = { println("A.m"); this }
}
class B {
def m(p: P): B = { println("B.m"); this }
}
This is a simplified example the actual code is more complicated and classes A, B have many other similar methods.
I need to call method m for instances of classes A, B
The obvious solution is:
def fill(ab: AnyRef, p: P): Unit = {
ab match {
case a: A => a.m(p)
case b: B => b.m(p)
}
}
but that involves code duplication. I tried to solve it with duck typing and so far my best take on the subject is this:
type WithM[T] = { def m(p: P): T }
def fill[S, T <: WithM[S]](ab: T, p: P): S =
ab.m(p)
fill(new A, new P)
but I get type inference errors like:
Error:(18, 5) inferred type arguments [Nothing,A] do not conform to method fill's type parameter bounds [S,T <: Test.WithM[S]]
fill(new A, new P)
^
Can this problem be solved in an elegant way with minimal magic?
You've got a few options. One is to provide the type parameters explicitly:
scala> fill[A, A](new A, new P)
A.m
res1: A = A#4beb8b21
If the m method always returns a value of the type that it's defined on, you can help out the type inference by encoding that fact in your fill:
scala> def fill[T <: WithM[T]](o: T, p: P): T = o.m(p)
fill: [T <: WithM[T]](o: T, p: P)T
scala> fill(new A, new P)
A.m
res2: A = A#5f9940d4
You can also skip the type alias:
scala> def fill[S](o: { def m(o: P): S }, p: P): S = o.m(p)
fill: [S](o: AnyRef{def m(o: P): S}, p: P)S
scala> fill(new A, new P)
A.m
res3: A = A#3388156e
I'd strongly suggest using a type class, though—it's a little bit of syntactic overhead but much cleaner:
trait HasM[T] {
type Out
def apply(t: T, p: P): Out
}
object HasM {
type Aux[T, Out0] = HasM[T] { type Out = Out0 }
implicit def AHasM: Aux[A, A] = new HasM[A] {
type Out = A
def apply(t: A, p: P): A = t.m(p)
}
implicit def BHasM: Aux[B, B] = new HasM[B] {
type Out = B
def apply(t: B, p: P): B = t.m(p)
}
}
def fill[T](t: T, p: P)(implicit hm: HasM[T]): hm.Out = hm(t, p)
And then:
scala> fill(new A, new P)
A.m
res4: A = A#74e92aa9
scala> fill(new B, new P)
B.m
res5: B = B#1ea35068
No reflective access and you're using a widely-understood idiom.
You can use a typeclass, but honestly in this case I would just pattern match if there's really not common supertype of A and B.
trait POps[T] {
def m(t: T, p: P): T
}
object POps {
def apply[T : POps] = implicitly[POps[T]]
}
object A {
implicit val aPops: POps[A] = new POps[A] {
def m(t: A, p: P) = t.m(p)
}
}
object B {
implicit val bPops: POps[B] = new POps[B] {
def m(t: B, p: P) = t.m(p)
}
}
def fill[M : POps](o: M, p: P): Unit = {
POps[M].m(o, p)
}
If there are really only two then just use pattern matching.
Related
I'm writing an implementation for Curry-Howard isomorphism in scala, a family of proofs can be defined as an object implementing the following trait:
(the full code repo is uploaded to https://github.com/tribbloid/shapesafe/blob/djl/prove-debug/macro/src/main/scala/org/shapesafe/core/shape/unary/WithNames.scala)
trait ProofSystem[OUB] { // TODO: no IUB?
trait Proposition extends Serializable {
type Codomain <: OUB
def value: Codomain
}
case object Proposition {
type Aux[O <: OUB] = Proposition {
type Codomain = O
}
type Lt[+O <: OUB] = Proposition {
type Codomain <: O
}
// Can't use Aux, syntax not supported by scala
trait Of[O <: OUB] extends Proposition {
final type Codomain = O
}
case class ToBe[O <: OUB](override val value: O) extends Of[O]
}
// doesn't extend T => R intentionally
// each ProofSystem use a different one to alleviate search burden of compiler (or is it redundant?)
trait CanProve[-I, +P <: Proposition] {
def apply(v: I): P
final def valueOf(v: I): P#Codomain = apply(v).value // TODO: this should be real apply? The above should be 'prove'
}
object CanProve {}
/**
* representing 2 morphism:
*
* - value v --> value apply(v)
*
* - type I --> type O
*
* which is why it uses =>>
* #tparam I src type
* #tparam P tgt type
*/
class =>>^^[-I, P <: Proposition](
val toProof: I => P
) extends CanProve[I, P] {
override def apply(v: I): P = toProof(v)
}
type -->[-I, O <: OUB] = CanProve[I, Proposition.Aux[O]]
type ~~>[-I, +O <: OUB] = CanProve[I, Proposition.Lt[O]]
class =>>[-I, O <: OUB](
val toOut: I => O
) extends =>>^^[I, Proposition.ToBe[O]](v => Proposition.ToBe(toOut(v)))
def from[I]: Factory[I] = new Factory[I]()
class Factory[I]() {
def =>>^^[P <: Proposition](fn: I => P) = new (I =>>^^ P)(fn)
def =>>[O <: OUB](fn: I => O) = new (I =>> O)(fn)
}
def at[I](v: I) = new Summoner[I](v)
class Summoner[I](v: I) extends Factory[I] {
implicit def summon[P <: Proposition](
implicit
ev: CanProve[I, P]
): P = ev.apply(v)
implicit def summonValue[P <: Proposition](
implicit
ev: CanProve[I, P]
): P#Codomain = summon(ev).value
}
}
In one particular case, the compiler got into a dead loop when 2 implicits are defined in a scope:
case class WithNames[
S1 <: Shape,
N <: Names
](
s1: S1,
newNames: N
) extends Shape {}
object WithNames {
// TODO: DEAD LOOP!
implicit def axiom[
P1 <: LeafShape,
N <: Names,
HO <: HList,
O <: LeafShape
](
implicit
zip: ZipWithKeys.Aux[N#Keys, P1#_Dimensions#Static, HO],
toShape: LeafShape.FromRecord.==>[HO, O]
): WithNames[P1, N] =>> O = {
from[WithNames[P1, N]].=>> { src =>
val keys: N#Keys = src.newNames.keys
val p1: P1 = src.s1
val values: P1#_Dimensions#Static = p1.dimensions.static
val zipped: HO = values.zipWithKeys(keys)
LeafShape.FromRecord(zipped)
}
}
implicit def theorem[
S1 <: Shape,
N <: Names,
P1 <: LeafShape,
O <: LeafShape
](
implicit
lemma1: S1 ~~> P1,
lemma2: WithNames[P1, N] --> O
): WithNames[S1, N] =>> O = {
from[WithNames[S1, N]].=>> { src =>
val p1: P1 = lemma1.valueOf(src.s1)
lemma2.valueOf(
src.copy(p1)
)
}
}
}
The compiler seems to be stuck on a cyclic proof resolution: it tries to fulfil lemma2 of theorem with itself. If I merge 2 implicits into 1, the problem disappeared:
implicit def asLeaf[
S1 <: Shape,
P1 <: LeafShape,
N <: Names,
HO <: HList,
O <: LeafShape
](
implicit
lemma: S1 ~~> P1,
zip: ZipWithKeys.Aux[N#Keys, P1#_Dimensions#Static, HO],
toShape: LeafShape.FromRecord.==>[HO, O]
): WithNames[S1, N] =>> O = {
from[WithNames[S1, N]].=>> { src =>
val keys: N#Keys = src.newNames.keys
val p1: P1 = lemma.valueOf(src.s1)
val values: P1#_Dimensions#Static = p1.dimensions.static
val zipped: HO = values.zipWithKeys(keys)
LeafShape.FromRecord(zipped)
}
}
So my questions are:
Why this could happen and what change of the scala compiler algorithm could eliminate any cyclic search for proves?
How to manually circumvent this error?
I also realised that the canonical implementation of homotopy type theory (https://github.com/siddhartha-gadgil/ProvingGround) actually does most of the inference in runtime, instead of compile-time, which seems to be defeating the purpose. Perhaps this bug is part of the reason?
I'm trying to deal with an ambiguous implicits problem, and (relatedly) figure out what best practise should be for parameterizing typeclasses. I have a situation where I am using a typeclass to implement a polymorphic method. I initially tried the approach below:
abstract class IsValidTypeForContainer[A]
object IsValidTypeForContainer {
implicit val IntIsValid = new IsValidTypeForContainer[Int] {}
implicit val DoubleIsValid = new IsValidTypeForContainer[Double] {}
}
abstract class IsContainer[A, B: IsValidTypeForContainer] {
def getElement(self: A, i: Int): B
def get[R](self: A, ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = gets.get(self, ref)
def fromList(self: A, other: List[B]): A = ???
}
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B],
) {
def getElement(i: Int) = isCont.getElement(self, i)
def get[R](ref: R)(implicit gets: GetsFromContainerMax[A, R, B]): gets.Out = isCont.get(self, ref)
def fromList(other: List[B]): A = isCont.fromList(self, other)
}
abstract class GetsFromContainerMax[A, R, B: IsValidTypeForContainer] {
type Out
def get(self: A, ref: R): Out
}
object GetsFromContainerMax {
type Aux[A, R, B, O] = GetsFromContainerMax[A, R, B] { type Out = O }
def instance[A, R, B: IsValidTypeForContainer, O](func: (A, R) => O): Aux[A, R, B, O] = new GetsFromContainerMax[A, R, B] {
type Out = O
def get(self: A, ref: R): Out = func(self, ref)
}
implicit def getsForListInt[A, B: IsValidTypeForContainer](implicit
isCons: IsContainer[A, B],
): Aux[A, List[Int], B, A] = instance(
(self: A, ref: List[Int]) => {
val lst = ref.map(isCons.getElement(self, _)).toList
isCons.fromList(self, lst)
}
)
}
Where I have given the GetsContainerMax typeclass three parameters - one for the IsContainer object, one for the reference and one for the data type of the IsContainer object.
When I then try to use this, I get a compile error:
case class List1[B: IsValidTypeForContainer] (
data: List[B]
)
implicit def list1IsContainer[B: IsValidTypeForContainer] = new IsContainer[List1[B], B] {
def getElement(self: List1[B], i: Int): B = self.data(i)
def fromList(self: List1[B], other: List[B]): List1[B] = ???
}
val l1 = List1[Int](List(1,2,3))
implicitly[IsContainer[List1[Int], Int]].get(l1, List(1,2)) // Works
implicitly[List1[Int] => IsContainerOps[List1[Int], Int]] // Works
l1.get(List(1,2)) // Does not work
If I use the -Xlog-implicits build parameter, it tells me that
ambiguous implicit values: both value IntIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Int] and value DoubleIsValid in object IsValidTypeForContainer of type example.Test.IsValidTypeForContainer[Double] match expected type example.Test.IsValidTypeForContainer[B]
Which seems to make sense; presumably I am bringing both of these implicits into scope by parameterizing the typeclass with the generic B.
My next thought was therefore to try to reduce the number of generic parameters for IsValidTypeForContainer to the minimum, in order to have only one typeclass in scope per type of R, likeso:
abstract class GetsFromContainerMin[R] {
type Out
def get[A](self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out
}
object GetsFromContainerMin {
type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
def instance[A, R, O](func: (A, R) => O): Aux[R, O] = new GetsFromContainerMin[R] {
type Out = O
def get(self: A, ref: R)(implicit isCont: IsContainer[A, _]): Out = func(self, ref)
}
implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = instance(
(self: A, ref: List[Int]) => {
val lst = ref.map(isCons.getElement(self, _)).toList
isCons.fromList(self, lst) // type mismatch - found: List[Any], required: List[_$3]
}
)
}
But this seems to not only not solve the problem, but to generate an additional error in that the compiler can no longer type-check that type B implements IsValidTypeForContainer.
Any help gratefully received.
So I've messed around with this a bit and seem to have found the solution. The typeclass-with-three-parameters approach works, if I use
implicit class IsContainerOps[A, B](self: A)(implicit
isCont: IsContainer[A, B],
)
instead of
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B],
)
I am not exactly sure why this is and if anyone would care to respond I'd be interested to know. Are multiple typeclasses created if you use a context bound like in the original example, one for each implementation of IsValidTypeForContainer?
Regarding GetsFromContainerMin, only type classes without polymorphic methods can have constructor method instance (in companion object) because of the lack of polymorphic functions in Scala 2. In Scala 3 you'll be able to write
def instance[R, O](func: [A] => (A, R) => O): Aux[R, O] = ...
So far you have to write
object GetsFromContainerMin {
type Aux[R, O] = GetsFromContainerMin[R] { type Out = O }
implicit def getsForListInt[A](implicit isCons: IsContainer[A, _]): Aux[List[Int], A] = new GetsFromContainerMin[List[Int]] {
override type Out = A
override def get[A1](self: A1, ref: List[Int])(implicit isCont: IsContainer[A1, _]): Out = {
val lst = ref.map(isCons.getElement(self, _)).toList
// ^^^^
isCons.fromList(self, lst)
// ^^^^
}
}
}
I guess compile errors are pretty clear
type mismatch;
found : self.type (with underlying type A1)
required: A
Regarding your first question,
implicit class IsContainerOps[A, B: IsValidTypeForContainer](self: A)(implicit
isCont: IsContainer[A, B]
)
is desugared to
implicit class IsContainerOps[A, B](self: A)(implicit
ev: IsValidTypeForContainer[B],
isCont: IsContainer[A, B]
)
but order of implicits is significant. If you modify IsContainerOps to
implicit class IsContainerOps[A, B](self: A)(implicit
isCont: IsContainer[A, B],
ev: IsValidTypeForContainer[B],
)
then l1.get(List(1,2)) will compile.
Implicits are resolved from left to right. You want firstly A to be inferred (from self), then IsContainer[A, B] to be resolved, so B to be inferred and IsValidTypeForContainer[B] to be resolved and not vice versa firstly A to be inferred, then IsValidTypeForContainer[B] to be resolved, and at this step B is not restricted, there is no connection between A and B, so possibly B can be not inferred or inferred to be Nothing, so IsContainer[A, B] is not resolved. I'm a little simplifying (not every time when you swap implicits you break resolution) but general strategy of implicit resolution and type inference is as I described.
I am writing a little toy language built on top of expressions. Here is some code to get the idea:
trait Expression[+T] {
def eval: T
}
case class Literal[+T](value: T) extends Expression[T] {
def eval = value
}
The parser builds a tree of expressions which are then evaluated by calling the eval method. Now I want to add a Sum expression that represents the sum of two other expressions:
case class Sum[+T: Numeric](left: Expression[T], right: Expression[T]) {
def eval = implicitly[Numeric[T]].plus(left.eval, right.eval)
}
This works fine if the left and right expression have the same type (as specified by the constructor). But naturally I would like it to work in the following case as well:
Sum(Literal(1.1), Literal(1))
This does not work because the compiler does not find an implicit argument of type Numeric[AnyVal], which makes sense.
I came up with the following code, using type bounds, to try to fix the issue:
case class Sum2[+T: Numeric, L <% T, R <% T](left: Expression[L], right: Expression[R]) extends Expression[T] {
def eval = implicitly[Numeric[T]].plus(left.eval, right.eval)
}
Now the compiler complains that left.eval and right.eval are not of type T. Casting to T using asInstanceOf[T] generates more compiler errors because of ambiguous implicit arguments.
What is the proper way to achieve this?
As it was pointed in the comments, the fact that there is safe conversion from Int to Double for your operation is not enough for the compiler to be able prove that this conversion is valid in all relevant contexts. I'm not aware of any simpler way to achieve what you want than this code (see also online):
trait Expression[+T] {
def eval: T
}
trait TypeConverter[S, T] {
def convert(value: S): T
}
trait TypeConverterLowPriority {
implicit def compose[A, B, C](implicit aToB: TypeConverter[A, B], bToC: TypeConverter[B, C]): TypeConverter[A, C] = new TypeConverter.TypeConverterImpl(a => bToC.convert(aToB.convert(a)))
}
object TypeConverter extends TypeConverterLowPriority {
class TypeConverterImpl[S, T](f: S => T) extends TypeConverter[S, T] {
override def convert(value: S): T = f(value)
}
def sameType[T]: TypeConverter[T, T] = new TypeConverterImpl(identity)
implicit val intToDouble: TypeConverter[Int, Double] = new TypeConverterImpl(_.toDouble)
implicit val shortToInt: TypeConverter[Short, Int] = new TypeConverterImpl(_.toInt)
// add more "primitive" type conversions here
}
case class Literal[+T](value: T) extends Expression[T] {
def eval = value
}
trait BinaryOpImpl[A, B, R] {
protected val numericR: Numeric[R]
protected val aToR: TypeConverter[A, R]
protected val bToR: TypeConverter[B, R]
final def eval(left: A, right: B): R = evalImpl(aToR.convert(left), bToR.convert(right))
protected def evalImpl(left: R, right: R): R
}
trait BinaryOpImplCompanionLowPriority[Ops[_, _, _]] {
protected def build[A, B, R](numericR: Numeric[R], aToR: TypeConverter[A, R], bToR: TypeConverter[B, R]): Ops[A, B, R]
implicit def castLeftToRight[L, R: Numeric](implicit tcl: TypeConverter[L, R]): Ops[L, R, R] = build(implicitly[Numeric[R]], tcl, TypeConverter.sameType)
implicit def castRightToLeft[L: Numeric, R](implicit tcr: TypeConverter[R, L]): Ops[L, R, L] = build(implicitly[Numeric[L]], TypeConverter.sameType, tcr)
}
trait BinaryOpImplCompanion[Ops[_, _, _]] extends BinaryOpImplCompanionLowPriority[Ops] {
implicit def sameType[T: Numeric]: Ops[T, T, T] = build(implicitly[Numeric[T]], TypeConverter.sameType, TypeConverter.sameType)
}
class SumImpl[A, B, R](val numericR: Numeric[R], val aToR: TypeConverter[A, R], val bToR: TypeConverter[B, R]) extends BinaryOpImpl[A, B, R] {
override protected def evalImpl(left: R, right: R): R = numericR.plus(left, right)
}
object SumImpl extends BinaryOpImplCompanion[SumImpl] {
override protected def build[A, B, R](numericR: Numeric[R], aToR: TypeConverter[A, R], bToR: TypeConverter[B, R]): SumImpl[A, B, R] = new SumImpl(numericR, aToR, bToR)
}
case class Sum[+T, L, R](left: Expression[L], right: Expression[R])(implicit impl: SumImpl[L, R, T]) extends Expression[T] {
def eval = impl.eval(left.eval, right.eval)
}
usage example:
def test(): Unit = {
println(Sum(Literal(3), Literal(1)).eval)
println(Sum(Literal(1.1), Literal(1)).eval)
println(Sum(Literal(1), Literal(1.1)).eval)
println(Sum(Literal[Short](1), Literal(1.12)).eval) // composite conversion Short -> Int -> Double
}
Essentially the idea is to have one implicit variable that encapsulates all 3 relevant types instead of having 3 separate implicits. So the code complies if the compiler can build one composite evidence for a triplet LeftArgType-RightArgType-ResultType.
the problem specifically is that Sum(Literal(1.1), Literal(1)) has a Literal[Double] on the left and a Literal[Int] on the right. The LUB of Int and Double is indeed AnyVal as you have seen.
https://scalafiddle.io/sf/ALM9urR/1
works perfectly fine. I also think this is good behavior because adding different types can be a bit iffy but else you could introduce an implicit that lets you do the necessary conversions.
I have the following code:
trait M[Type[_]]{
type T[X] = Type[X]
def from[A](f: T[A]): A
}
class ListM extends M[List]{ def from[A](f: T[A]) = f.head }
class Trans[A, X[_], B <: M[X]](val r: X[Option[A]])
trait CurriedTrans[X[_], B <: M[X]]{ type Type[A] = Trans[A, X, B] }
class TransM[X[_], B <: M[X]](val b: B) extends M[CurriedTrans[X, B]#Type]{
def from[A] = (f: T[A]) => b.from(f.r).get
}
and I can instantiate variables of type TransM in two ways:
val x1 = new TransM[List, ListM](new ListM)
val x2 = new TransM[ListM#T, ListM](new ListM)
I think ListM#T is redundant type parameter, so I'm trying to eliminate it:
trait M{
type T[X]
def from[A](f: T[A]): A
}
class ListM extends M {
type T[X] = List[X]
def from[A](f: T[A]) = f.head
}
class Trans[A, B <: M](val r: B#T[Option[A]])
class TransM[B <: M](val b: B) extends M {
type T[X] = Trans[X, B]
def from[Y] = (f: T[Y]) => b.from(f.r).get
}
to instantiate variable as
val x = new TransM[ListM](new ListM)
Unfortunately, the second implementation can't be compiled because of a type mismatch error:
type mismatch;
found : f.r.type (with underlying type B#T[Option[Y]])
required: TransM.this.b.T[?]
def from[Y] = (f: T[Y]) => b.from(f.r).get
^
Can I fix this issue and simplify my code or should I write boilerplate ListM#T everywhere?
#ziggystar says it: Drop the bound B and use M[X] directly:
class TransM[X[_]](val b: M[X]) extends M[CurriedTrans[X, M[X]]#Type] {
def from[A](f: T[A]) = b.from(f.r).get
}
val x1 = new TransM(new ListM)
You could consider to do the same for Trans and CurriedTrans. If you need the inner type of M, you can always expose it through a type member of Trans and CurriedTrans.
Given this code:
trait S {
def s: String
}
trait I {
def i: Int
}
case class CC_S(s: String) extends S
case class CC_I(i: Int) extends I
case class CC_S_I(s: String, i: Int) extends S with I
def combine(s: S, i: I): S with I = CC_S_I(s.s, i.i)
This test succeeds:
Assert.assertEquals(CC_S_I("s", 1), combine(CC_S("s"), CC_I(1)))
(How) can I make the combine method generic as a trait with type parameters? I.e. I would like to define something like this:
trait MyTrait[A,B] {
def combine(a: A, b: B): A with B
}
So that I can use it like this:
class MyClass[A <: CC_S, B <: CC_I] extends MyTrait[A,B] {
override def combine(s: A, i: B) = CC_S_I(s.s, i.i) //Note: this line does not compile...
}
Assert.assertEquals(CC_S_I("s", 1), new MyClass().combine(CC_S("s"), CC_I(1)))
Update. This would have been a better example:
class MyClass[A <: S, B <: I] extends MyTrait[A,B] {
override def combine(s: A, i: B) = CC_S_I(s.s, i.i) //Note: this line does not compile...
}
See comments under Travis's answer to see that answered as well.
The problem is just that CC_S_I isn't actually a subtype of either CC_S or CC_I (although it is a subtype of S and I). The following will work:
class MyClass extends MyTrait[S, I] {
def combine(s: S, i: I): S with I = CC_S_I(s.s, i.i)
}
As a side note, the Shapeless library is worth looking into if you're doing much of this kind of thing. It allows you to write your combine method very generically:
import shapeless._
def combine[A, B, C, AL <: HList, BL <: HList, CL <: HList](a: A, b: B)(implicit
aIso: Iso[A, AL],
bIso: Iso[B, BL],
cIso: Iso[C, CL],
p: PrependAux[AL, BL, CL]
): C = cIso from p(aIso to a, bIso to b)
Now you just need a bit of boilerplate:
implicit val ccsIso = Iso.hlist(CC_S.apply _, CC_S.unapply _)
implicit val cciIso = Iso.hlist(CC_I.apply _, CC_I.unapply _)
implicit val ccsiIso = Iso.hlist(CC_S_I.apply _, CC_S_I.unapply _)
And you can write:
scala> val x = combine[
| CC_S, CC_I, CC_S_I, String :: HNil, Int :: HNil, String :: Int :: HNil
| ](CC_S("s"), CC_I(1))
x: CC_S_I = CC_S_I(s,1)
The enormous type annotation is unfortunately necessary with this formulation, but this should give you some idea of what's possible, and there are things you could do to make the usage cleaner.