-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Support a rational type #146
base: master
Are you sure you want to change the base?
Conversation
This initial push isn't working. In broad strokes, it seems to be operating the way we were targeting in the #140 discussion: it will attempt to typecheck the Fix may be simple, but thought I'd push this just for the record. |
I currently don't have time to get into this, but from a brief read I think I didn't make my point across. Please reread my comments from #140 (comment) You should not need a specific trait |
(a, b) match { | ||
case (aArg : CalcVal, bArg : CalcVal) => abort(s"Unsupported $funcType[$a, $b, $cArg]") | ||
case _ => CalcUnknown(funcType.toType, None) | ||
val itree = c.typecheck(q"implicitly[_root_.singleton.ops.impl.OpIntercept[${funcType.toType}, ${a.tpe}, ${b.tpe}, ${cArg.tpe}]]", silent = true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be a try-catch here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am checking for EmptyTree instead
@soronpo that is how it's working. |
Oh, right. As I said, a brief look and I missed it. Sorry. |
Think I may have localized the problem to needing a new case in |
Sorry that I can't help right now. I hope I can take a look during the weekend. |
If it's not too much bother, I would rather this PR be split into two. The |
No, that's fine. It would probably be easier to debug this with a simpler test case to start with. |
factored the basic machinery in |
979e441
to
a9db1b8
Compare
@soronpro I rebased this off of #149 and refactored it. It is still WIP but the basic operations |
Why do you use an upper-bound |
Looking at the code got me thinking about the way we write the operations in types vs how we write them with type classes.
Maybe we should take the same approach with |
Partly it was leftover from the Libra code. Also, not sure what it means to allow a Rational with, say, Float numerator and denominator, or String, etc. Or mixed types. I suppose the underlying numeric operations themselves would fail and constrain it that way. |
@soronpo another policy question is whether this is consistent with the rest of the library: rat: Require[IsRational[LHS] || IsRational[RHS]],
lhs: OpGen.Aux[ToRational[LHS], Rational[LN, LD]],
rhs: OpGen.Aux[ToRational[RHS], Rational[RN, RD]], That allows any argument convertable to Rational to be mixed with a rational arg, like I'm starting to think it's better to just require left and right arguments to be both |
Look at this example. Typelevel operations can be done like typeclasses 😃 type OpConv[F <: Function1[_,_], Out] = OpIntercept.Aux[F, Out]
implicit def VecToVec[A0, A1] : OpIntercept.Aux[Vec[A0, A1] => Vec[_, _], Vec[A0, A1]] =
new OpIntercept[Vec[A0, A1] => Vec[_,_]] {
val value : Out = new Vec[A0, A1]{}
type Out = Vec[A0, A1]
}
implicit def ScalarIntToVec[I <: XInt] : OpIntercept.Aux[I => Vec[_, _], Vec[I, I]] =
new OpIntercept[I => Vec[_,_]] {
val value : Out = new Vec[I, I]{}
type Out = Vec[I, I]
}
implicit def `Vec+`[T, VL0, VL1, VR0, VR1, V[_,_]](
implicit
conv : OpConv[T => Vec[_, _], V[VR0, VR1]],
opL : VL0 + VR0,
opR : VL1 + VR1
) : OpIntercept.Aux[Vec[VL0, VL1] + T, Vec[opL.Out, opR.Out]] =
new OpIntercept[Vec[VL0, VL1] + T] {
type Out = Vec[opL.Out, opR.Out]
val value : Out = new Vec[opL.Out, opR.Out]{}
}
val vecPlusScalar = shapeless.the[Vec[1, 2] + 10] //yeah, I know in math this is not right
val vecPlusVec = shapeless.the[Vec[1, 2] + Vec[10, 10]] |
At the beginning, the library allowed that, but that caused the case-statement to be HUGE, and slowed things down. However, with this "typeclass"-like trick, I can add support for extended support for primitive through |
What is your opinion on the issue of conversion interactions? For example, if I write rules that allow various numeric types to convert to Rational, and then write my |
requiring something like |
You need to have a separation: In case both side are rational, maybe just implicit prioritization will do. |
@soronpo so you'd prefer an idiom like |
Yes, because it saves you from needing to declare a new type/trait |
That is, unless the user requires an explicit |
I see that as a library policy convention question - do you want a |
The idiom |
They don't do the same thing (conversion vs. just an auxiliary). |
I do not understand this distinction |
One is |
The only reason you require |
OK that is clear. So, my current use of With this new support for custom non-singleton types, it might be useful to have something like a generic |
There is |
If extending casting to non-singleton types breaks the model, it maybe isn't worth it |
Well, actually
So the entire |
But |
Maybe you're right and it's possible to unify |
BTW, here is what implicit def Long2Long[L <: XLong] : OpConv[L => XLong, L] = ???
implicit def Int2Long[I <: XInt](implicit op : ToLong[I]) : OpConv[I => XLong, op.Out] = ???
implicit def `Long+Long`[L, R, LL, RL]( //repeat for all basic operations that support Long
implicit
convL : OpConv[L => XLong, LL],
convR : OpConv[R => XLong, RL],
op : LL + RL
) : OpIntercept.Aux[L + R, op.Out] = ???
implicitly[1 + 1L + 1]
implicitly[1L + 1 + 1L] |
What should the cross-product of operator output types look like? For example, the following can all lose integer precision on the left hand side:
The following are reasonable and defensible, but aren't lossless:
|
btw, I am in the process of removing |
The same as in terms. That's why it's best to have these implicits in a separate namespace. We can have: |
I've done similar policy settings using implicit values: and then using them is like: |
Nice way to support various casting combinations 😄 sealed trait Cast
object Cast {
trait Char extends Cast
trait Int extends Cast
trait Long extends Cast
trait Float extends Cast
trait Double extends Cast
trait Boolean extends Cast
trait String extends Cast
}
implicit def castCI[L <: XChar, R <: XInt] : OpConv[(L, R) => Cast, Cast.Int] = ???
implicit def castCL[L <: XChar, R <: XLong] : OpConv[(L, R) => Cast, Cast.Long] = ???
implicit def castIC[L <: XInt, R <: XChar] : OpConv[(L, R) => Cast, Cast.Int] = ???
implicit def castIL[L <: XInt, R <: XLong] : OpConv[(L, R) => Cast, Cast.Long] = ???
implicit def castLC[L <: XLong, R <: XChar] : OpConv[(L, R) => Cast, Cast.Long] = ???
implicit def castLI[L <: XLong, R <: XInt] : OpConv[(L, R) => Cast, Cast.Long] = ???
implicit def Char2Char[C <: XChar] : OpConv[C => Cast.Char, C] = ???
implicit def Char2Int[C <: XChar](implicit op : ToInt[C]) : OpConv[C => Cast.Int, op.Out] = ???
implicit def Char2Long[C <: XChar](implicit op : ToLong[C]) : OpConv[C => Cast.Long, op.Out] = ???
implicit def Int2Int[I <: XInt] : OpConv[I => Cast.Int, I] = ???
implicit def Int2Long[I <: XInt](implicit op : ToLong[I]) : OpConv[I => Cast.Long, op.Out] = ???
implicit def Long2Long[L <: XLong] : OpConv[L => Cast.Long, L] = ???
implicit def `+Ext`[L, R, BO, LL, RL](
implicit
basicOp : OpConv[(L, R) => Cast, BO],
convL : OpConv[L => BO, LL],
convR : OpConv[R => BO, RL],
op : LL + RL
) : OpIntercept.Aux[L + R, op.Out] = ???
implicit def `-Ext`[L, R, BO, LL, RL](
implicit
basicOp : OpConv[(L, R) => Cast, BO],
convL : OpConv[L => BO, LL],
convR : OpConv[R => BO, RL],
op : LL - RL
) : OpIntercept.Aux[L - R, op.Out] = ???
implicit def `*Ext`[L, R, BO, LL, RL](
implicit
basicOp : OpConv[(L, R) => Cast, BO],
convL : OpConv[L => BO, LL],
convR : OpConv[R => BO, RL],
op : LL * RL
) : OpIntercept.Aux[L * R, op.Out] = ???
implicit def `/Ext`[L, R, BO, LL, RL](
implicit
basicOp : OpConv[(L, R) => Cast, BO],
convL : OpConv[L => BO, LL],
convR : OpConv[R => BO, RL],
op : LL / RL
) : OpIntercept.Aux[L / R, op.Out] = ???
implicitly[20/4 + 1L + 1 * 5L + 'c'] |
@soronpo I updated the code to remove any XInt upper bounds. It is moving in the right direction, for example the following now operates using either XInt or XLong: scala> shapeless.the[Simplify[Rational[4,6]]].value.show
res0: String = Rational(2, 3)
scala> shapeless.the[Simplify[Rational[4L,6L]]].value.show
res1: String = Rational(2, 3) However some invocations are now failing, although superficially they should work exactly as before. These are two different failures, that may or may not be related: scala> shapeless.the[Simplify[Rational[-4,6]]].value.show
^
error: Simplify requires non-zero denominator
scala> shapeless.the[Rational[1,2] + Rational[1,3]].value.show
^
error: Calculation has returned a non-literal type/value.
To accept non-literal values, use `AcceptNonLiteral[T]`. |
it looks like |
You shouldn't need to use |
The latest code is pushed. I don't yet understand why backing away from the XInt type bounds causes things to start failing. "Low level" calls like |
I may have found a way to combine |
@soronpo have you had a chance to play with this? |
@erikerlandson I've pushed my WIP to the other pull request |
Addresses #140