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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With