1
votes

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.

1
From what I see it is quite easy to get HList/Coproduct size in Nat. Problem is how to convert Nat to Int singleton (Witness). It it really important for you to have values as Witness types?Mateusz Kubuszok
Well, the problem is to make this information visible in compile time, when Nat is rendered it is generally too latetribbloid
By visible you mean printing in e.g. macro? Or like in compiler error?Mateusz Kubuszok
OK I probably wasn't clear about my intention in that example. Let me update ittribbloid
I think it would be possible to write a macro which generate some trait NatToWitness[N <: Nat] { type W <: Witness.Lt[Int]; def witness(): W }, and then use build-in tooling to make HList -> Size -> NatToWitness -> W conversion, but I am not sure if you would be interested in such solution.Mateusz Kubuszok

1 Answers

1
votes

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.