在Scala中推断相互依赖的默认方法实现

时间:2011-09-08 09:48:45

标签: scala traits

我想定义一个具有一些具有良好定义关系的属性的特征 - 例如,让我们说a * b = c。这个想法是这个特征的实现可以提供其中的两个,并且具有自动派生的第三个属性的访问器。

这与Haskell的类型类相同,如果我记得正确,如果你从<定义了Ord,那么>=将被实现为{{ 1}} - 虽然你可以定义任何函数子集,只要可以推断出其余部分。)(我不记得Haskell的类型类正确。)

天真的方法实际上运作得很好:

! . <

此处,实施trait Foo { // a * b = c def a: Double = c / b def b: Double = c / a def c: Double = a * b } class FooOne(override val a: Double, override val b: Double) extends Foo class FooTwo(override val a: Double, override val c: Double) extends Foo FooOneFooTwo的完整实现,并按预期运行。到现在为止还挺好;这种方法允许类定义两个属性,并获得第三个属性“免费”。

然而,如果定义第三类,事情开始变得不那么乐观了:

Foo

这样编译得很好 - 但是,如果对其class FooFail(override val a: Double) extends Foo b方法进行评估,它将导致堆栈溢出。


因此,天真的方法给出了Haskell类型类方法的推理方面,但我们没有编译时安全性。我想要的是编译器抱怨如果实现类定义的方法少于两个。显然,目前的语法还不够;我们需要将这些方法视为抽象的,尽管有一个默认实现,当且仅当依赖方法是非抽象的时才可以使用。

Scala是否公开了适当的语义来定义它? (如果有一种有点迂回的方式来定义它,我没有问题,类似于union types,因为我不知道语言中对此有任何一流的支持。)

如果没有,我将使用天真的方法,并仔细定义和测试我的类。但我真的认为这是类型系统应该能够捕获的东西(毕竟 - 它不是Ruby。:)。)。

3 个答案:

答案 0 :(得分:8)

使用含义:

object test {
  case class A(v : Double)
  case class B(v : Double)
  case class C(v : Double)

  implicit def a(implicit b : B, c : C) = A(c.v / b.v)
  implicit def b(implicit a : A, c : C) = B(c.v / a.v)
  implicit def c(implicit a : A, b : B) = C(a.v * b.v)

  def foo(implicit a : A, b : B, c : C) = 
    a + ", " + b + ", " + c

  // Remove either of these and it won't compile
  implicit val aa = A(3)
  implicit val bb = B(4)

  def main(args : Array[String]) {
    println(foo)
  }
}

答案 1 :(得分:5)

如何在Haskell获得安全保障?我对语言不太熟悉,但我可以做到

data Func = Func (Int -> Int) 
instance Eq Func
instance Ord Func

compared = Func (\i ->  i-1) < Func (\i -> i+1)

并在评估比较时获得堆栈溢出。

我可以想象scala中的解决方法,但是相当虚弱。首先,让Foo完全抽象

trait Foo { def a: Double; def b: Double; def c: Double }

然后为每个允许的方法定义组合创建mixin特征

trait FooWithAB extends Foo {def c : Double = a * b}
trait FooWithAC extends Foo {def b : Double = c / a}
trait FooWithBC extends Foo {def a : Double = c / b}

FooOne和FooTwo必须混合其中一个特征。在FooFail中,没有什么可以阻止你混合其中两个,但仍然失败,但你已经受到了一些警告。

是否有可能更进一步,禁止混合其中两种,带有一种幻像类型

trait Foo {
  type t;
  def a: Double; def b: Double; def c: Double
}
trait FooWithAB extends Foo {type t = FooWithAB; def c : Double = a * b}
trait FooWithAC extends Foo {type t = FooWithAC; def b : Double = c / a}
trait FooWithBC extends Foo {type t = FooWithBC; def c : Double = c / b}

这可以防止混合两个FooWithXX,你无法定义

class FooFail(val a: Double) extends FooWithAC with FooWithAB

答案 2 :(得分:2)

稍微弱于您可能需要的解决方案如下:

trait Foo {
  def a : Double
  def b : Double
  def c : Double
}

// Anything with two out of three can be promoted to Foo status.
implicit def ab2Foo(ab : { def a : Double; def b : Double }) =
  new Foo { val a = ab.a; val b = ab.b; def c = ab.a * ab.b }
implicit def bc2Foo(bc : { def b : Double; def c : Double }) =
  new Foo { val a = bc.c / bc.b; val b = bc.b; def c = bc.c }
implicit def ac2Foo(ac : { def a : Double; def c : Double }) =
  new Foo { val a = ac.a; val b = ac.c / ac.a; def c = ac.c }

这里,任何具有三种a,b,c方法中的两种的类都可以查看并用作Foo。例如:

case class AB(a : Double, b : Double)
AB(5.0, 7.1).c // prints 35.5

但是如果你试试:

case class ABC(a : Double, b : Double, c : Double)
val x : Foo = ABC(1.0, 2.0, 3.0)

...你得到一个“含糊不清的”错误。

你的原始陈述的主要问题是这些类没有正确地从Foo继承,如果你想重用其他方法,这可能是一个问题。您可以通过使用包含这些其他方法的另一个特征FooImpl来解决它,并将隐式转换限制为FooImpl的子类型。