Вывод взаимозависимых реализаций метода по умолчанию в Scala - PullRequest
9 голосов
/ 08 сентября 2011

Я хотел бы определить черту с некоторыми свойствами, которые имеют четко определенные отношения - например, скажем, a * b = c.Идея состоит в том, что реализации этой черты могут предоставить два из них и иметь аксессор для третьего свойства, полученного автоматически.

( Это та же особенность, что и у классов типов 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

Здесь реализации FooOne и FooTwo являются полными реализациями Foo и ведут себякак и ожидалось.Все идет нормально;этот подход позволяет классам определять два свойства и получать третье «бесплатно».

Однако все начинает выглядеть менее радужно, если определить третий класс:

class FooFail(override val a: Double) extends Foo

Это компилируется просто отлично - однако, это вызовет переполнение стека, если его методы b или c когда-либо будут оценены.


Таким образом, наивный подход дает вывод аспект подхода класса типов в Haskell, но у нас нет безопасности во время компиляции.Что бы я хотел, чтобы компилятор жаловался, если для реализации классов определены менее двух методов.Очевидно, что текущий синтаксис здесь недостаточен;нам нужно, чтобы методы считались абстрактными, хотя и с реализацией по умолчанию, которую можно использовать тогда и только тогда, когда зависимые методы неабстрактны.

Предоставляет ли Scala соответствующую семантику для определения этого?(У меня нет проблемы, если есть какой-то окольный способ его определения, подобный типам объединения , поскольку я не знаю никакой первоклассной поддержки этого в языке).

Если нет, я справлюсь с наивным подходом и просто тщательно определю и протестирую свои занятия.Но я действительно думаю, что это система, которая должна улавливать систему типов (в конце концов, это не Ruby.:)).

Ответы [ 3 ]

8 голосов
/ 08 сентября 2011

Использование последствий:

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)
  }
}
5 голосов
/ 08 сентября 2011

Как вы получаете безопасность в Хаскеле?Я не очень знаком с языком, но я могу сделать

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 }

Затем создайте миксин-черты для каждой разрешенной комбинации определения метода

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 голосов
/ 08 сентября 2011

Решение, немного более слабое, чем то, что вам может понадобиться, состоит в следующем:

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.

...