Обеспечить, чтобы все входные данные некоторого типа `T` к методам черты` F` были ранее произведены самим `F` - PullRequest
0 голосов
/ 25 июня 2018

Предположим, что у меня есть некоторая черта Foo, например:

trait Foo {
  def bar: List[Int]
  def baz(i: Int): Unit
}

Я хочу во время компиляции установить, что все входные данные, переданные в baz, были ранее произведены bar.Например, если это реализация Foo:

object A extends Foo {
  def bar = List(2, 3, 5, 7)
  def baz(i: Int): Unit = {
    if (bar contains i) println("ok")
    else println("not good")
  }
}

, то я хочу обеспечить, чтобы в baz передавались только однозначные простые числа.Это, очевидно, не работает, если известно, что тип ввода baz равен Int, потому что это позволяет мне создавать экземпляры всех видов целых чисел, которые не являются простыми и не находятся между 0 и 9:

val okValues: List[Int] = A.bar
A.baz(okValues(1)) // ok
A.baz(3)           // ok (but dangerous! `3` appeared out of nowhere!)
A.baz(42)          // not good

Как я могу обеспечить, чтобы только значения, ранее созданные bar, могли быть переданы в baz?


Что не работает

Преобразование Int в элемент типа Foo не помогает, потому что он реализован для конкретного типа Int в реализации A из Foo:

trait Foo {
  type T
  def bar: List[T]
  def baz(t: T): Unit
}

object A extends Foo {
  type T = Int 
  def bar = List(2, 3, 4, 5)
  def baz(i: Int): Unit = {
    if (bar contains i) println("ok")
    else println("not good")
  } 
}

A.baz(42) // not good

1 Ответ

0 голосов
/ 25 июня 2018

Вот одно решение, которое основано на замене конкретного типа Int на абстрактный тип элемента T, а затем просто не раскрывает конкретную реализацию T на Int:

  • заменить конкретный тип Int на абстрактный тип элемента T
  • переместить методы bar и baz и тип T во внутреннюю черту YFoo
  • внутри Foo, предоставьте метод, который производит YFoo, но не раскрывает, что такое T.

В коде:

trait Foo {
  trait YFoo {
    type T
    def bar: List[T]
    def baz(i: T): Unit
  }
  def yFoo: YFoo
}

object B extends Foo {
  def yFoo: YFoo = new YFoo {
    type T = Int
    def bar: List[Int] = List(2, 3, 5, 7)
    def baz(i: Int): Unit = {
      if (bar contains i) println("ok")
      else println("not good")
    }
  }
}

val a = B.yFoo
val okValues: List[a.T] = a.bar
a.baz(okValues(1)) // ok
// a.baz(3)           // dangerous stuff doesn't compile!
// found   : Int(3)
// required: a.T

// a.baz(42)          // invalid stuff also doesn't compile
// found   : Int(42)
// required: a.T

Теперь все опасные / недействительные вещи даже не компилируются. Единственные значения, которые вы можете передать baz, это значения из "списка сертифицированных значений", созданного bar.

...