From 2277a098e3db941b662383956a9626a612f4d2b6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 21 Jun 2020 00:35:05 +0300 Subject: [PATCH 01/11] Add OpIntercept to allow custom types and operations --- .../scala/singleton/ops/OpIntercept.scala | 22 ++++ .../singleton/ops/impl/GeneralMacros.scala | 112 +++++++++++++----- src/main/scala/singleton/ops/impl/Op.scala | 8 +- .../scala/singleton/ops/OpInterceptSpec.scala | 55 +++++++++ 4 files changed, 166 insertions(+), 31 deletions(-) create mode 100644 src/main/scala/singleton/ops/OpIntercept.scala create mode 100644 src/test/scala/singleton/ops/OpInterceptSpec.scala diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala new file mode 100644 index 00000000..d15362f1 --- /dev/null +++ b/src/main/scala/singleton/ops/OpIntercept.scala @@ -0,0 +1,22 @@ +package singleton.ops +import scala.reflect.macros.whitebox +import impl._ + +import scala.annotation.implicitNotFound + +trait OpIntercept[Op <: HasOut] extends HasOut +object OpIntercept { + type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0} + @implicitNotFound("Failed to cache op ${Op} with result ${Out}") + trait CacheResult[Op <: HasOut, Out] + object CacheResult { + implicit def call[Op <: HasOut, Out] : CacheResult[Op, Out] = macro Macro.materializeCacheResult[Op, Out] + final class Macro(val c: whitebox.Context) extends GeneralMacros { + def materializeCacheResult[ + Op : c.WeakTypeTag, + Out: c.WeakTypeTag, + ]: c.Tree = cacheOpInterceptResult[Op, Out] + } + + } +} \ No newline at end of file diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala index 6679139b..e0495ff0 100644 --- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala +++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala @@ -1,13 +1,17 @@ package singleton.ops.impl import singleton.twoface.impl.TwoFaceAny -import scala.reflect.macros.whitebox +import scala.reflect.macros.{TypecheckException, whitebox} private object MacroCache { import scala.collection.mutable val cache = mutable.Map.empty[Any, Any] def get(key : Any) : Option[Any] = cache.get(key) def add[V <: Any](key : Any, value : V) : V = {cache += (key -> value); value} + private var opInterceptValue : Option[Any] = None + def setOpInterceptValue(value : Any) : Unit = opInterceptValue = Some(value) + def getOpInterceptValue : Any = opInterceptValue + def clearOpInterceptValue() : Unit = opInterceptValue = None } trait GeneralMacros { val c: whitebox.Context @@ -261,7 +265,7 @@ trait GeneralMacros { def unapply(arg: CalcType): Option[Primitive] = Some(arg.primitive) } - case class CalcUnknown(tpe : Type, treeOption : Option[Tree]) extends Calc { + case class CalcUnknown(tpe : Type, treeOption : Option[Tree], opIntercept : Boolean) extends Calc { override val primitive: Primitive = Primitive.Unknown(tpe, "Unknown") } object NonLiteralCalc { @@ -328,6 +332,19 @@ trait GeneralMacros { VerboseTraversal(s"${GREEN}${BOLD}caching${RESET} $k -> $value") value } + def setOpInterceptCalc(calc : Calc) : Unit = MacroCache.setOpInterceptValue(Left(calc)) + def setOpInterceptError(msg : String) : Unit = MacroCache.setOpInterceptValue(Right(msg)) + def clearOpInterceptCalc() : Unit = MacroCache.clearOpInterceptValue() + def getOpInterceptCalc : Option[Either[Calc, String]] = { + MacroCache.getOpInterceptValue.asInstanceOf[Option[Either[Calc, String]]] match { + case Some(Left(v)) => Some(Left(v match { + case lit : CalcLit => CalcLit(lit.value) //reconstruct internal literal tree + case nlit : CalcNLit => CalcNLit(nlit.primitive, deepCopyTree(nlit.tree)) + case c => c + })) + case v => v + } + } } //////////////////////////////////////////////////////////////////// @@ -456,7 +473,7 @@ trait GeneralMacros { def unapply(tp: Type): Option[Calc] = { tp match { case TypeRef(_, sym, ft :: tp :: _) if sym == opMacroSym && ft.typeSymbol == funcTypes.GetType => - Some(CalcUnknown(tp, None)) + Some(CalcUnknown(tp, None, opIntercept = false)) case TypeRef(_, sym, args) if sym == opMacroSym => VerboseTraversal(s"@@OpCalc@@\nTP: $tp\nRAW: ${showRaw(tp)}") val funcType = args.head.typeSymbol.asType @@ -473,7 +490,7 @@ trait GeneralMacros { case (funcTypes.ImplicitFound, _) => setUncachingReason(1) aValue match { - case CalcUnknown(t, _) => try { + case CalcUnknown(t, _, false) => try { c.typecheck(q"implicitly[$t]") Some(CalcLit(true)) } catch { @@ -484,7 +501,7 @@ trait GeneralMacros { } case (funcTypes.EnumCount, _) => aValue match { - case CalcUnknown(t, _) => Some(CalcLit(t.typeSymbol.asClass.knownDirectSubclasses.size)) + case CalcUnknown(t, _, false) => Some(CalcLit(t.typeSymbol.asClass.knownDirectSubclasses.size)) case _ => Some(CalcLit(0)) } case (funcTypes.IsNat, _) => @@ -549,9 +566,10 @@ trait GeneralMacros { } case _ => //regular cases - opCalc(funcType, aValue, bValue, cValue) match { + opCalc(Some(tp), funcType, aValue, bValue, cValue) match { case (res : CalcVal) => Some(res) - case u @ CalcUnknown(_,Some(_)) => Some(u) //Accept unknown values with a tree + case u @ CalcUnknown(_,Some(_), _) => Some(u) //Accept unknown values with a tree + case oi @ CalcUnknown(_,_, true) => Some(oi) //Accept unknown op interception case _ => None } } @@ -575,7 +593,7 @@ trait GeneralMacros { case Some(t : CalcUnknown) => t case _ => VerboseTraversal(s"@@Unknown@@\nTP: $tp\nRAW: ${showRaw(tp)}") - CalcUnknown(tp, None) + CalcUnknown(tp, None, opIntercept = false) } } @@ -654,10 +672,11 @@ trait GeneralMacros { } //////////////////////////////////////////////////////////////////////// - def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym): Nothing = { + def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym, position : Position = c.enclosingPosition): Nothing = { VerboseTraversal(s"!!!!!!aborted with: $msg at $annotatedSym, $defaultAnnotatedSym") if (annotatedSym.isDefined) setAnnotation(msg, annotatedSym.get) - c.abort(c.enclosingPosition, msg) + CalcCache.setOpInterceptError(msg) //propagating the error in case this is an inner implicit call for OpIntercept + c.abort(position, msg) } def buildWarningMsgLoc : String = s"${c.enclosingPosition.source.path}:${c.enclosingPosition.line}:${c.enclosingPosition.column}" @@ -734,11 +753,11 @@ trait GeneralMacros { case None => q""" new $opTpe { - type OutWide = Option[$outTpe] - type Out = Option[$outTpe] - final val value: Option[$outTpe] = None + type OutWide = $outTpe + type Out = $outTpe + final lazy val value: $outTpe = throw new IllegalArgumentException("This operation does not produce a value.") final val isLiteral = false - final val valueWide: Option[$outTpe] = None + final lazy val valueWide: $outTpe = throw new IllegalArgumentException("This operation does not produce a value.") } """ } @@ -771,11 +790,11 @@ trait GeneralMacros { } opTree match { - case q"""{ - $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$parents { $self => ..$opClsBlk } - $expr(...$exprss) - }""" => getOut(opClsBlk) - case _ => extractionFailed(opTree) + case q"""{ + $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$parents { $self => ..$opClsBlk } + $expr(...$exprss) + }""" => getOut(opClsBlk) + case _ => extractionFailed(opTree) } } @@ -881,27 +900,61 @@ trait GeneralMacros { val (typedTree, tpe) = GetArgTree(argIdx, lhs) VerboseTraversal(s"@@extractFromArg@@\nTP: $tpe\nRAW: ${showRaw(tpe)}\nTree: $typedTree") TypeCalc(tpe) match { - case _ : CalcUnknown => CalcUnknown(tpe, Some(c.untypecheck(typedTree))) + case _ : CalcUnknown => CalcUnknown(tpe, Some(c.untypecheck(typedTree)), opIntercept = false) case t : CalcNLit => CalcNLit(t, typedTree) case t => t } } /////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////// + // OpInterept Result Caching + /////////////////////////////////////////////////////////////////////////////////////////// + def cacheOpInterceptResult[Op, Out](implicit ev0: c.WeakTypeTag[Op], ev1: c.WeakTypeTag[Out]) : Tree = { + val opTpe = weakTypeOf[Op] + val outTpe = weakTypeOf[Out] + val outCalc = TypeCalc(outTpe) + CalcCache.setOpInterceptCalc(outCalc) + q"new _root_.singleton.ops.OpIntercept.CacheResult[$opTpe, $outTpe]{}" + } + /////////////////////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////////////////// // Three operands (Generic) /////////////////////////////////////////////////////////////////////////////////////////// def materializeOpGen[F](implicit ev0: c.WeakTypeTag[F]): MaterializeOpAuxGen = new MaterializeOpAuxGen(weakTypeOf[F]) - def opCalc(funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = { + def opCalc(opTpe : Option[Type], funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = { lazy val a = aCalc lazy val b = bCalc lazy val cArg = cCalc def unsupported() : Calc = { - (a, b) match { - case (aArg : CalcVal, bArg : CalcVal) => abort(s"Unsupported $funcType[$a, $b, $cArg]") - case _ => CalcUnknown(funcType.toType, None) + val cachedTpe = opTpe.get match { + case TypeRef(pre, sym, args) => c.internal.typeRef(pre, sym, List(funcType.toType, a.tpe, b.tpe, cArg.tpe)) + } + //calling OpIntercept for the operation should cache the expected result if executed correctly + CalcCache.clearOpInterceptCalc() + val implicitlyTree = q"implicitly[_root_.singleton.ops.OpIntercept[$cachedTpe]]" + try { + c.typecheck(implicitlyTree, silent = false) + val cachedCalc = CalcCache.getOpInterceptCalc match { + case Some(calc) => calc + case None => abort("Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`") + } + CalcCache.clearOpInterceptCalc() + cachedCalc match { + case Left(t : CalcUnknown) => + t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later + case Left(t) => t + case Right(msg) => abort(msg) + } + } catch { + case TypecheckException(pos, msg) => + CalcCache.getOpInterceptCalc match { + case Some(Right(msg)) => abort(msg) + case _ => abort(s"Unsupported operation $cachedTpe") + } } } @@ -1055,7 +1108,7 @@ trait GeneralMacros { } //directly using the java lib `require` resulted in compiler crash, so we use wrapped require instead case CalcNLit(Primitive.String, msg, _) => cArg match { - case CalcUnknown(t, _) if t.typeSymbol == symbolOf[Warn] => + case CalcUnknown(t, _, false) if t.typeSymbol == symbolOf[Warn] => CalcNLit(Primitive.Boolean, q"""{println(${buildWarningMsg(msg)}); false}""") case _ => CalcNLit(Primitive.Boolean, q"{_root_.singleton.ops.impl._require(false, $msg); false}") @@ -1065,7 +1118,7 @@ trait GeneralMacros { case CalcNLit(Primitive.Boolean, cond, _) => b match { //directly using the java lib `require` resulted in compiler crash, so we use wrapped require instead case CalcVal(msg : String, msgt) => cArg match { - case CalcUnknown(t, _) if t == symbolOf[Warn] => + case CalcUnknown(t, _, false) if t == symbolOf[Warn] => CalcNLit(Primitive.Boolean, q"""{ if ($cond) true @@ -1366,7 +1419,7 @@ trait GeneralMacros { case funcTypes.PrefixMatch => PrefixMatch case funcTypes.ReplaceFirstMatch => ReplaceFirstMatch case funcTypes.ReplaceAllMatches => ReplaceAllMatches - case _ => abort(s"Unsupported $funcType[$a, $b, $cArg]") + case _ => unsupported() } } @@ -1381,6 +1434,7 @@ trait GeneralMacros { else genOpTreeNat(opTpe, t) case (_, CalcLit(_, t)) => genOpTreeLit(opTpe, t) case (funcTypes.AcceptNonLiteral | funcTypes.GetArg, t : CalcNLit) => genOpTreeNLit(opTpe, t) + case (_, t @ CalcUnknown(_,_,true)) => genOpTreeUnknown(opTpe, t) case (funcTypes.GetArg, t : CalcUnknown) => genOpTreeUnknown(opTpe, t) case (_, t: CalcNLit) => abort("Calculation has returned a non-literal type/value.\nTo accept non-literal values, use `AcceptNonLiteral[T]`.") @@ -1500,7 +1554,7 @@ trait GeneralMacros { } } - val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None)) + val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) q""" (new $chkSym[$condTpe, $msgTpe, $chkArgTpe]($outTree.asInstanceOf[$outTpe])) @@ -1566,7 +1620,7 @@ trait GeneralMacros { } } - val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None)) + val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) q""" (new $chkSym[$condTpe, $msgTpe, $chkArgTpe, $paramFaceTpe, $paramTpe]($outTree.asInstanceOf[$outTpe])) diff --git a/src/main/scala/singleton/ops/impl/Op.scala b/src/main/scala/singleton/ops/impl/Op.scala index 1544434e..ca400f7c 100644 --- a/src/main/scala/singleton/ops/impl/Op.scala +++ b/src/main/scala/singleton/ops/impl/Op.scala @@ -6,7 +6,11 @@ trait HasOut extends Any with Serializable { type Out } -trait Op extends HasOut { +trait HasOutValue extends HasOut { + val value : Out +} + +trait Op extends HasOutValue { type OutWide type Out type OutNat <: Nat @@ -29,7 +33,7 @@ protected[singleton] object OpGen { implicit def getValue[O <: Op, Out](o : Aux[O, Out]) : Out = o.value } -trait OpCast[T, O <: Op] extends HasOut {type Out <: T; val value : Out} +trait OpCast[T, O <: Op] extends HasOutValue {type Out <: T} @scala.annotation.implicitNotFound(msg = "Unable to prove type argument is a Nat.") diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala new file mode 100644 index 00000000..cf772e25 --- /dev/null +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -0,0 +1,55 @@ +package singleton.ops + +import org.scalacheck.Properties +import singleton.TestUtils._ + +class OpInterceptSpec extends Properties("OpInterceptSpec") { + + trait Vec[A0, A1] + + implicit def `Vec+`[VL0, VL1, VR0, VR1, VO0, VO1]( + implicit + opL : OpAuxGen[VL0 + VR0, VO0], + opR : OpAuxGen[VL1 + VR1, VO1], + result : OpIntercept.CacheResult[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[VO0, VO1]] + ) : OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] = ??? + + implicit def `Vec==`[VL0, VL1, VR0, VR1, EqOut]( + implicit + op : OpAuxGen[(VL0 == VR0) && (VL1 == VR1), EqOut], + result : OpIntercept.CacheResult[Vec[VL0, VL1] == Vec[VR0, VR1], EqOut] + ) : OpIntercept[Vec[VL0, VL1] == Vec[VR0, VR1]] = ??? + + + property("Custom Vec Equality OK") = wellTyped { + val eq1 = shapeless.the[Vec[1, 2] == Vec[1, 2]] + val eq2 = shapeless.the[Vec[1, 2] == Vec[1, 1]] + implicitly[eq1.Out =:= true] + implicitly[eq2.Out =:= false] + } + + property("Custom Vec Addition OK") = wellTyped { + val add2 = shapeless.the[Vec[1, 2] + Vec[3, 8]] + val add3 = shapeless.the[Vec[1, 2] + Vec[3, 8] + Vec[20, 20]] + implicitly[add2.Out =:= Vec[4, 10]] + implicitly[add3.Out =:= Vec[24, 30]] + val add23 = shapeless.the[add2.Out + add3.Out] + implicitly[add23.Out =:= Vec[28, 40]] + } + + trait FibId + type Fib[P] = impl.OpMacro[FibId, P, 0, 0] + implicit def doFib[P, Out]( + implicit + op : OpAuxGen[ITE[P == 0, 0, ITE[P == 1, 1, Fib[P - 1] + Fib[P - 2]]], Out], + result : OpIntercept.CacheResult[Fib[P], Out] + ) : OpIntercept[Fib[P]] = ??? + + + property("Custom Fibonacci Op OK") = wellTyped { + val fib4 = shapeless.the[Fib[4]] + implicitly[fib4.Out =:= 3] + val fib10 = shapeless.the[Fib[10]] + implicitly[fib10.Out =:= 55] + } +} From a56f62b293df88e1f974d2b5b14af8565a7265b2 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 21 Jun 2020 00:54:09 +0300 Subject: [PATCH 02/11] Assume caching to be unique to the running operation without specifying it --- src/main/scala/singleton/ops/OpIntercept.scala | 9 ++++----- src/main/scala/singleton/ops/impl/GeneralMacros.scala | 5 ++--- src/test/scala/singleton/ops/OpInterceptSpec.scala | 6 +++--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala index d15362f1..32750534 100644 --- a/src/main/scala/singleton/ops/OpIntercept.scala +++ b/src/main/scala/singleton/ops/OpIntercept.scala @@ -8,14 +8,13 @@ trait OpIntercept[Op <: HasOut] extends HasOut object OpIntercept { type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0} @implicitNotFound("Failed to cache op ${Op} with result ${Out}") - trait CacheResult[Op <: HasOut, Out] + trait CacheResult[Out] object CacheResult { - implicit def call[Op <: HasOut, Out] : CacheResult[Op, Out] = macro Macro.materializeCacheResult[Op, Out] + implicit def call[Out] : CacheResult[Out] = macro Macro.materializeCacheResult[Out] final class Macro(val c: whitebox.Context) extends GeneralMacros { def materializeCacheResult[ - Op : c.WeakTypeTag, - Out: c.WeakTypeTag, - ]: c.Tree = cacheOpInterceptResult[Op, Out] + Out: c.WeakTypeTag + ]: c.Tree = cacheOpInterceptResult[Out] } } diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala index e0495ff0..c34a6a9e 100644 --- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala +++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala @@ -910,12 +910,11 @@ trait GeneralMacros { /////////////////////////////////////////////////////////////////////////////////////////// // OpInterept Result Caching /////////////////////////////////////////////////////////////////////////////////////////// - def cacheOpInterceptResult[Op, Out](implicit ev0: c.WeakTypeTag[Op], ev1: c.WeakTypeTag[Out]) : Tree = { - val opTpe = weakTypeOf[Op] + def cacheOpInterceptResult[Out](implicit ev0: c.WeakTypeTag[Out]) : Tree = { val outTpe = weakTypeOf[Out] val outCalc = TypeCalc(outTpe) CalcCache.setOpInterceptCalc(outCalc) - q"new _root_.singleton.ops.OpIntercept.CacheResult[$opTpe, $outTpe]{}" + q"new _root_.singleton.ops.OpIntercept.CacheResult[$outTpe]{}" } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala index cf772e25..129a553b 100644 --- a/src/test/scala/singleton/ops/OpInterceptSpec.scala +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -11,13 +11,13 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { implicit opL : OpAuxGen[VL0 + VR0, VO0], opR : OpAuxGen[VL1 + VR1, VO1], - result : OpIntercept.CacheResult[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[VO0, VO1]] + result : OpIntercept.CacheResult[Vec[VO0, VO1]] ) : OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] = ??? implicit def `Vec==`[VL0, VL1, VR0, VR1, EqOut]( implicit op : OpAuxGen[(VL0 == VR0) && (VL1 == VR1), EqOut], - result : OpIntercept.CacheResult[Vec[VL0, VL1] == Vec[VR0, VR1], EqOut] + result : OpIntercept.CacheResult[EqOut] ) : OpIntercept[Vec[VL0, VL1] == Vec[VR0, VR1]] = ??? @@ -42,7 +42,7 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { implicit def doFib[P, Out]( implicit op : OpAuxGen[ITE[P == 0, 0, ITE[P == 1, 1, Fib[P - 1] + Fib[P - 2]]], Out], - result : OpIntercept.CacheResult[Fib[P], Out] + result : OpIntercept.CacheResult[Out] ) : OpIntercept[Fib[P]] = ??? From 14d3c9fb3a61b7035233fe93905adcf9474e33b4 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 21 Jun 2020 00:58:54 +0300 Subject: [PATCH 03/11] fix calculation missing unknown return --- src/main/scala/singleton/ops/impl/GeneralMacros.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala index c34a6a9e..15ccebb4 100644 --- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala +++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala @@ -952,7 +952,11 @@ trait GeneralMacros { case TypecheckException(pos, msg) => CalcCache.getOpInterceptCalc match { case Some(Right(msg)) => abort(msg) - case _ => abort(s"Unsupported operation $cachedTpe") + case _ => + (a, b) match { + case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $cachedTpe") + case _ => CalcUnknown(funcType.toType, None, opIntercept = false) + } } } } From 4daee9a29e597b9485ac23ba6d9b4f6838f97ca6 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 21 Jun 2020 01:18:31 +0300 Subject: [PATCH 04/11] Added error cases coverage and fixed literals for pre-2.13 scala --- .../scala/singleton/ops/OpInterceptSpec.scala | 56 ++++++++++++++----- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala index 129a553b..f3a6932d 100644 --- a/src/test/scala/singleton/ops/OpInterceptSpec.scala +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -1,6 +1,7 @@ package singleton.ops import org.scalacheck.Properties +import shapeless.test.illTyped import singleton.TestUtils._ class OpInterceptSpec extends Properties("OpInterceptSpec") { @@ -22,34 +23,59 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { property("Custom Vec Equality OK") = wellTyped { - val eq1 = shapeless.the[Vec[1, 2] == Vec[1, 2]] - val eq2 = shapeless.the[Vec[1, 2] == Vec[1, 1]] - implicitly[eq1.Out =:= true] - implicitly[eq2.Out =:= false] + val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]] + val eq2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`1`.T]] + implicitly[eq1.Out =:= W.`true`.T] + implicitly[eq2.Out =:= W.`false`.T] } property("Custom Vec Addition OK") = wellTyped { - val add2 = shapeless.the[Vec[1, 2] + Vec[3, 8]] - val add3 = shapeless.the[Vec[1, 2] + Vec[3, 8] + Vec[20, 20]] - implicitly[add2.Out =:= Vec[4, 10]] - implicitly[add3.Out =:= Vec[24, 30]] + val add2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T]] + val add3 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T] + Vec[W.`20`.T, W.`20`.T]] + implicitly[add2.Out =:= Vec[W.`4`.T, W.`10`.T]] + implicitly[add3.Out =:= Vec[W.`24`.T, W.`30`.T]] val add23 = shapeless.the[add2.Out + add3.Out] - implicitly[add23.Out =:= Vec[28, 40]] + implicitly[add23.Out =:= Vec[W.`28`.T, W.`40`.T]] } trait FibId - type Fib[P] = impl.OpMacro[FibId, P, 0, 0] + type Fib[P] = impl.OpMacro[FibId, P, W.`0`.T, W.`0`.T] implicit def doFib[P, Out]( implicit - op : OpAuxGen[ITE[P == 0, 0, ITE[P == 1, 1, Fib[P - 1] + Fib[P - 2]]], Out], + op : OpAuxGen[ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]], Out], result : OpIntercept.CacheResult[Out] ) : OpIntercept[Fib[P]] = ??? property("Custom Fibonacci Op OK") = wellTyped { - val fib4 = shapeless.the[Fib[4]] - implicitly[fib4.Out =:= 3] - val fib10 = shapeless.the[Fib[10]] - implicitly[fib10.Out =:= 55] + val fib4 = shapeless.the[Fib[W.`4`.T]] + implicitly[fib4.Out =:= W.`3`.T] + val fib10 = shapeless.the[Fib[W.`10`.T]] + implicitly[fib10.Out =:= W.`55`.T] } + + + trait FooOpId + type FooOp[C, M] = impl.OpMacro[FooOpId, C, M, W.`0`.T] + implicit def FooOp[C, M]( + implicit + r : RequireMsg[C, M], + result : OpIntercept.CacheResult[W.`true`.T] + ) : OpIntercept[FooOp[C, M]] = ??? + + property("Error Message Propagation") = wellTyped { + illTyped("""shapeless.the[FooOp[W.`false`.T, W.`"this is a test"`.T]]""", "this is a test") + } + + trait BarOpId + type BarOp[C, M] = impl.OpMacro[BarOpId, C, M, W.`0`.T] + implicit def BarOp[C, M]( + implicit + op : C + M + ) : OpIntercept[BarOp[C, M]] = ??? + + property("Missing Caching Error") = wellTyped { + illTyped("""shapeless.the[BarOp[W.`1`.T, W.`2`.T]]""", "Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`") + } + } From b87d834fc9084c09d0fea5bddcb6753553fcc368 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Sun, 21 Jun 2020 01:28:57 +0300 Subject: [PATCH 05/11] fix minor issue --- src/main/scala/singleton/ops/OpIntercept.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala index 32750534..a30f13e4 100644 --- a/src/main/scala/singleton/ops/OpIntercept.scala +++ b/src/main/scala/singleton/ops/OpIntercept.scala @@ -4,10 +4,9 @@ import impl._ import scala.annotation.implicitNotFound -trait OpIntercept[Op <: HasOut] extends HasOut +trait OpIntercept[Op <: HasOut] object OpIntercept { - type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0} - @implicitNotFound("Failed to cache op ${Op} with result ${Out}") + @implicitNotFound("Failed to cache with result ${Out}") trait CacheResult[Out] object CacheResult { implicit def call[Out] : CacheResult[Out] = macro Macro.materializeCacheResult[Out] From 6a278b7b8e4b09607a2284f2539222dc29f5c0de Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 22 Jun 2020 02:01:32 +0300 Subject: [PATCH 06/11] changed method of procuring the Out for OpIntercept --- .../scala/singleton/ops/OpIntercept.scala | 17 +--- .../singleton/ops/impl/GeneralMacros.scala | 85 ++++++------------- .../scala/singleton/ops/OpInterceptSpec.scala | 41 +++------ 3 files changed, 42 insertions(+), 101 deletions(-) diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala index a30f13e4..791b1dcd 100644 --- a/src/main/scala/singleton/ops/OpIntercept.scala +++ b/src/main/scala/singleton/ops/OpIntercept.scala @@ -1,20 +1,9 @@ package singleton.ops -import scala.reflect.macros.whitebox import impl._ import scala.annotation.implicitNotFound - -trait OpIntercept[Op <: HasOut] +@implicitNotFound("Missing an `OpIntercept` implicit for the operation ${Op}") +trait OpIntercept[Op <: HasOut] extends HasOut object OpIntercept { - @implicitNotFound("Failed to cache with result ${Out}") - trait CacheResult[Out] - object CacheResult { - implicit def call[Out] : CacheResult[Out] = macro Macro.materializeCacheResult[Out] - final class Macro(val c: whitebox.Context) extends GeneralMacros { - def materializeCacheResult[ - Out: c.WeakTypeTag - ]: c.Tree = cacheOpInterceptResult[Out] - } - - } + type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0} } \ No newline at end of file diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala index 15ccebb4..f0320d7c 100644 --- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala +++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala @@ -8,10 +8,10 @@ private object MacroCache { val cache = mutable.Map.empty[Any, Any] def get(key : Any) : Option[Any] = cache.get(key) def add[V <: Any](key : Any, value : V) : V = {cache += (key -> value); value} - private var opInterceptValue : Option[Any] = None - def setOpInterceptValue(value : Any) : Unit = opInterceptValue = Some(value) - def getOpInterceptValue : Any = opInterceptValue - def clearOpInterceptValue() : Unit = opInterceptValue = None + var errorCache : String = "" + def clearErrorCache() : Unit = errorCache = "" + def setErrorCache(msg : String) : Unit = errorCache = msg + def getErrorMessage : String = errorCache } trait GeneralMacros { val c: whitebox.Context @@ -332,19 +332,6 @@ trait GeneralMacros { VerboseTraversal(s"${GREEN}${BOLD}caching${RESET} $k -> $value") value } - def setOpInterceptCalc(calc : Calc) : Unit = MacroCache.setOpInterceptValue(Left(calc)) - def setOpInterceptError(msg : String) : Unit = MacroCache.setOpInterceptValue(Right(msg)) - def clearOpInterceptCalc() : Unit = MacroCache.clearOpInterceptValue() - def getOpInterceptCalc : Option[Either[Calc, String]] = { - MacroCache.getOpInterceptValue.asInstanceOf[Option[Either[Calc, String]]] match { - case Some(Left(v)) => Some(Left(v match { - case lit : CalcLit => CalcLit(lit.value) //reconstruct internal literal tree - case nlit : CalcNLit => CalcNLit(nlit.primitive, deepCopyTree(nlit.tree)) - case c => c - })) - case v => v - } - } } //////////////////////////////////////////////////////////////////// @@ -566,7 +553,7 @@ trait GeneralMacros { } case _ => //regular cases - opCalc(Some(tp), funcType, aValue, bValue, cValue) match { + opCalc(funcType, aValue, bValue, cValue) match { case (res : CalcVal) => Some(res) case u @ CalcUnknown(_,Some(_), _) => Some(u) //Accept unknown values with a tree case oi @ CalcUnknown(_,_, true) => Some(oi) //Accept unknown op interception @@ -675,7 +662,7 @@ trait GeneralMacros { def abort(msg: String, annotatedSym : Option[TypeSymbol] = defaultAnnotatedSym, position : Position = c.enclosingPosition): Nothing = { VerboseTraversal(s"!!!!!!aborted with: $msg at $annotatedSym, $defaultAnnotatedSym") if (annotatedSym.isDefined) setAnnotation(msg, annotatedSym.get) - CalcCache.setOpInterceptError(msg) //propagating the error in case this is an inner implicit call for OpIntercept + MacroCache.setErrorCache(msg) //propagating the error in case this is an inner implicit call for OpIntercept c.abort(position, msg) } @@ -907,56 +894,38 @@ trait GeneralMacros { } /////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////// - // OpInterept Result Caching - /////////////////////////////////////////////////////////////////////////////////////////// - def cacheOpInterceptResult[Out](implicit ev0: c.WeakTypeTag[Out]) : Tree = { - val outTpe = weakTypeOf[Out] - val outCalc = TypeCalc(outTpe) - CalcCache.setOpInterceptCalc(outCalc) - q"new _root_.singleton.ops.OpIntercept.CacheResult[$outTpe]{}" - } - /////////////////////////////////////////////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////////////////// // Three operands (Generic) /////////////////////////////////////////////////////////////////////////////////////////// def materializeOpGen[F](implicit ev0: c.WeakTypeTag[F]): MaterializeOpAuxGen = new MaterializeOpAuxGen(weakTypeOf[F]) - def opCalc(opTpe : Option[Type], funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = { + def opCalc(funcType : TypeSymbol, aCalc : => Calc, bCalc : => Calc, cCalc : => Calc) : Calc = { lazy val a = aCalc lazy val b = bCalc lazy val cArg = cCalc def unsupported() : Calc = { - val cachedTpe = opTpe.get match { - case TypeRef(pre, sym, args) => c.internal.typeRef(pre, sym, List(funcType.toType, a.tpe, b.tpe, cArg.tpe)) - } - //calling OpIntercept for the operation should cache the expected result if executed correctly - CalcCache.clearOpInterceptCalc() - val implicitlyTree = q"implicitly[_root_.singleton.ops.OpIntercept[$cachedTpe]]" + val opMacroTpe = typeOf[OpMacro[_,_,_,_]].typeConstructor + val opTpe = appliedType(opMacroTpe, List(funcType.toType, a.tpe, b.tpe, cArg.tpe)) + val interceptTpe = typeOf[singleton.ops.OpIntercept[_]].typeConstructor + MacroCache.clearErrorCache() try { - c.typecheck(implicitlyTree, silent = false) - val cachedCalc = CalcCache.getOpInterceptCalc match { - case Some(calc) => calc - case None => abort("Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`") - } - CalcCache.clearOpInterceptCalc() - cachedCalc match { - case Left(t : CalcUnknown) => - t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later - case Left(t) => t - case Right(msg) => abort(msg) + val itree = c.inferImplicitValue ( + appliedType(interceptTpe, List(opTpe)), + silent = false + ) + TypeCalc(itree.tpe.decls.head.info) match { + case t : CalcUnknown => t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later + case t => t } } catch { - case TypecheckException(pos, msg) => - CalcCache.getOpInterceptCalc match { - case Some(Right(msg)) => abort(msg) - case _ => - (a, b) match { - case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $cachedTpe") - case _ => CalcUnknown(funcType.toType, None, opIntercept = false) - } + case TypecheckException(_, _) => + MacroCache.getErrorMessage match { + case m if m.nonEmpty => abort(m) + case _ => (a, b) match { + case (_ : CalcVal, _ : CalcVal) => abort(s"Unsupported operation $opTpe") + case _ => CalcUnknown(funcType.toType, None, opIntercept = false) + } } } } @@ -1557,7 +1526,7 @@ trait GeneralMacros { } } - val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) + val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) q""" (new $chkSym[$condTpe, $msgTpe, $chkArgTpe]($outTree.asInstanceOf[$outTpe])) @@ -1623,7 +1592,7 @@ trait GeneralMacros { } } - val reqCalc = opCalc(None, funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) + val reqCalc = opCalc(funcTypes.Require, condCalc, msgCalc, CalcUnknown(typeOf[NoSym], None, opIntercept = false)) q""" (new $chkSym[$condTpe, $msgTpe, $chkArgTpe, $paramFaceTpe, $paramTpe]($outTree.asInstanceOf[$outTpe])) diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala index f3a6932d..2d237f0c 100644 --- a/src/test/scala/singleton/ops/OpInterceptSpec.scala +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -8,19 +8,16 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { trait Vec[A0, A1] - implicit def `Vec+`[VL0, VL1, VR0, VR1, VO0, VO1]( + implicit def `Vec+`[VL0, VL1, VR0, VR1]( implicit - opL : OpAuxGen[VL0 + VR0, VO0], - opR : OpAuxGen[VL1 + VR1, VO1], - result : OpIntercept.CacheResult[Vec[VO0, VO1]] - ) : OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] = ??? + opL : VL0 + VR0, + opR : VL1 + VR1 + ) : OpIntercept.Aux[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[opL.Out, opR.Out]] = ??? - implicit def `Vec==`[VL0, VL1, VR0, VR1, EqOut]( + implicit def `Vec==`[VL0, VL1, VR0, VR1]( implicit - op : OpAuxGen[(VL0 == VR0) && (VL1 == VR1), EqOut], - result : OpIntercept.CacheResult[EqOut] - ) : OpIntercept[Vec[VL0, VL1] == Vec[VR0, VR1]] = ??? - + op : (VL0 == VR0) && (VL1 == VR1) + ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ??? property("Custom Vec Equality OK") = wellTyped { val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]] @@ -38,14 +35,13 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { implicitly[add23.Out =:= Vec[W.`28`.T, W.`40`.T]] } + trait FibId type Fib[P] = impl.OpMacro[FibId, P, W.`0`.T, W.`0`.T] - implicit def doFib[P, Out]( + implicit def doFib[P]( implicit - op : OpAuxGen[ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]], Out], - result : OpIntercept.CacheResult[Out] - ) : OpIntercept[Fib[P]] = ??? - + op : ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]] + ) : OpIntercept.Aux[Fib[P], op.Out] = ??? property("Custom Fibonacci Op OK") = wellTyped { val fib4 = shapeless.the[Fib[W.`4`.T]] @@ -59,23 +55,10 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { type FooOp[C, M] = impl.OpMacro[FooOpId, C, M, W.`0`.T] implicit def FooOp[C, M]( implicit - r : RequireMsg[C, M], - result : OpIntercept.CacheResult[W.`true`.T] + r : RequireMsg[C, M] ) : OpIntercept[FooOp[C, M]] = ??? property("Error Message Propagation") = wellTyped { illTyped("""shapeless.the[FooOp[W.`false`.T, W.`"this is a test"`.T]]""", "this is a test") } - - trait BarOpId - type BarOp[C, M] = impl.OpMacro[BarOpId, C, M, W.`0`.T] - implicit def BarOp[C, M]( - implicit - op : C + M - ) : OpIntercept[BarOp[C, M]] = ??? - - property("Missing Caching Error") = wellTyped { - illTyped("""shapeless.the[BarOp[W.`1`.T, W.`2`.T]]""", "Missing a result cache for OpIntercept. Make sure you set `OpIntercept.CacheResult`") - } - } From aa14d5edb80677859c6dbd31ec33f8139cc50d85 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 22 Jun 2020 02:37:37 +0300 Subject: [PATCH 07/11] add value support --- .../scala/singleton/ops/OpIntercept.scala | 2 +- .../singleton/ops/impl/GeneralMacros.scala | 2 +- .../scala/singleton/ops/OpInterceptSpec.scala | 23 +++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/main/scala/singleton/ops/OpIntercept.scala b/src/main/scala/singleton/ops/OpIntercept.scala index 791b1dcd..35a03d64 100644 --- a/src/main/scala/singleton/ops/OpIntercept.scala +++ b/src/main/scala/singleton/ops/OpIntercept.scala @@ -3,7 +3,7 @@ import impl._ import scala.annotation.implicitNotFound @implicitNotFound("Missing an `OpIntercept` implicit for the operation ${Op}") -trait OpIntercept[Op <: HasOut] extends HasOut +trait OpIntercept[Op <: HasOut] extends HasOutValue object OpIntercept { type Aux[Op <: HasOut, Out0] = OpIntercept[Op]{type Out = Out0} } \ No newline at end of file diff --git a/src/main/scala/singleton/ops/impl/GeneralMacros.scala b/src/main/scala/singleton/ops/impl/GeneralMacros.scala index f0320d7c..18687b56 100644 --- a/src/main/scala/singleton/ops/impl/GeneralMacros.scala +++ b/src/main/scala/singleton/ops/impl/GeneralMacros.scala @@ -915,7 +915,7 @@ trait GeneralMacros { silent = false ) TypeCalc(itree.tpe.decls.head.info) match { - case t : CalcUnknown => t.copy(opIntercept = true) //the unknown result must be marked properly so we allow it later + case t : CalcUnknown => t.copy(treeOption = Some(c.untypecheck(q"$itree.value")),opIntercept = true) //the unknown result must be marked properly so we allow it later case t => t } } catch { diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala index 2d237f0c..9a1bd173 100644 --- a/src/test/scala/singleton/ops/OpInterceptSpec.scala +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -6,33 +6,41 @@ import singleton.TestUtils._ class OpInterceptSpec extends Properties("OpInterceptSpec") { - trait Vec[A0, A1] + trait Vec[A0, A1] { + def show(implicit a0 : ValueOf[A0], a1 : ValueOf[A1]) : String = s"Vec[${valueOf[A0]}, ${valueOf[A1]}]" + } implicit def `Vec+`[VL0, VL1, VR0, VR1]( implicit opL : VL0 + VR0, opR : VL1 + VR1 - ) : OpIntercept.Aux[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[opL.Out, opR.Out]] = ??? + ) : OpIntercept.Aux[Vec[VL0, VL1] + Vec[VR0, VR1], Vec[opL.Out, opR.Out]] = //Vec is not a singleton value, so we need to instantiate OpIntercept + new OpIntercept[Vec[VL0, VL1] + Vec[VR0, VR1]] { + type Out = Vec[opL.Out, opR.Out] + val value : Out = new Vec[opL.Out, opR.Out]{} + } implicit def `Vec==`[VL0, VL1, VR0, VR1]( implicit op : (VL0 == VR0) && (VL1 == VR1) - ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ??? + ) : OpIntercept.Aux[Vec[VL0, VL1] == Vec[VR0, VR1], op.Out] = ??? //No need to instantiate when a singleton value is returned - property("Custom Vec Equality OK") = wellTyped { + property("Custom Vec Equality OK") = { val eq1 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`2`.T]] val eq2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] == Vec[W.`1`.T, W.`1`.T]] implicitly[eq1.Out =:= W.`true`.T] implicitly[eq2.Out =:= W.`false`.T] + eq1.value == true } - property("Custom Vec Addition OK") = wellTyped { + property("Custom Vec Addition OK") = { val add2 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T]] val add3 = shapeless.the[Vec[W.`1`.T, W.`2`.T] + Vec[W.`3`.T, W.`8`.T] + Vec[W.`20`.T, W.`20`.T]] implicitly[add2.Out =:= Vec[W.`4`.T, W.`10`.T]] implicitly[add3.Out =:= Vec[W.`24`.T, W.`30`.T]] val add23 = shapeless.the[add2.Out + add3.Out] implicitly[add23.Out =:= Vec[W.`28`.T, W.`40`.T]] + add2.value.show == "Vec[4, 10]" } @@ -41,13 +49,14 @@ class OpInterceptSpec extends Properties("OpInterceptSpec") { implicit def doFib[P]( implicit op : ITE[P == W.`0`.T, W.`0`.T, ITE[P == W.`1`.T, W.`1`.T, Fib[P - W.`1`.T] + Fib[P - W.`2`.T]]] - ) : OpIntercept.Aux[Fib[P], op.Out] = ??? + ) : OpIntercept.Aux[Fib[P], op.Out] = ??? //No need to instantiate when a singleton value is returned - property("Custom Fibonacci Op OK") = wellTyped { + property("Custom Fibonacci Op OK") = { val fib4 = shapeless.the[Fib[W.`4`.T]] implicitly[fib4.Out =:= W.`3`.T] val fib10 = shapeless.the[Fib[W.`10`.T]] implicitly[fib10.Out =:= W.`55`.T] + fib10.value == 55 } From 6e5868a8ab156d7c58301d43dfdbf21f04f0d002 Mon Sep 17 00:00:00 2001 From: Oron Port Date: Mon, 22 Jun 2020 02:46:37 +0300 Subject: [PATCH 08/11] fixed test-case for scala pre-2.13 --- src/test/scala/singleton/ops/OpInterceptSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/scala/singleton/ops/OpInterceptSpec.scala b/src/test/scala/singleton/ops/OpInterceptSpec.scala index 9a1bd173..883bda45 100644 --- a/src/test/scala/singleton/ops/OpInterceptSpec.scala +++ b/src/test/scala/singleton/ops/OpInterceptSpec.scala @@ -7,7 +7,7 @@ import singleton.TestUtils._ class OpInterceptSpec extends Properties("OpInterceptSpec") { trait Vec[A0, A1] { - def show(implicit a0 : ValueOf[A0], a1 : ValueOf[A1]) : String = s"Vec[${valueOf[A0]}, ${valueOf[A1]}]" + def show(implicit a0 : Id[A0], a1 : Id[A1]) : String = s"Vec[${a0.value}, ${a1.value}]" } implicit def `Vec+`[VL0, VL1, VR0, VR1]( From a9db1b8447a6a5c1b2df48cdfdebae3824bc5c47 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Tue, 23 Jun 2020 17:48:07 -0700 Subject: [PATCH 09/11] initial draft for Rational support --- src/main/scala/singleton/ops/rational.scala | 222 ++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 src/main/scala/singleton/ops/rational.scala diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala new file mode 100644 index 00000000..7d0728ee --- /dev/null +++ b/src/main/scala/singleton/ops/rational.scala @@ -0,0 +1,222 @@ +package singleton.ops + +import singleton.ops._ +import singleton.ops.impl.{OpCast, OpGen, OpInt, OpMacro} + +object rational { + /** Represents a rational number + * + * @tparam N the numerator + * @tparam D the denominator + */ + trait Rational[N, D] { + // currently only XInt is supported, + // other types such as XLong could be added with additional implicit rules + def n(implicit nv: Id[N]): nv.Out = nv.value + def d(implicit dv: Id[D]): dv.Out = dv.value + def show(implicit nv: Id[N], dv: Id[D]): String = s"Rational(${n}, ${d})" + } + + private trait IsRationalImpl[P] { + type Out + } + private trait IsRationalImplDefault { + type Aux[P, O] = IsRationalImpl[P] { type Out = O } + implicit def isRationalFalse[P]: Aux[P, false] = + new IsRationalImpl[P] { + type Out = false + } + } + private object IsRationalImpl extends IsRationalImplDefault { + implicit def isRationalTrue[N, D]: Aux[Rational[N, D], true] = + new IsRationalImpl[Rational[N, D]] { + type Out = true + } + } + + trait IsRationalOpId + type IsRational[P] = OpMacro[IsRationalOpId, P, W.`0`.T, W.`0`.T] + + implicit def doIsRational[P, T](implicit + tst: IsRationalImpl.Aux[P, T]): OpIntercept.Aux[IsRational[P], T] = ??? + + trait ToRationalOpId + type ToRational[P] = OpMacro[ToRationalOpId, P, W.`0`.T, W.`0`.T] + + implicit def toRationalFromRat[ + N <: XInt, D <: XInt, + SN <: XInt, SD <: XInt]( + implicit + sim: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] + ): OpIntercept.Aux[ToRational[Rational[N, D]], Rational[SN, SD]] = + new OpIntercept[ToRational[Rational[N, D]]] { + type Out = Rational[SN, SD] + val value: Out = new Rational[SN, SD] {} + } + + implicit def toRationalFromInt[N <: XInt]: OpIntercept.Aux[ToRational[N], Rational[N, W.`1`.T]] = + new OpIntercept[ToRational[N]] { + type Out = Rational[N, W.`1`.T] + val value: Out = new Rational[N, W.`1`.T] {} + } + + implicit def doRationalNegate[N <: XInt, D <: XInt, NN <: XInt](implicit + neg: OpInt.Aux[Negate[N], NN]): OpIntercept.Aux[Negate[Rational[N, D]], Rational[NN, D]] = + new OpIntercept[Negate[Rational[N, D]]] { + type Out = Rational[NN, D] + val value: Out = new Rational[NN, D] {} + } + + implicit def doRationalAdd[ + LHS, RHS, + LN <: XInt, LD <: XInt, + RN <: XInt, RD <: XInt, + LNRD <: XInt, RNLD <: XInt, + N <: XInt, D <: XInt, + SN <: XInt, SD <: XInt]( + implicit + rat: Require[IsRational[LHS] || IsRational[RHS]], + lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], + rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + ev0: OpInt.Aux[LN * RD, LNRD], + ev1: OpInt.Aux[RN * LD, RNLD], + ev2: OpInt.Aux[LNRD + RNLD, N], + ev3: OpInt.Aux[LD * RD, D], + ev4: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]], + ): OpIntercept.Aux[LHS + RHS, Rational[SN, SD]] = + new OpIntercept[LHS + RHS] { + type Out = Rational[SN, SD] + val value: Out = new Rational[SN, SD] {} + } + + implicit def doRationalSubtract[ + LHS, RHS, + LN <: XInt, LD <: XInt, + RN <: XInt, RD <: XInt, RNN <: XInt, + SN <: XInt, SD <: XInt]( + implicit + rat: Require[IsRational[LHS] || IsRational[RHS]], + lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], + rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + neg: OpInt.Aux[Negate[RN], RNN], + add: OpGen.Aux[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]] + ): OpIntercept.Aux[LHS - RHS, Rational[SN, SD]] = + new OpIntercept[LHS - RHS] { + type Out = Rational[SN, SD] + val value: Out = new Rational[SN, SD] {} + } + + implicit def doRationalMultiply[ + LHS, RHS, + LN <: XInt, LD <: XInt, + RN <: XInt, RD <: XInt, + N <: XInt, D <: XInt, + SN <: XInt, SD <: XInt]( + implicit + rat: Require[IsRational[LHS] || IsRational[RHS]], + lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], + rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + ev0: OpInt.Aux[LN * RN, N], + ev1: OpInt.Aux[LD * RD, D], + ev2: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] + ): OpIntercept.Aux[LHS * RHS, Rational[SN, SD]] = + new OpIntercept[LHS * RHS] { + type Out = Rational[SN, SD] + val value: Out = new Rational[SN, SD] {} + } + + implicit def doRationalDivide[ + LHS, RHS, + LN <: XInt, LD <: XInt, + RN <: XInt, RD <: XInt, + SN <: XInt, SD <: XInt]( + implicit + rat: Require[IsRational[LHS] || IsRational[RHS]], + lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], + rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + mul: OpGen.Aux[Rational[LN, LD] * Rational[RD, RN], Rational[SN, SD]] + ): OpIntercept.Aux[LHS / RHS, Rational[SN, SD]] = + new OpIntercept[LHS / RHS] { + type Out = Rational[SN, SD] + val value: Out = new Rational[SN, SD] {} + } + + trait GCDOpId + type GCD[A, B] = OpMacro[GCDOpId, A, B, W.`0`.T] + + private type gcdErrorMsg = W.`"GCD requires positive integers"`.T + + implicit def doGCDforBasisCase[A <: XInt, B <: XInt, Rem <: XInt](implicit + ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg], + ev1: OpInt.Aux[A % B, Rem], + ev2: Require[Rem == W.`0`.T]): OpIntercept.Aux[GCD[A, B], B] = ??? + + implicit def doGCDforAgeB[A <: XInt, B <: XInt,Rem <: XInt, D <: XInt](implicit + ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg], + ev1: OpInt.Aux[A % B, Rem], + ev2: Require[Rem != W.`0`.T], + ev3: OpInt.Aux[GCD[B, Rem], D]): OpIntercept.Aux[GCD[A, B], D] = ??? + + implicit def doGCDforAltB[A <: XInt, B <: XInt, Rem <: XInt, D <: XInt](implicit + ev0: RequireMsg[(A < B) && (A > W.`0`.T), gcdErrorMsg], + ev1: OpInt.Aux[GCD[B, A], D]): OpIntercept.Aux[GCD[A, B], D] = ??? + + trait SimplifyOpId + type Simplify[F] = OpMacro[SimplifyOpId, F, W.`0`.T, W.`0`.T] + + private type simplifyErrorMsg = W.`"Simplify requires non-zero denominator"`.T + + implicit def doSimplifyPositive[ + N <: XInt, D <: XInt, + C <: XInt, + SN <: XInt, SD <: XInt]( + implicit + ev0: RequireMsg[(N > W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg], + gcd: OpInt.Aux[GCD[N, D], C], + n: OpInt.Aux[N / C, SN], + d: OpInt.Aux[D / C, SD] + ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = + new OpIntercept[Simplify[Rational[N, D]]] { + type Out = Rational[SN, SD] + val value = new Rational[SN, SD] {} + } + + implicit def doSimplifyNegative[ + N <: XInt, D <: XInt, + F <: Rational[_, _], + SNF <: Rational[_, _], + SN <: XInt, SD <: XInt]( + implicit + ev0: RequireMsg[(N < W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg], + ev1: OpGen.Aux[Negate[Rational[N, D]], F], + ev2: OpGen.Aux[Simplify[F], SNF], + ev3: OpGen.Aux[Negate[SNF], Rational[SN, SD]] + ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = + new OpIntercept[Simplify[Rational[N, D]]] { + type Out = Rational[SN, SD] + val value = new Rational[SN, SD] {} + } + + implicit def doSimplifyZero[D <: XInt](implicit + nz: RequireMsg[D > W.`0`.T, simplifyErrorMsg] + ): OpIntercept.Aux[Simplify[Rational[W.`0`.T, D]], Rational[W.`0`.T, W.`1`.T]] = + new OpIntercept[Simplify[Rational[W.`0`.T, D]]] { + type Out = Rational[W.`0`.T, W.`1`.T] + val value = new Rational[W.`0`.T, W.`1`.T] {} + } + + implicit def doSimplifyNegDenom[ + N <: XInt, D <: XInt, + NN <: XInt, ND <: XInt, + SN <: XInt, SD <: XInt]( + implicit + bn: RequireMsg[D < W.`0`.T, simplifyErrorMsg], + nn: OpInt.Aux[Negate[N], NN], + nd: OpInt.Aux[Negate[D], ND], + sf: OpGen.Aux[Simplify[Rational[NN, ND]], Rational[SN, SD]] + ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = + new OpIntercept[Simplify[Rational[N, D]]] { + type Out = Rational[SN, SD] + val value = new Rational[SN, SD] {} + } +} From 14abaa44fc0a9a24002b46957c8aa7efdfca7f04 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Wed, 24 Jun 2020 17:42:23 -0700 Subject: [PATCH 10/11] remove type bounds for GCD --- src/main/scala/singleton/ops/rational.scala | 107 +++++++++++++------- 1 file changed, 72 insertions(+), 35 deletions(-) diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala index 7d0728ee..75007587 100644 --- a/src/main/scala/singleton/ops/rational.scala +++ b/src/main/scala/singleton/ops/rational.scala @@ -17,6 +17,42 @@ object rational { def show(implicit nv: Id[N], dv: Id[D]): String = s"Rational(${n}, ${d})" } + trait AlignTypeOpId + type AlignType[V, P] = OpMacro[AlignTypeOpId, V, P, W.`0`.T] + implicit def doAlignTypeInt[V, P <: XInt](implicit + v: ToInt[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ??? + implicit def doAlignTypeLong[V, P <: XLong](implicit + v: ToLong[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ??? + implicit def doAlignTypeFloat[V, P <: XFloat](implicit + v: ToFloat[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ??? + implicit def doAlignTypeDouble[V, P <: XDouble](implicit + v: ToDouble[V]): OpIntercept.Aux[AlignType[V, P], v.Out] = ??? + + trait IsPositiveOpId + type IsPositive[P] = OpMacro[IsPositiveOpId, P, W.`0`.T, W.`0`.T] + implicit def doIsPositive[P](implicit + tst: P > AlignType[0, P]): OpIntercept.Aux[IsPositive[P], tst.Out] = ??? + + trait IsNegativeOpId + type IsNegative[P] = OpMacro[IsNegativeOpId, P, W.`0`.T, W.`0`.T] + implicit def doIsNegative[P](implicit + tst: P < AlignType[0, P]): OpIntercept.Aux[IsNegative[P], tst.Out] = ??? + + trait IsZeroOpId + type IsZero[P] = OpMacro[IsZeroOpId, P, W.`0`.T, W.`0`.T] + implicit def doIsZero[P](implicit + tst: P == AlignType[0, P]): OpIntercept.Aux[IsZero[P], tst.Out] = ??? + + trait IsNonZeroOpId + type IsNonZero[P] = OpMacro[IsNonZeroOpId, P, W.`0`.T, W.`0`.T] + implicit def doIsNonZero[P](implicit + tst: P != AlignType[0, P]): OpIntercept.Aux[IsNonZero[P], tst.Out] = ??? + + trait IsIntegralOpId + type IsIntegral[P] = OpMacro[IsIntegralOpId, P, W.`0`.T, W.`0`.T] + implicit def doIsIntegral[P](implicit + tst: IsInt[P] || IsLong[P]): OpIntercept.Aux[IsIntegral[P], tst.Out] = ??? + private trait IsRationalImpl[P] { type Out } @@ -47,21 +83,22 @@ object rational { N <: XInt, D <: XInt, SN <: XInt, SD <: XInt]( implicit - sim: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] + sim: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]] ): OpIntercept.Aux[ToRational[Rational[N, D]], Rational[SN, SD]] = new OpIntercept[ToRational[Rational[N, D]]] { type Out = Rational[SN, SD] val value: Out = new Rational[SN, SD] {} } - implicit def toRationalFromInt[N <: XInt]: OpIntercept.Aux[ToRational[N], Rational[N, W.`1`.T]] = + implicit def toRationalFromInt[N, One](implicit + one: OpAuxGen[AlignType[1, N], One]): OpIntercept.Aux[ToRational[N], Rational[N, One]] = new OpIntercept[ToRational[N]] { - type Out = Rational[N, W.`1`.T] - val value: Out = new Rational[N, W.`1`.T] {} + type Out = Rational[N, One] + val value: Out = new Rational[N, One] {} } - implicit def doRationalNegate[N <: XInt, D <: XInt, NN <: XInt](implicit - neg: OpInt.Aux[Negate[N], NN]): OpIntercept.Aux[Negate[Rational[N, D]], Rational[NN, D]] = + implicit def doRationalNegate[N, D, NN](implicit + neg: OpAuxGen[Negate[N], NN]): OpIntercept.Aux[Negate[Rational[N, D]], Rational[NN, D]] = new OpIntercept[Negate[Rational[N, D]]] { type Out = Rational[NN, D] val value: Out = new Rational[NN, D] {} @@ -76,13 +113,13 @@ object rational { SN <: XInt, SD <: XInt]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], - lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], - rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], + rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], ev0: OpInt.Aux[LN * RD, LNRD], ev1: OpInt.Aux[RN * LD, RNLD], ev2: OpInt.Aux[LNRD + RNLD, N], ev3: OpInt.Aux[LD * RD, D], - ev4: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]], + ev4: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]], ): OpIntercept.Aux[LHS + RHS, Rational[SN, SD]] = new OpIntercept[LHS + RHS] { type Out = Rational[SN, SD] @@ -96,10 +133,10 @@ object rational { SN <: XInt, SD <: XInt]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], - lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], - rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], + rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], neg: OpInt.Aux[Negate[RN], RNN], - add: OpGen.Aux[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]] + add: OpAuxGen[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]] ): OpIntercept.Aux[LHS - RHS, Rational[SN, SD]] = new OpIntercept[LHS - RHS] { type Out = Rational[SN, SD] @@ -114,11 +151,11 @@ object rational { SN <: XInt, SD <: XInt]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], - lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], - rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], + lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], + rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], ev0: OpInt.Aux[LN * RN, N], ev1: OpInt.Aux[LD * RD, D], - ev2: OpGen.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] + ev2: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]] ): OpIntercept.Aux[LHS * RHS, Rational[SN, SD]] = new OpIntercept[LHS * RHS] { type Out = Rational[SN, SD] @@ -132,9 +169,9 @@ object rational { SN <: XInt, SD <: XInt]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], - lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]], - rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], - mul: OpGen.Aux[Rational[LN, LD] * Rational[RD, RN], Rational[SN, SD]] + lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], + rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], + mul: OpAuxGen[Rational[LN, LD] * Rational[RD, RN], Rational[SN, SD]] ): OpIntercept.Aux[LHS / RHS, Rational[SN, SD]] = new OpIntercept[LHS / RHS] { type Out = Rational[SN, SD] @@ -144,22 +181,22 @@ object rational { trait GCDOpId type GCD[A, B] = OpMacro[GCDOpId, A, B, W.`0`.T] - private type gcdErrorMsg = W.`"GCD requires positive integers"`.T + private type gcdErrorMsg = W.`"GCD requires positive integral arguments"`.T - implicit def doGCDforBasisCase[A <: XInt, B <: XInt, Rem <: XInt](implicit - ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg], - ev1: OpInt.Aux[A % B, Rem], - ev2: Require[Rem == W.`0`.T]): OpIntercept.Aux[GCD[A, B], B] = ??? + implicit def doGCDforBasisCase[A, B, Rem](implicit + ev0: RequireMsg[IsIntegral[A] && IsIntegral[B] && (A >= B) && IsPositive[B], gcdErrorMsg], + ev1: OpAuxGen[A % B, Rem], + ev2: Require[IsZero[Rem]]): OpIntercept.Aux[GCD[A, B], B] = ??? - implicit def doGCDforAgeB[A <: XInt, B <: XInt,Rem <: XInt, D <: XInt](implicit - ev0: RequireMsg[(A >= B) && (B > W.`0`.T), gcdErrorMsg], - ev1: OpInt.Aux[A % B, Rem], - ev2: Require[Rem != W.`0`.T], - ev3: OpInt.Aux[GCD[B, Rem], D]): OpIntercept.Aux[GCD[A, B], D] = ??? + implicit def doGCDforAgeB[A, B, Rem, D](implicit + ev0: RequireMsg[IsIntegral[A] && IsIntegral[B] && (A >= B) && IsPositive[B], gcdErrorMsg], + ev1: OpAuxGen[A % B, Rem], + ev2: Require[IsNonZero[Rem]], + ev3: OpAuxGen[GCD[B, Rem], D]): OpIntercept.Aux[GCD[A, B], D] = ??? - implicit def doGCDforAltB[A <: XInt, B <: XInt, Rem <: XInt, D <: XInt](implicit - ev0: RequireMsg[(A < B) && (A > W.`0`.T), gcdErrorMsg], - ev1: OpInt.Aux[GCD[B, A], D]): OpIntercept.Aux[GCD[A, B], D] = ??? + implicit def doGCDforAltB[A, B, Rem, D](implicit + ev0: RequireMsg[IsIntegral[A] && IsIntegral[B] && (A < B) && IsPositive[A], gcdErrorMsg], + ev1: OpAuxGen[GCD[B, A], D]): OpIntercept.Aux[GCD[A, B], D] = ??? trait SimplifyOpId type Simplify[F] = OpMacro[SimplifyOpId, F, W.`0`.T, W.`0`.T] @@ -188,9 +225,9 @@ object rational { SN <: XInt, SD <: XInt]( implicit ev0: RequireMsg[(N < W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg], - ev1: OpGen.Aux[Negate[Rational[N, D]], F], - ev2: OpGen.Aux[Simplify[F], SNF], - ev3: OpGen.Aux[Negate[SNF], Rational[SN, SD]] + ev1: OpAuxGen[Negate[Rational[N, D]], F], + ev2: OpAuxGen[Simplify[F], SNF], + ev3: OpAuxGen[Negate[SNF], Rational[SN, SD]] ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = new OpIntercept[Simplify[Rational[N, D]]] { type Out = Rational[SN, SD] @@ -213,7 +250,7 @@ object rational { bn: RequireMsg[D < W.`0`.T, simplifyErrorMsg], nn: OpInt.Aux[Negate[N], NN], nd: OpInt.Aux[Negate[D], ND], - sf: OpGen.Aux[Simplify[Rational[NN, ND]], Rational[SN, SD]] + sf: OpAuxGen[Simplify[Rational[NN, ND]], Rational[SN, SD]] ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = new OpIntercept[Simplify[Rational[N, D]]] { type Out = Rational[SN, SD] From eedb6306229f044a04743b5f7aed4f67aa7503bc Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Thu, 25 Jun 2020 07:13:57 -0700 Subject: [PATCH 11/11] abstract away from XInt, use OpAuxGen --- src/main/scala/singleton/ops/rational.scala | 94 +++++++++------------ 1 file changed, 40 insertions(+), 54 deletions(-) diff --git a/src/main/scala/singleton/ops/rational.scala b/src/main/scala/singleton/ops/rational.scala index 75007587..c48412ff 100644 --- a/src/main/scala/singleton/ops/rational.scala +++ b/src/main/scala/singleton/ops/rational.scala @@ -10,8 +10,6 @@ object rational { * @tparam D the denominator */ trait Rational[N, D] { - // currently only XInt is supported, - // other types such as XLong could be added with additional implicit rules def n(implicit nv: Id[N]): nv.Out = nv.value def d(implicit dv: Id[D]): dv.Out = dv.value def show(implicit nv: Id[N], dv: Id[D]): String = s"Rational(${n}, ${d})" @@ -106,19 +104,19 @@ object rational { implicit def doRationalAdd[ LHS, RHS, - LN <: XInt, LD <: XInt, - RN <: XInt, RD <: XInt, - LNRD <: XInt, RNLD <: XInt, - N <: XInt, D <: XInt, - SN <: XInt, SD <: XInt]( + LN, LD, + RN, RD, + LNRD, RNLD, + N, D, + SN, SD]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], - ev0: OpInt.Aux[LN * RD, LNRD], - ev1: OpInt.Aux[RN * LD, RNLD], - ev2: OpInt.Aux[LNRD + RNLD, N], - ev3: OpInt.Aux[LD * RD, D], + ev0: OpAuxGen[LN * RD, LNRD], + ev1: OpAuxGen[RN * LD, RNLD], + ev2: OpAuxGen[LNRD + RNLD, N], + ev3: OpAuxGen[LD * RD, D], ev4: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]], ): OpIntercept.Aux[LHS + RHS, Rational[SN, SD]] = new OpIntercept[LHS + RHS] { @@ -128,14 +126,14 @@ object rational { implicit def doRationalSubtract[ LHS, RHS, - LN <: XInt, LD <: XInt, - RN <: XInt, RD <: XInt, RNN <: XInt, - SN <: XInt, SD <: XInt]( + LN, LD, + RN, RD, RNN, + SN, SD]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], - neg: OpInt.Aux[Negate[RN], RNN], + neg: OpAuxGen[Negate[RN], RNN], add: OpAuxGen[Rational[LN, LD] + Rational[RNN, RD], Rational[SN, SD]] ): OpIntercept.Aux[LHS - RHS, Rational[SN, SD]] = new OpIntercept[LHS - RHS] { @@ -145,16 +143,16 @@ object rational { implicit def doRationalMultiply[ LHS, RHS, - LN <: XInt, LD <: XInt, - RN <: XInt, RD <: XInt, - N <: XInt, D <: XInt, - SN <: XInt, SD <: XInt]( + LN, LD, + RN, RD, + N, D, + SN, SD]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], rhs: OpAuxGen[ToRational[RHS], Rational[RN, RD]], - ev0: OpInt.Aux[LN * RN, N], - ev1: OpInt.Aux[LD * RD, D], + ev0: OpAuxGen[LN * RN, N], + ev1: OpAuxGen[LD * RD, D], ev2: OpAuxGen[Simplify[Rational[N, D]], Rational[SN, SD]] ): OpIntercept.Aux[LHS * RHS, Rational[SN, SD]] = new OpIntercept[LHS * RHS] { @@ -164,9 +162,9 @@ object rational { implicit def doRationalDivide[ LHS, RHS, - LN <: XInt, LD <: XInt, - RN <: XInt, RD <: XInt, - SN <: XInt, SD <: XInt]( + LN, LD, + RN, RD, + SN, SD]( implicit rat: Require[IsRational[LHS] || IsRational[RHS]], lhs: OpAuxGen[ToRational[LHS], Rational[LN, LD]], @@ -203,28 +201,19 @@ object rational { private type simplifyErrorMsg = W.`"Simplify requires non-zero denominator"`.T - implicit def doSimplifyPositive[ - N <: XInt, D <: XInt, - C <: XInt, - SN <: XInt, SD <: XInt]( - implicit - ev0: RequireMsg[(N > W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg], - gcd: OpInt.Aux[GCD[N, D], C], - n: OpInt.Aux[N / C, SN], - d: OpInt.Aux[D / C, SD] + implicit def doSimplifyPositive[N, D, C, SN, SD](implicit + pos: RequireMsg[IsPositive[N] && IsPositive[D], simplifyErrorMsg], + gcd: OpAuxGen[GCD[N, D], C], + n: OpAuxGen[N / C, SN], + d: OpAuxGen[D / C, SD] ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = new OpIntercept[Simplify[Rational[N, D]]] { type Out = Rational[SN, SD] val value = new Rational[SN, SD] {} } - implicit def doSimplifyNegative[ - N <: XInt, D <: XInt, - F <: Rational[_, _], - SNF <: Rational[_, _], - SN <: XInt, SD <: XInt]( - implicit - ev0: RequireMsg[(N < W.`0`.T) && (D > W.`0`.T), simplifyErrorMsg], + implicit def doSimplifyNegative[N, D, F, SNF, SN, SD](implicit + neg: RequireMsg[IsNegative[N] && IsPositive[D], simplifyErrorMsg], ev1: OpAuxGen[Negate[Rational[N, D]], F], ev2: OpAuxGen[Simplify[F], SNF], ev3: OpAuxGen[Negate[SNF], Rational[SN, SD]] @@ -234,22 +223,19 @@ object rational { val value = new Rational[SN, SD] {} } - implicit def doSimplifyZero[D <: XInt](implicit - nz: RequireMsg[D > W.`0`.T, simplifyErrorMsg] - ): OpIntercept.Aux[Simplify[Rational[W.`0`.T, D]], Rational[W.`0`.T, W.`1`.T]] = - new OpIntercept[Simplify[Rational[W.`0`.T, D]]] { - type Out = Rational[W.`0`.T, W.`1`.T] - val value = new Rational[W.`0`.T, W.`1`.T] {} + implicit def doSimplifyZero[Z, D](implicit + zro: RequireMsg[IsZero[Z] && IsPositive[D], simplifyErrorMsg], + v1: AlignType[1, D] + ): OpIntercept.Aux[Simplify[Rational[Z, D]], Rational[Z, v1.Out]] = + new OpIntercept[Simplify[Rational[Z, D]]] { + type Out = Rational[Z, v1.Out] + val value = new Rational[Z, v1.Out] {} } - implicit def doSimplifyNegDenom[ - N <: XInt, D <: XInt, - NN <: XInt, ND <: XInt, - SN <: XInt, SD <: XInt]( - implicit - bn: RequireMsg[D < W.`0`.T, simplifyErrorMsg], - nn: OpInt.Aux[Negate[N], NN], - nd: OpInt.Aux[Negate[D], ND], + implicit def doSimplifyNegDenom[N, D, NN, ND, SN, SD](implicit + neg: RequireMsg[IsNegative[D], simplifyErrorMsg], + nn: OpAuxGen[Negate[N], NN], + nd: OpAuxGen[Negate[D], ND], sf: OpAuxGen[Simplify[Rational[NN, ND]], Rational[SN, SD]] ): OpIntercept.Aux[Simplify[Rational[N, D]], Rational[SN, SD]] = new OpIntercept[Simplify[Rational[N, D]]] {