Generic sum of two numeric expressions












1















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?










share|improve this question



























    1















    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?










    share|improve this question

























      1












      1








      1








      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?










      share|improve this question














      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






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 27 '18 at 15:30









      user9457782user9457782

      83




      83
























          2 Answers
          2






          active

          oldest

          votes


















          0














          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.






          share|improve this answer
























          • 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



















          0














          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.






          share|improve this answer
























          • 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 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











          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
          });


          }
          });














          draft saved

          draft discarded


















          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









          0














          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.






          share|improve this answer
























          • 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
















          0














          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.






          share|improve this answer
























          • 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














          0












          0








          0







          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.






          share|improve this answer













          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          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



















          • 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













          0














          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.






          share|improve this answer
























          • 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 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
















          0














          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.






          share|improve this answer
























          • 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 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














          0












          0








          0







          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.






          share|improve this answer













          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.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          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 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



















          • 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 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

















          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


















          draft saved

          draft discarded




















































          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.




          draft saved


          draft discarded














          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





















































          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







          Popular posts from this blog

          A CLEAN and SIMPLE way to add appendices to Table of Contents and bookmarks

          Calculate evaluation metrics using cross_val_predict sklearn

          Insert data from modal to MySQL (multiple modal on website)