scalashapeless

Missing Sized.unapply


In object Sized (in "shapeless/sized.scala") there is unapplySeq, which unfortunately doesn't provide static checking. For example code below will fail at runtime with MatchError:

Sized(1, 2, 3) match { case Sized(x, y) => ":(" }

It would be better if there was unapply method instead, returning Option of tuple, and concrete shape of tuple was constructed according to size of Sized instance. For example:

Sized(1) => x
Sized(1, 2) => (x, y)
Sized(1, 2, 3) => (x, y, z)

In that case previous code snippet would fail to compile with constructor cannot be instantiated to expected type.

Please help me implement unapply for object Sized. Is this method already implemented anywhere?

Thanks in advance!


Solution

  • This is definitely possible (at least for Sized where N is less than 23), but the only approach I can think of (barring macros, etc.) is kind of messy. First we need a type class that'll help us convert sized collections to HLists:

    import shapeless._, Nat._0
    import scala.collection.generic.IsTraversableLike
    
    trait SizedToHList[R, N <: Nat] extends DepFn1[Sized[R, N]] {
      type Out <: HList
    }
    
    object SizedToHList {
      type Aux[R, N <: Nat, Out0 <: HList] = SizedToHList[R, N] { type Out = Out0 }
    
      implicit def emptySized[R]: Aux[R, Nat._0, HNil] = new SizedToHList[R, _0] {
        type Out = HNil
        def apply(s: Sized[R, _0]) = HNil
      }
    
      implicit def otherSized[R, M <: Nat, T <: HList](implicit
        sth: Aux[R, M, T],
        itl: IsTraversableLike[R]
      ): Aux[R, Succ[M], itl.A :: T] = new SizedToHList[R, Succ[M]] {
        type Out = itl.A :: T
        def apply(s: Sized[R, Succ[M]]) = s.head :: sth(s.tail)
      }
    
      def apply[R, N <: Nat](implicit sth: SizedToHList[R, N]): Aux[R, N, sth.Out] =
        sth
    
      def toHList[R, N <: Nat](s: Sized[R, N])(implicit
        sth: SizedToHList[R, N]
      ): sth.Out = sth(s)
    }
    

    And then we can define an extractor object that uses this conversion:

    import ops.hlist.Tupler
    
    object SafeSized {
      def unapply[R, N <: Nat, L <: HList, T <: Product](s: Sized[R, N])(implicit
        itl: IsTraversableLike[R],
        sth: SizedToHList.Aux[R, N, L],
        tupler: Tupler.Aux[L, T]
      ): Option[T] = Some(sth(s).tupled)
    }
    

    And then:

    scala> val SafeSized(x, y, z) = Sized(1, 2, 3)
    x: Int = 1
    y: Int = 2
    z: Int = 3
    

    But:

    scala> val SafeSized(x, y) = Sized(1, 2, 3)
    <console>:18: error: wrong number of arguments for object SafeSized
           val SafeSized(x, y) = Sized(1, 2, 3)
                        ^
    <console>:18: error: recursive value x$1 needs type
           val SafeSized(x, y) = Sized(1, 2, 3)
                         ^
    

    As desired.