2

Using Peter Neyens's helpful answer, I tried to create an X class that only consists of Nat's less than or equal to 2.

import shapeless._
import shapeless.ops.nat._
import shapeless.nat._

case class X[A <: Nat](values: List[A])(implicit ev: LTEq[A, _2])

The following works:

scala> X( List(_1, _1, _1) )
res6: X[shapeless.nat._1] = X(List(Succ(), Succ(), Succ()))

But when I used different Nat's, i.e. _1 and _2, I got a compile-time error:

scala> X( List(_1, _2) )
<console>:23: error: could not find implicit value for parameter ev: 
   shapeless.ops.nat.LTEq[shapeless.Succ[_ >: shapeless.Succ[shapeless._0] 
       with shapeless._0 <: Serializable with shapeless.Nat],shapeless.nat._2]
       X( List(_1, _2) )
        ^

Then I tried:

scala> case class X(values: List[_ <: Nat])
defined class X

which works for sub-classes of Nat, but it's not bounded by <= 2:

scala> X(List(_1, _2, _3))
res8: X = X(List(Succ(), Succ(), Succ()))

How can I write my above X that has a List of Nat's that fall in the range of 0 to 2?

My immediate thought is to use an Algebraic Data Type for MyZero, MyOne, and MyTwo, but not sure that's the cleanest way.

Lastly, it seems to me that X's requirement to have a type parameter is unnecessary. Can it either be inferred or left out in an implementation of my desired X?

Community
  • 1
  • 1
Kevin Meredith
  • 41,036
  • 63
  • 209
  • 384

1 Answers1

1

Introducing a new ADT for the manual bound is probably the only way to go given you need to bound by distinct macro materialised objects that can only be materialised out of constants.

The obvious generic solution would be an implicit mechanism of generating type evidence for possible comparator operations, such as when using < or <= or whatever on a Nat implicit evidence could be produced that would simply compare the underlying int values of the Nat.

You could still manually do this using NatOps with the native toInt.

You hit another constraint looking into the implementation of Nat, namely there's a Literal(Constant()) required.

val n = i.tree match {
  case Literal(Constant(n: Int)) => n
  case _ =>
    c.abort(c.enclosingPosition, s"Expression ${i.tree} does not evaluate to an Int constant")
}

So this probably leaves you with something like the following:

abstract class SizeBound[N <: Nat](n: N)

import shapeless.nat._

implicit object ZeroBound extends SizeBound(_0)
implicit object OneBound extends SizeBound(_1)
implicit object TwoBound extends SizeBound(_2)

And then the obvious def op[T <: Nat : SizeBound](...). Maybe one of the Shapeless gurus around has a better way, I'm sadly just a novice.

Update

I just had a flashback thinking about this, and remember how Miles did Shapeless Fizz buzz for fun, namely how it relied on Mod.Aux to do mod division between nats.

Long story short, there is a type-class already:

import shapeless.ops.nat._
import shapeless.nat._

def doStuff[N <: Nat](n: N)(
  // There is a _2 type already available.
  implicit ev: LT[N, _2]
) = { // now you know N is less than _2}

LT is obviously lower than, and you've got a few more variations at your disposal. Here you go, you can browse for other fun things in here too. This works, but not when you use _2.type, instead you have to use the _2 type directly from import shapeless.nat._

flavian
  • 28,161
  • 11
  • 65
  • 105
  • Using `doStuff` does not work, as I understand this test's outcome: `scala> doStuff(_0) :22: error: could not find implicit value for parameter ev: shapeless.ops.nat.LT[shapeless.nat._0,shapeless.nat._2.type] doStuff(_0)` – Kevin Meredith Aug 28 '16 at 19:25
  • Also - what do you think about using the following over an ADT? `case class SSNDigit[N <: Nat](n: N)(implicit ev: LTEq[N, _9])` – Kevin Meredith Aug 28 '16 at 19:40
  • 1
    @KevinMeredith I've corrected my example, apologies. I've also double checked locally that it works. I would normally advise against mixing the ide of a `case class` with an implicit, I would seal it and move the implicit to the companion apply method – flavian Aug 29 '16 at 21:02