Scala隐式专业化

时间:2017-06-04 15:02:47

标签: scala shapeless

我正在使用通用转换库,我想使用无形的方法添加自动类型类派生。它适用于一般情况,但我想介绍Haskellesque newtypes自动解包,所以我想专门为我的NewType类型的派生函数,但scalac仍然采用更通用的隐式值。这是迄今为止的代码:

import shapeless._, shapeless.syntax._

trait NewType[A] { val value: A }

sealed trait ConversionTree
case class CInt(value:     Int) extends ConversionTree
case class CBoolean(value: Boolean) extends ConversionTree
case class CString(value:  String) extends ConversionTree
case class CArray(value:   List[ConversionTree]) extends ConversionTree
case class CObject(values:  Map[String, ConversionTree]) extends ConversionTree

def mergeCObjects(o1: CObject, o2: CObject): CObject = CObject(o1.values ++ o2.values)

trait ConvertsTo[A] {
  def convertTo(value: A): ConversionTree
}
object ConvertsTo {

  implicit val intConverter = new ConvertsTo[Int] { ... }
  implicit val boolConverter = new ConvertsTo[Boolean] { ... }
  implicit val stringConverter = new ConvertsTo[String] { ... }
  implicit def arrayConverter[A](implicit convertInner: ConvertsTo[A]) = new ConvertsTo[List[A]] { ... }
  implicit def objectConverter[A](implicit convertInner: ConvertsTo[A]) = new ConvertsTo[Map[String, A]] { ... }

  implicit def newTypeConverter[A](implicit convertInner: ConvertsTo[A]): ConvertsTo[NewType[A]] = new ConvertsTo[NewType[A]] {
    override def convertTo(value: NewType[A]): ConversionTree = {
      convertInner.convertTo(value.value)
    }
  }

  implicit def deriveHNil: ConvertsTo[HNil] = new ConvertsTo[HNil] {
    override def convertTo(value: HNil): ConversionTree = CObject(Map[String, ConversionTree]())
  }

  // This is the generic case
  implicit def deriveHCons[K <: Symbol, V, T <: HList](
    implicit 
      key: Witness.Aux[K],
      sv: Lazy[ConvertsTo[V]],
      st: Lazy[ConvertsTo[T]]
  ): ConvertsTo[FieldType[K, V] :: T] = new ConvertsTo[FieldType[K, V] :: T]{
    override def convertTo(value: FieldType[K, V] :: T): ConversionTree = {
      val head = sv.value.convertTo(value.head)
      val tail = st.value.convertTo(value.tail)

      mergeCObjects(CObject(Map(key.value.name -> head)), tail.asInstanceOf[CObject])
    }
  }

  // This is the special case for NewTypes
  implicit def deriveHConsNewType[K <: Symbol, V, T <: HList](
    implicit 
      key: Witness.Aux[K],
      sv: Lazy[ConvertsTo[V]],
      st: Lazy[ConvertsTo[T]]
  ): ConvertsTo[FieldType[K, NewType[V]] :: T] = new ConvertsTo[FieldType[K, NewType[V]] :: T] {
    override def convertTo(value: FieldType[K, NewType[V]] :: T): ConversionTree = {
      val head = sv.value.convertTo(value.head.value)
      val tail = st.value.convertTo(value.tail)

      mergeCObjects(CObject(Map(key.value.name -> head)), tail.asInstanceOf[CObject])
    }
  }

  implicit def deriveInstance[F, G](implicit gen: LabelledGeneric.Aux[F, G], sg: Lazy[ConvertsTo[G]]): ConvertsTo[F] = {
    new ConvertsTo[F] {
      override def convertTo(value: F): ConversionTree = sg.value.convertTo(gen.to(value))
    }
  }
}

def convertToG[A](value: A)(implicit converter: ConvertsTo[A]): ConversionTree = converter.convertTo(value)

case class Id(value: String) extends NewType[String]
case class User(id: Id, name: String)

println(s"${ConvertsTo.newTypeConverter[String].convertTo(Id("id1"))}")
println(s"User: ${convertToG(User(Id("id1"), "user1"))}")

输出第一张println:CString("id1")

输出第二张println:CObject(Map("id" -> CObject(Map("value" -> CString("id1"))), "name" -> CString("user1")))

我想摆脱第二个CObject中id字段周围的额外println。正如您所看到的,直接调用newTypeConverter会产生正确的输出,但当NewType嵌入到对象中时(如果我在{{1中放置一个断点)它不起作用方法我可以验证它没有被调用)。 我也试图像这样定义deriveHConsNewType.convertTo,但它没有帮助:

deriveHConsNewType

有人可以解释一下当发生这种重叠时隐式搜索是如何工作的并为我的问题提供解决方案吗?

编辑:通过使 implicit def deriveHConsNewType[K <: Symbol, V, N <: NewType[V], T <: HList]( implicit key: Witness.Aux[K], sv: Lazy[ConvertsTo[V]], st: Lazy[ConvertsTo[T]] ): ConvertsTo[FieldType[K, N] :: T] = new ConvertsTo[FieldType[K, N] :: T] { ... } 逆变的类型变量解决问题,scalac现在选择专门的隐含值。

1 个答案:

答案 0 :(得分:1)

通过使ConvertsTo逆变量的类型变量解决问题,scalac现在选择专门的隐含值。

var fs:FileStream = new FileStream ();
fs.addEventListener(Event.CLOSE, onFileClosed);

fs.openAsync( f , FileMode.WRITE ); // f is contains the file path
fs.writeBytes ( fzf.content ); // fzf is a FZipFile which contains some content
fs.close ();

private function onFileClosed(e:Event):void
{
    // The file was closed succesfully, it is now safe to write the next one
}