如何限制该案例类在Scala中具有特定的参数类型构造函数?

时间:2017-01-16 00:34:29

标签: scala

假设我的案例类如下所示。

trait Foo {
  def a: String
}

case class Bar(a: String,b: Option[Int]) extends Foo{
  def this(test: Test) = this(test.foo,None)
}

case class Buzz(a: String,b: Boolean) extends Foo{
  def this(test: Test) = this(test.foo,false)
}

我正在通过反射使用构造函数def this(test: Test)并按预期工作。

我使用构造函数的方法签名就是这样的

def test[T <: Foo: ClassTag](cb: (String) => Future[T]): Future[Result]

我想要做的是限制任何扩展trait Foo的case类都需要def this(test: Test)。如果它们中的任何一个没有它,那么它应该是编译错误

我的尝试

//Compile error
trait Foo[T] {
  def a: String
  def this(test: Test):T
}

有没有办法做到这一点?

提前致谢。

3 个答案:

答案 0 :(得分:2)

不可能使用类型系统来强制某个类具有特定的构造函数。这不应该是一个惊喜,因为你已经使用反射来访问所述构造函数。使用反射调用,检查相应构造函数的唯一方法是使用更多反射 - 最好通过宏来邮件编译失败。

然而,几乎总是比使用反射更好的方法。在这种情况下,我们可以使用类型类来查找正确的方法,该方法可以从Foo构建Test(或其他任何东西)的子类型。

我们假设Test看起来像这样:

case class Test(foo: String)

然后,我们定义了一个TestBuilder类型类,它可以提供证据证明我们可以从A构建Test

trait TestBuilder[A] {
    def build(test: Test): A
}

// Convenience method for creating type class instances
object TestBuilder {
  def apply[A](f: Test => A): TestBuilder[A] = new TestBuilder[A] {
    def build(test: Test): A = f(test)
  }
}

然后,我们定义Foo个,其中每个都有TestBuilder[A]的实例,其中A是每个Foo的类型:

trait Foo {
  def a: String
}

case class Bar(a: String, b: Option[Int]) extends Foo

object Bar {
    implicit val builder = TestBuilder(test => Bar(test.foo, None))
}

case class Buzz(a: String, b: Boolean) extends Foo

object Buzz {
    implicit val builder = TestBuilder(test => Buzz(test.foo, false))
}

请注意,我们不再需要备用构造函数,并依赖类型类实例使用Foo构建apply

现在,您的test方法可以看起来像这样。我改变了返回类型,因为你没有定义任何实现或Result是什么,但想法是一样的。

def test[T <: Foo : ClassTag : TestBuilder](cb: String => Future[T]): Future[T] = {
  val test = Test("abc")
  // use the implicitly resolved type class to build `T` from a `Test`
  val t = implicitly[TestBuilder[T]].build(test) 
  Future(t).andThen {
    case Success(x) => cb(x.a)
  }
}

现在,这样的东西会编译:

// T is Bar
scala> test((s: String) => Future(Bar(s, None)))
res0: scala.concurrent.Future[Bar] = scala.concurrent.impl.Promise$DefaultPromise@56f2bbea

使用其他类型Baz,如果没有TestBuilder[Baz]的实例,则会失败。

case class Baz(a: String) extends Foo

scala> test((s: String) => Future(Baz(s)))
<console>:29: error: could not find implicit value for evidence parameter of type TestBuilder[Baz]
       test((s: String) => Future(Baz(s)))
           ^

答案 1 :(得分:1)

我认为你不能做你想要的。但也许这对你有用:

trait Foo {
  def a: String
  def create(a: String): Foo
}
case class Bar(a: String,b: Option[Int]) extends Foo{
  def create(a: String) = Bar(a,None)
}
case class Buzz(a: String,b: Boolean) extends Foo{
  def create(a: String) = Buzz(a,false)
}

然后您可以构建Bar或Buzz,而无需指定第二个参数。

顺便说一句,我并没有完全遵循你的模板,因为我不知道 Test 应该是什么。

答案 2 :(得分:0)

我认为没有办法直接这样做。但是人们通常会通过工厂构建案例类 - 它们的伴随对象 - 这使您可以灵活地以不同的方式做你想做的事。

定义

trait Test {
  def foo : String = ???
}

abstract class Foo[T <: Foo[T]]()(implicit ev : FooMaker[T]) {
  def a: String
}

trait FooMaker[T <: Foo[T]] {
   def apply( test : Test ) : T
}

implicit object Bar extends FooMaker[Bar] {
  def apply(test: Test) = Bar(test.foo,None)
}
case class Bar(a: String,b: Option[Int]) extends Foo[Bar]

implicit object Buzz extends FooMaker[Buzz] {
  def apply(test: Test) = Buzz(test.foo,false)
}
case class Buzz(a: String,b: Boolean) extends Foo[Buzz]

但是如果你尝试在你需要的伴侣对象中没有工厂方法定义Foo:

case class Barf(a : String, b : Short ) extends Foo[Barf]

你会看到

scala> case class Barf(a : String, b : Short ) extends Foo[Barf]
<console>:12: error: could not find implicit value for parameter ev: FooMaker[Barf]
   case class Barf(a : String, b : Short ) extends Foo[Barf]

使用您需要的工厂添加随播对象,这一切都很好

implicit object Barf extends FooMaker[Barf] {
  def apply(test: Test) = Barf(test.foo,0.toShort)
}
case class Barf(a : String, b : Short ) extends Foo[Barf]

在REPL中:

scala> :paste
// Entering paste mode (ctrl-D to finish)

    implicit object Barf extends FooMaker[Barf] {
      def apply(test: Test) = Barf(test.foo,0.toShort)
    }
    case class Barf(a : String, b : Short ) extends Foo[Barf]

// Exiting paste mode, now interpreting.

defined object Barf
defined class Barf

请注意,要在REPL中编译此内容,您需要使用:paste,因为无法单独定义相互依赖的定义。