2011-09-08 48 views
9

我想定义一些特性,这些特性具有良好定义的关系 - 例如,我们假设a * b = c。这个想法是,这个特质的实现可以提供其中的两个,并为第三个属性自动生成一个访问器。在Scala中推断相互依赖的默认方法实现

这是两个不同的功能Haskell的类型类,如果我没有记错的正确,在那里,如果你从Ord定义<>=将被实现为! . < - 尽管你可以这么久定义功能任何子集作为剩余部分, 。可以推断)(我没记错的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 

这里,实现FooOneFooTwoFoo的完整实现,并且按预期运行。到现在为止还挺好;这种方法确实允许类定义两个属性并获得第三个“免费”。

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

class FooFail(override val a: Double) extends Foo 

这将编译就好了 - 但是,它会导致堆栈溢出如果bc方法是不断进行评估。


那么幼稚的方法给出了Haskell的类型类方法的推断方面,但我们没有编译时的安全性。如果少于两个的方法是通过实现类来定义的,我希望编译器能够投诉。显然目前的语法在这里是不够的;我们需要将这些方法视为抽象的,尽管只有当依赖方法非抽象时才可以使用默认实现。

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

如果不是的话,我会用幼稚的方法来做,并且仔细地定义和测试我的课程。但我真的认为这是类型系统应该能够捕获的东西(毕竟 - 它不是Ruby。:))。

+2

除非我完全记错,否则Haskell对于同样的问题使用完全相同的天真方法。 –

+0

@Alexey - 有趣的评论。这已经有一段时间了,但我似乎记得,如果你没有定义足够多的方法,你会得到一个编译器/解释器错误,导致无法为类型类派生所有方法。也许我会试着重现这一点,并阅读Haskell如何做,以获取灵感。 –

+0

好吧,在Haskell中测试过它确实遭受同样的问题(它相当于Scala的情况,它只会警告完全抽象的方法)。尽管如此,这个问题仍然存在,尽管没有另一种语言的先例。 –

回答

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

非常有趣的方法 - 根据我对didierd的评论,我怀疑implicits是让编译器检查是否存在充分defintions的关键。不过,消费者不能仅仅声明'val b = 5'是一种耻辱。我想知道这是否可以通过另一个内在层面的特征进行按摩。 –

5

如何在Haskell中获得安全?我对语言不是很熟悉,但我可以做

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

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

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

我可以想象在scala中的一种解决方法,但比较虚弱。首先,让富完全抽象

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

那么对于允许

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 
+0

好点 - 我怀疑任何解决方案都会涉及将方法完全抽象到'Foo'中,然后混合来自* somewhere *的默认定义。幻影式的方法很有趣,但令人遗憾的是,这种笨拙会暴露给不得不明确混入这种特质的消费者。也许隐式转换可以在某种程度上解决这个问题,嗯... –

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 } 

这里,两三个中的任何类,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的子类型。

+0

我喜欢你在这里所做的,使用结构类型和隐式转换,以便在编译时执行检查,而不会对消费者提出任何额外要求。我同意,不是'Foo'不是理想的,但我担心这可能是不可避免的,以便同时满足其他要求(否则'Foo'中的方法必须同时被抽象和定义)。 –