Question

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!

Was it helpful?

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.

OTHER TIPS

You could call .tupled on the Sized to get a statically-checked unapply? e.g.

val (a, b, c) = Sized(1, 2, 3).tupled
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top