简体   繁体   中英

In scala shapeless library, is there a native way to convert a product type (HList/Generic/NamedGeneric) to an int singleton type equal to its arity?

I'm trying to figure out how to associate a feature (singleton type) of shapeless with another (HList), assuming we would like to derive a generic type Vector that contains information about the arity of the input vector:

package com.tribbloids.spike.shapeless_spike.shapesafe

import shapeless.{Generic, Witness}

import scala.language.implicitConversions

trait Vector[W <: Witness.Lt[Int]] extends Serializable {

  def witness: W

  def <*>(that: Vector[W]): Vector[W] = ???
}

object Vector {

  case class Impl[W <: Witness.Lt[Int]](witness: W) extends Vector[W] {}

  def zeros[W <: Witness.Lt[Int]](witness: W): Impl[W] = Impl(witness)

  object Attempt1 {

    def values(v: (Double)) = Impl(Witness(1))
    def values(v: (Double, Double)) = Impl(Witness(2))
    def values(v: (Double, Double, Double)) = Impl(Witness(3))

    Vector.zeros(Witness(2)) <*> values(2.0, 3.0)

    Vector.zeros(Witness(3)) <*> values(2.0, 3.0) // breaks as expected
  }
}

I can write numerous lines to support the derivation (which will eventually hit the JVM's limitation for classloading), or I can write a more succinct implicit conversion to dynamically crate singleton types on-demand, something like this:

  object Attempt2 {
    type HInts = Int :: HInts

    def values[T <: Product](v: T)(implicit aux: Generic.Aux[T, HInts]) = {
      ???
    }
  }

However, despite that both are very matured shapeless feature, it seems to have no documentation teaching how to enable such algebraic type derivation.

Is there an easy way to replace all my implicit functions with a general enough statement? I'm using scala-2.12 + shapeless 2.3 at the moment.

Thanks a lot for the information.

If all you need is

  • ability to express size of product in code as a literal
  • enforcing that size at compile time

it is much easier, if you use Nat instead of Witness.Lt[Int] :

~ amm
Loading...
Welcome to the Ammonite Repl 2.0.4 (Scala 2.13.1 Java 1.8.0_242)
@  import $ivy.`com.chuusai::shapeless:2.3.3`, shapeless._
import $ivy.$                             , shapeless._

@ {
  trait OfSize[A, N <: Nat]
  object OfSize {

    implicit def evidence[A, Repr <: HList, N <: Nat](
      implicit gen: Generic.Aux[A, Repr],
      length: shapeless.ops.hlist.Length.Aux[Repr, N]
    ): OfSize[A, N] = new OfSize[A, N] {}
  }
  }
defined trait OfSize
defined object OfSize

@ def needSized[A, N <: Nat](a: A, n: N)(implicit ev: A OfSize N) = "OK"
defined function needSized

@ case class Test(a: String, b: Int)
defined class Test

@ needSized(Test("a", 0), Nat(3))
cmd4.sc:1: could not find implicit value for parameter ev: ammonite.$sess.cmd1.OfSize[ammonite.$sess.cmd3.Test,shapeless.Succ[shapeless.Succ[shapeless.Succ[shapeless._0]]]]
val res4 = needSized(Test("a", 0), Nat(3))
                    ^
Compilation Failed

@ needSized(Test("a", 0), Nat(2))
res4: String = "OK"

@

I believe Nat in some for will be available in Shapeless 3, so it would be more portable approach than relying on witnesses. If you wanted to use Witness, however, then I believe then it would be required to provide your own macro to convert Witness to Nat and then use existing shapeless utilities.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM