Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 416
Dmytro Starosud Avatar asked Apr 15 '14 17:04

Dmytro Starosud


1 Answers

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.

like image 127
Travis Brown Avatar answered Oct 20 '22 01:10

Travis Brown