繁体   English   中英

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

[英]How to restrict that case classes have specific parameter types constructor in 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]

我想做的是限制任何扩展特征Foo案例类都必须具有def this(test: Test) 。如果他们中的任何一个案例类都没有,那就应该是编译错误。

我的尝试

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

有什么办法吗?

提前致谢。

无法使用类型系统来强制类具有特定的构造函数。 这并不应该让您感到意外,因为您已经在使用反射来访问所述构造函数。 使用反射调用,检查合适的构造函数的唯一方法是使用更多反射-最好通过宏来发送编译失败的消息。

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

假设Test看起来像这样:

case class Test(foo: String)

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

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))
}

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

现在,您的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)))
           ^

我认为您无法完全满足您的需求。 但这也许对您有用:

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。

顺便说一句,我不完全遵循您的模板,因为我不知道测试应该是什么。

我认为没有办法直接做到这一点。 但是通常是通过工厂(它们的伴随对象)构造案例类的,这使您可以灵活地以不同的方式执行所需的操作。

限定

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因为相互依赖的定义不能单独定义。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM