Generic sum of two numeric expressions
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?
scala
add a comment |
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?
scala
add a comment |
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?
scala
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?
scala
scala
asked Nov 27 '18 at 15:30
user9457782user9457782
83
83
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
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.
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
add a comment |
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.
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
1
Just because the compiler will convertInt
toDouble
doesn't mean that it will convertF[Int]
toF[Double]
. Doing so in general for anyF
wouldn't make sense, since the compiler knows nothing of the semantics ofF
.
– Seth Tisue
Nov 27 '18 at 17:47
In the example for Sum2 the issue is that...plus(left.eval, right.eval)
expects two arguments of typeT
. Butleft.eval
returns aL
andright.eval
aR
. I specified, however, with the<%
view bound that bothL
andR
have to be convertible toT
. So why does it not convert them?
– user9457782
Nov 27 '18 at 19:25
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53502971%2fgeneric-sum-of-two-numeric-expressions%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
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.
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
add a comment |
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.
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
add a comment |
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.
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.
answered Nov 27 '18 at 20:28
SergGrSergGr
21.3k22243
21.3k22243
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
add a comment |
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
Thanks a lot! This definitely solves my problem.
– user9457782
Nov 27 '18 at 20:49
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
@user9457782, if I were you, I'd unaccepted this answer and waited some more (at least one day) for other solutions. This code looks too complicated to me but I was not able to come up with a simpler solution. Still someone else might be able to do so. Then come back and accept this answer if it is still the best one.
– SergGr
Nov 27 '18 at 20:58
add a comment |
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.
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
1
Just because the compiler will convertInt
toDouble
doesn't mean that it will convertF[Int]
toF[Double]
. Doing so in general for anyF
wouldn't make sense, since the compiler knows nothing of the semantics ofF
.
– Seth Tisue
Nov 27 '18 at 17:47
In the example for Sum2 the issue is that...plus(left.eval, right.eval)
expects two arguments of typeT
. Butleft.eval
returns aL
andright.eval
aR
. I specified, however, with the<%
view bound that bothL
andR
have to be convertible toT
. So why does it not convert them?
– user9457782
Nov 27 '18 at 19:25
add a comment |
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.
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
1
Just because the compiler will convertInt
toDouble
doesn't mean that it will convertF[Int]
toF[Double]
. Doing so in general for anyF
wouldn't make sense, since the compiler knows nothing of the semantics ofF
.
– Seth Tisue
Nov 27 '18 at 17:47
In the example for Sum2 the issue is that...plus(left.eval, right.eval)
expects two arguments of typeT
. Butleft.eval
returns aL
andright.eval
aR
. I specified, however, with the<%
view bound that bothL
andR
have to be convertible toT
. So why does it not convert them?
– user9457782
Nov 27 '18 at 19:25
add a comment |
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.
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.
answered Nov 27 '18 at 15:38
Dominic EggerDominic Egger
87817
87817
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
1
Just because the compiler will convertInt
toDouble
doesn't mean that it will convertF[Int]
toF[Double]
. Doing so in general for anyF
wouldn't make sense, since the compiler knows nothing of the semantics ofF
.
– Seth Tisue
Nov 27 '18 at 17:47
In the example for Sum2 the issue is that...plus(left.eval, right.eval)
expects two arguments of typeT
. Butleft.eval
returns aL
andright.eval
aR
. I specified, however, with the<%
view bound that bothL
andR
have to be convertible toT
. So why does it not convert them?
– user9457782
Nov 27 '18 at 19:25
add a comment |
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
1
Just because the compiler will convertInt
toDouble
doesn't mean that it will convertF[Int]
toF[Double]
. Doing so in general for anyF
wouldn't make sense, since the compiler knows nothing of the semantics ofF
.
– Seth Tisue
Nov 27 '18 at 17:47
In the example for Sum2 the issue is that...plus(left.eval, right.eval)
expects two arguments of typeT
. Butleft.eval
returns aL
andright.eval
aR
. I specified, however, with the<%
view bound that bothL
andR
have to be convertible toT
. So why does it not convert them?
– user9457782
Nov 27 '18 at 19:25
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
But I am using type bounds. Shouldn't the compiler figure out that it can convert the Int to a Double and then everything works as expected?
– user9457782
Nov 27 '18 at 15:49
1
1
Just because the compiler will convert
Int
to Double
doesn't mean that it will convert F[Int]
to F[Double]
. Doing so in general for any F
wouldn't make sense, since the compiler knows nothing of the semantics of F
.– Seth Tisue
Nov 27 '18 at 17:47
Just because the compiler will convert
Int
to Double
doesn't mean that it will convert F[Int]
to F[Double]
. Doing so in general for any F
wouldn't make sense, since the compiler knows nothing of the semantics of F
.– Seth Tisue
Nov 27 '18 at 17:47
In the example for Sum2 the issue is that
...plus(left.eval, right.eval)
expects two arguments of type T
. But left.eval
returns a L
and right.eval
a R
. I specified, however, with the <%
view bound that both L
and R
have to be convertible to T
. So why does it not convert them?– user9457782
Nov 27 '18 at 19:25
In the example for Sum2 the issue is that
...plus(left.eval, right.eval)
expects two arguments of type T
. But left.eval
returns a L
and right.eval
a R
. I specified, however, with the <%
view bound that both L
and R
have to be convertible to T
. So why does it not convert them?– user9457782
Nov 27 '18 at 19:25
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53502971%2fgeneric-sum-of-two-numeric-expressions%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown