2017-11-17 45 views
2

有没有人知道如何使用无形的测试工作。使用无形的类型级别过滤

package net.jtownson.swakka.jsonschema 

import org.scalatest.FlatSpec 
import org.scalatest.Matchers._ 

class OptionalFieldSpec extends FlatSpec { 

    case class A(i: Int, j: Option[Int]) 

    "an extractor of some kind" should "get the (non)optional fields from a case class" in { 

    extractNonOptionalFieldNames[A] shouldBe List("i") 

    extractOptionalFieldNames[A] shouldBe List("j") 

    } 

    def extractNonOptionalFieldNames[T <: Product](/* implicit typeclass instances? */): List[String] = ??? 

    def extractOptionalFieldNames[T <: Product]: List[String] = ??? 

} 

我有一个不运行时实例或它的仿制药,因为我在对的情况下A类,它是独立于任何特定实例的创建工作JsonSchema。该模式有一个所需的字段,该字段是非可选字段的列表。例如

{ 
    "type" -> "object", 
    "required" -> ["i"], 
    "properties" -> { 
    "i" -> { 
     "type" -> "integer", 
     "format" -> "int32" 
    } 
    } 
} 
+0

你想'LabelledGeneric' - 这将使你的字段名的类型级证人,以及它们的类型。除此之外,似乎你只是想过滤这个HList。 – Alec

回答

3

事情是这样的:

trait FieldNameExtractor[T] extends Serializable { 
    import shapeless.ops.hlist.{RightFolder, ToTraversable} 
    import shapeless.ops.record.Keys 
    import shapeless.{HList, HNil, LabelledGeneric, Poly2} 

    /** 
    * Extracts filtered field names for type [[T]], 
    * given a polymorphic function that acts as the type filter 
    */ 
    def extract[L <: HList, R <: HList, O <: HList](op: Poly2)(
     implicit lgen: LabelledGeneric.Aux[T, L], 
     folder: RightFolder.Aux[L, HNil.type, op.type, R], 
     keys: Keys.Aux[R, O], 
     traversable: ToTraversable.Aux[O, List, Symbol] 
): List[String] = { 
    val result = keys().to[List] 
    result.map(_.name) 
    } 
} 

object FieldNameExtractor { 
    def apply[T] = new FieldNameExtractor[T] {} 
} 

用法:

import org.scalatest.FlatSpec 
import org.scalatest.Matchers._ 

class Test extends FlatSpec { 
    /* type filters */ 
    import shapeless.{HList, Poly2} 
    import shapeless.labelled.KeyTag, shapeless.tag.Tagged 

    type FilterO[A, T] = Option[A] with KeyTag[Symbol with Tagged[T], Option[A]] 

    trait Ignore extends Poly2 { 
    implicit def default[A, L <: HList] = at[A, L]((_, l) => l) 
    } 
    trait Accept extends Poly2 { 
    implicit def default[A, L <: HList] = at[A, L](_ :: _) 
    } 

    object allOptions extends Ignore { 
    implicit def option[A, T, L <: HList] = at[FilterO[A, T], L](_ :: _) 
    } 
    object noOptions extends Accept { 
    implicit def option[A, T, L <: HList] = at[FilterO[A, T], L]((_, l) => l) 
    } 

    "an extractor of some kind" should "get the (non)optional fields from a case class" in { 
    case class A(i: Int, j: Option[Int], k: String) 

    val fne = FieldNameExtractor[A] 
    fne.extract(noOptions) shouldBe List("i", "k") // extractNonOptionalFieldNames 
    fne.extract(allOptions) shouldBe List("j")  // extractOptionalFieldNames 
    } 
} 
+0

美丽。谢谢。其实,我认为你发布的第一个解决方案也很好。它的代码少了几行。 –

+0

我设法使它更加可重用和美观。所有的“魔术”都发生在“提取”方法中;所以例如'extractOptionalFieldNames'实现只传递所需的参数:你自定义类型的值和作为过滤器的多态函数('Poly' impl) –

+0

已修改,因此不需要'A'的运行时实例 –

2

这里使用的一种方法类型类:

​​3210

有可能需要几件事情解释:

  • 既然您提到您没有运行时实例A。什么是您想要回来的类型级别表示?在这个解决方案中,我只是返回了一个HList证人的选择。我不认为List[String]表示就足够了,因为过滤掉非可选值将具有与无所事事相同的类型。
  • 类型类有一个优先级,所以过滤选项与反向优先级相同。

它可以像这样使用:

case class A(i: Int, j: Option[Int], k: Option[Long]) 
val x = LabelledGeneric[A] 
type filteredType = OptionExtractor[x.Repr] 
//type B = Symbol with shapeless.tag.Tagged[String("j")] :: Symbol with shapeless.tag.Tagged[String("k")] :: shapeless.HNil 
+0

干杯,Jamborta。 (我提高了你的解决方案,但我没有足够的街道信誉来计算它,当我更高级时,我会回到它的位置:-) –