2016-09-23 47 views
0

我有不同的来源和相应的参数Scala的设计使用反射,隐式和泛型

Source1的,源2,Source3

参数1,参数,参数3,

来源:是性状(可改变)

trait Source[T] { 
def get(Parameter)(implicit c: Context): MyData[T] 
} 

参数也是一个特点

trait Parameter 

我有不同的输出类型类:T1,T2,T3

我需要作为输出:迈德特[输出类型]

固定API签名(更改签名不太优选):

val data1: MyData[T1] = MyAPI.get[T1](Parameter1("a", "b")) // this should give MyData from Source1 of type T1 
val data2: MyData[T2] = MyAPI.get[T2](Parameter3(123)) // this should give MyData from Source3 of type T2 

某些源支持某些输出类型(比如T1,T2),但有些可能不支持。

我做了什么: 我试过使用scala反射typeTag来确定运行时的类型,但由于返回类型是MyData [T],并且处于contra-variant位置,所以不知道实际的返回类型。 (Why does TypeTag not work for return types?) 例如

object MyAPI { 
    get[T: TypeTag](p: Parameter)(implicit c: Context): MyData[T] = {} 
} 

我也试过使用类型模式。 Scala TypeTag Reflection returning type T 我可以使用不同的OutputType为每个创建隐式val,但仅适用于单个Source1。我无法为所有来源工作。

我试图做的事:

object MyAPI { 
     get[T: SomeConverter](p: Parameter)(implicit c: Context): MyData[T] = { 
     p match { 
      case Parameter1 => Source1[T].read(p.asInstanceOf(Parameter1) 
      case Parameter2 => Source2[T].read(p.asInstanceOf(Parameter2) 
     } 
     } 
    } 
+1

请您尝试更清楚地说明您正在尝试做什么?对我来说,你假设读者太多。给出一些你的类型的例子,也许有更多的代码缺失部分(例如,'Parameter'看起来像什么) –

回答

1

声明:我觉得我想通了,你想要什么。我也在学习设计类型安全的API,所以这里是一个。

提供的变体使用隐含。您必须手动建立参数类型与其产生的结果之间的映射,这可能包含或不包括来源。但它在运行时不起作用,所以我也删除了共同的特征Parameter。它也根本不对信息来源施加任何限制。

它也“看起来”你想要的样子,但它不完全是这样。

case class User(id: Int) // Example result type 

// Notice I totally removed any and all relation between different parameter types and sources 
// We will rebuild those relations later using implicits 
object Param1 
case class Param2(id: Int) 
case class Param3(key: String, filter: Option[String]) 

// these objects have kinda different APIs. We will unify them. 
// I'm not using MyData[T] because it's completely irrelevant. Types here are Int, User and String 
object Source1 { 
    def getInt = 42 
} 

object Source2 { 
    def addFoo(id: Int): Int = id + 0xF00 
    def getUser(id: Int) = User(id) 
} 

object Source3 { 
    def getGoodInt = 0xC0FFEE 
} 

// Finally, our dark implicit magic starts 
// This type will provide a way to give requested result for provided parameter 
// and sealedness will prevent user from adding more sources - remove if not needed 
sealed trait CanGive[Param, Result] { 
    def apply(p: Param): Result 
} 

// Scala will look for implicit CanGive-s in companion object 
object CanGive { 
    private def wrap[P, R](fn: P => R): P CanGive R = 
    new (P CanGive R) { 
     override def apply(p: P): R = fn(p) 
    } 

    // there three show how you can pass your Context here. I'm using DummyImplicits here as placeholders 
    implicit def param1ToInt(implicit source: DummyImplicit): CanGive[Param1.type, Int] = 
    wrap((p: Param1.type) => Source1.getInt) 

    implicit def param2ToInt(implicit source: DummyImplicit): CanGive[Param2, Int] = 
    wrap((p: Param2) => Source2.addFoo(p.id)) 

    implicit def param2ToUser(implicit source: DummyImplicit): CanGive[Param2, User] = 
    wrap((p: Param2) => Source2.getUser(p.id)) 

    implicit val param3ToInt: CanGive[Param3, Int] = wrap((p: Param3) => Source3.getGoodInt) 

    // This one is completely ad-hoc and doesn't even use the Source3, only parameter 
    implicit val param3ToString: CanGive[Param3, String] = wrap((p: Param3) => p.filter.map(p.key + ":" + _).getOrElse(p.key)) 
} 


object MyApi { 
    // We need a get method with two generic parameters: Result type and Parameter type 
    // We can "curry" type parameters using intermediate class and give it syntax of a function 
    // by implementing apply method 
    def get[T] = new _GetImpl[T] 

    class _GetImpl[Result] { 
    def apply[Param](p: Param)(implicit ev: Param CanGive Result): Result = ev(p) 
    } 

} 

MyApi.get[Int](Param1) // 42: Int 
MyApi.get[Int](Param2(5)) // 3845: Int 
MyApi.get[User](Param2(1)) // User(1): User 
MyApi.get[Int](Param3("Foo", None)) // 12648430: Int 
MyApi.get[String](Param3("Text", Some(" read it"))) // Text: read it: String 

// The following block doesn't compile 
//MyApi.get[User](Param1) // Implicit not found 
//MyApi.get[String](Param1) // Implicit not found 
//MyApi.get[User](Param3("Slevin", None)) // Implicit not found 
//MyApi.get[System](Param2(1)) // Same. Unrelated requested types won't work either 
1
object Main extends App { 
    sealed trait Parameter 

    case class Parameter1(n: Int) extends Parameter with Source[Int] { 
    override def get(p: Parameter): MyData[Int] = MyData(n) 
    } 

    case class Parameter2(s: String) extends Parameter with Source[String] { 
    override def get(p: Parameter): MyData[String] = MyData(s) 
    } 

    case class MyData[T](t: T) 

    trait Source[T] { 
    def get(p: Parameter): MyData[T] 
    } 

    object MyAPI { 
    def get[T](p: Parameter with Source[T]): MyData[T] = p match { 
     case p1: Parameter1 => p1.get(p) 
     case p2: Parameter2 => p2.get(p) 
    } 
    } 

    val data1: MyData[Int] = MyAPI.get(Parameter1(15)) // this should give MyData from Source1 of type T1 
    val data2: MyData[String] = MyAPI.get(Parameter2("Hello World")) // this should give MyData from Source3 of type T2 

    println(data1) 
    println(data2) 
} 

这是否你想要做什么?

ScalaFiddle:https://scalafiddle.io/sf/FrjJz75/0