Skip to content
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

support a rational type #140

Open
erikerlandson opened this issue May 8, 2020 · 11 comments
Open

support a rational type #140

erikerlandson opened this issue May 8, 2020 · 11 comments

Comments

@erikerlandson
Copy link
Contributor

Libra does this. I've wanted it in coulomb for a while (erikerlandson/coulomb#83). I could just port the code, but it seems like it might be a good fit for abstracting into singleton-ops, if stakeholders are agreeable.

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

Can you point me to the relevant code?

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

I think it's worth doing, but perhaps in a different way. I like that singleton-ops gives the "feel" of similar term algebraic behavior. The problem is that if we try to do in types Frac1 + Frac2 it will not work and we need to directly invoke fraction.Add[Frac1, Frac2]. The reason this doesn't work as-is is because the singleton-ops macro handles all (the known) possible operator variations since we don't have polymorphic calls to 'type operations'.
Can we do better? Can we make things more generic?
I believe so.

We can change it so that when a macro finds an unsupported situation, e.g. +[Frac, Frac], it will try to invoke an implicit of that type signature. Since the output of that operation is also a Frac, the result will be CalcUnknown. We may need to require a little more additional machinery, but that's no biggy I think.

So essentially, if we do this right, the type classes will be defined for the current op types (other than the new unique operations for Frac). I think new operations should use the same mechanism with new Op Ids.

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

Let me clarify further.
If we do +[Frac1, Frac2] and in scope you already have
implicit def fracAdd[Frac1 <: Frac[_,_], Frac2 <: Frac[_,_]] : Frac1 + Frac2 = ???
then this fracAdd will be called because it's a more specific signature.
However, once you have a composition
Frac1 + Frac2 + Frac3, then the macro will be called. So for this case we need to change the macro as I have stated above.

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

Perhaps to avoid implicit recursion we should add an AlternativeOp typeclass with the same signature as OpMacro, so the macro with call it instead.

@erikerlandson
Copy link
Contributor Author

I think we can probably make that work. I've had problems with inferImplicitValue in the past, although only in some situations:
https://stackoverflow.com/questions/43097790/scala-syntax-tree-returned-from-inferimplicitvalue-is-failing-to-evaluate

If it was 2.13+ only, I'd feel pretty comfortable having implicits that can parse out sub-expressions, but that also is dicey, pre 2.13 (and is the reason coulomb works only on 2.13 plus)

I'm a little hazy on how the internals of the OpMacro code works. Can the new type be detected inside to avoid a CalcUnknown ?

@erikerlandson
Copy link
Contributor Author

How does this happen now, with existing singleton types? The case of 1 + 2 + 3 seems like it incurs the same basic structural issue, even if the argument types are native singletons.

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

I think we can probably make that work. I've had problems with inferImplicitValue in the past, although only in some situations:
https://stackoverflow.com/questions/43097790/scala-syntax-tree-returned-from-inferimplicitvalue-is-failing-to-evaluate

If it was 2.13+ only, I'd feel pretty comfortable having implicits that can parse out sub-expressions, but that also is dicey, pre 2.13 (and is the reason coulomb works only on 2.13 plus)

That's because it uses c.eval. We don't need it. It's just something like c.typecheck(q"implicitly[AlternativeOp[$OpTpe, $Arg1Tpe, $Arg2Tpe, $Arg3Tpe]]")

I'm a little hazy on how the internals of the OpMacro code works. Can the new type be detected inside to avoid a CalcUnknown ?

CalcUnknown should be perfectly fine. It just keeps the calculated type as-is since it's not Int, Long, etc... The only thing I'm currently unsure of is what happens when we have something like CalcUnknown + CalcUnknown.

How does this happen now, with existing singleton types? The case of 1 + 2 + 3 seems like it incurs the same basic structural issue, even if the argument types are native singletons.

The macro does not have upper-bounds. It's just one big case for everything. So it traverses the type tree calculates 1 + 2, and takes that calculation to continue on with + 3.

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

My current guess is that we just need to add the call to implicitly here:
https://github.com/fthomas/singleton-ops/blob/master/src/main/scala/singleton/ops/impl/GeneralMacros.scala#L901
So in case there is no supported calculation, it tries to invoke implicitly with AlternativeOp and if it succeeds, then it reapplies the calculation on it using TypeCalc(alternativeTpe).

@erikerlandson
Copy link
Contributor Author

Thanks @soronpo! I'll play around with it when I get a chance

@soronpo
Copy link
Collaborator

soronpo commented May 8, 2020

Wow, this is really cool. If this actually works, singleton-ops will make it very easy to define recursive type operations:

  //Needs to be added to singleton-ops
  trait AlternativeOp[N, S1, S2, S3] extends OpMacro[N, S1, S2, S3] {
    type OutWide = None.type
    val valueWide : OutWide = None
    val isLiteral : Boolean = false
  }
  //Fibonacci example
  trait FibId
  type Fib[P] = AlternativeOp[FibId, P, 0, 0]
  implicit def doFib[P](implicit op : ITE[P == 1, 1, ITE[P == 0, 0, Fib[P - 1] + Fib[P - 2]]]) : Fib[P]{type Out = op.Out} = new Fib[P] {
    override type Out = op.Out
    override val value : op.Out = op.value
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants