Может ли Scala ограничить граф объектов, чтобы были видны только те объекты, которые имеют отношение к контексту?

Есть ли способ использовать систему типов Scala для краткого указания контекстно-значимого подграфа полного графа объектов?

DCI утверждает, что у вас часто есть довольно сложный граф объектов, но в любом случае вы часто хотите работать только с подграфом. У тебя есть Foo это имеет Bar и Bat, но когда вы используете вариант 1, вы заботитесь только о Bar и в случае использования 2, только о Bat,

Например, предположим, что у вас есть эта структура, а сценарий использования Role1 требует Foo->Bar->Baz->Bin и Role2 прецедент требует Foo->Bat->Baz->Buz:

class Foo{
   val bar = new Bar() //Only relevant to Role 1
   val bat = new Bat() //Only relevant to Role 2 
}

class Bar {
   val baz = new Baz() 
}

class Bat {
   val baz = new Baz()
}

//Relevant to both Role 1 and 2 (via Bar or Bat)
class Baz {
  val bin = new Bin() //Only relevant to Role 1
  val buz = new Buz() //Only relevant to Role 2
}

class Bin{}
class Buz{}

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

trait FooInRole1 { def bar : Bar }  //Define accessor in trait
s/Foo/Foo extends FooInRole1/       //Change Foo's declaration to implement trait
val f : FooInRole1 = new Foo        //LHS is i'face, RHS is implementation
//f.bat <--Compile error              Irrelevant field is not available. \o/ 

Но вы должны повторить этот шаблон для каждого объекта, соответствующего сценарию использования. (Например, вам нужен BazInRole1 чтобы получить доступ bin и BazInRole2 чтобы получить доступ biz)

Мой вопрос заключается в том, есть ли какой-нибудь способ избежать написания всех этих легко ошибочных, переполненных пространством имен. Например, я мог бы представить что-то вроде этого кода (который не компилируется):

class Foo[T] {
  T match { 
    case r1 : Role1 => def bar : Bar[T]
    case r2 : Role2 => def bat : Bat[T]
    case _ => //Nothing
  }
}

val fInRole1 = new Foo[Role1] //Provides Foo->Bar->Baz->Bin
val fInRole2 = new Foo[Role2] //Provides Foo->Bat->Baz->Buz

Кажется, что система типов Scala достаточно выразительна, чтобы делать что-то подобное, но я не могу понять это.

3 ответа

Не очень кратко, и члены там, просто невозможно использовать, но, возможно, движение в этом направлении будет приемлемым?

class Foo[R] {
  def bar(implicit ev: R <:< Role1) = new Bar[R] //Only relevant to Role 1
  def bat(implicit ev: R <:< Role2) = new Bat[R] //Only relevant to Role 2
}

Если я правильно понял ваш вопрос (в чем я не уверен), вы хотите Foo предоставить один из bar или же bat в зависимости от параметра типа Foo,

Мой первый выстрел будет:

class Bar
class Bat

trait BarExt { def bar = new Bar }
trait BatExt { def bat = new Bat }

trait Role
case object Role1 extends Role
case object Role2 extends Role

trait RoleProvider[From <: Role, To] {
  def apply(): To
}

object RoleProvider {
  implicit val r1 = new RoleProvider[Role1.type, Foo[Role1.type] with BarExt] {
    def apply() = new Foo[Role1.type] with BarExt
  }

  implicit val r2 = new RoleProvider[Role2.type, Foo[Role2.type] with BatExt] {
    def apply() = new Foo[Role2.type] with BatExt
  }
}

class Foo[T <: Role]

object Foo {
  def create[T <: Role, To](f: T)(implicit rp: RoleProvider[T,To]): To = rp()
}

чтобы

scala> Foo.create(Role1)
res1: Foo[Role1.type] with BarExt = RoleProvider$$anon$3$$anon$1@187b2d93    scala> Foo.create(Role1).bar

scala> Foo.create(Role1).bar
res2: Bar = Bar@7ea4b9da

scala> Foo.create(Role1).bat
<console>:12: error: value bat is not a member of Foo[Role1.type] with BarExt
              Foo.create(Role1).bat

а также

scala> Foo.create(Role2).bat
res3: Bat = Bat@56352b57

scala> Foo.create(Role2).bar
<console>:12: error: value bar is not a member of Foo[Role2.type] with BatExt
              Foo.create(Role2).bar

Можно избавиться от BarExt а также BatExt потянув соответствующие декларации в определения r1 а также r2Однако мне труднее работать с этим:

implicit val r1 = new RoleProvider[Role1.type, Foo[Role1.type] { val bar: Bar }] {
  def apply() = new Foo[Role1.type] { val bar = new Bar }
}

implicit val r2 = new RoleProvider[Role2.type, Foo[Role2.type] { val bat: Bat }] {
  def apply() = new Foo[Role2.type] { val bat = new Bat }
}

В итоге я все еще не уверен, что это именно то, о чем вы просили, или это так?

В этой статье о DCI автор представляет способ получить архитектуру DCI в Scala, которая выглядит как то, к чему вы стремитесь.

Основная идея состоит в том, чтобы определить методы, относящиеся к вашему варианту использования в признаке, но вместо вашего подхода он использует аннотацию самостоятельного типа, чтобы гарантировать, что он является объектом определенного базового класса.

Итак, чтобы сделать это немного более доступным: у вас есть класс данных Data, который содержит элементарные компоненты ваших объектов данных. Когда вы хотите реализовать определенный вариант использования, который любит рассматривать Data объект в определенной роли Role Вы можете подготовить роль следующим образом:

trait Role { self : Data => 
  def methodForOnlyThisUseCase = {...}
}

Для выполнения варианта использования вы затем создаете объект, специфичный для этой роли, с помощью:

val myUseCaseObject = new Data with Role

Как это, объект myUseCaseObject ограничивается именно его Data составляющие и методы, необходимые для его роли в данном сценарии использования.

Если это становится более сложным, вам, возможно, придется создать что-то вроде псевдоролевой черты, которая определяет методы, общие для нескольких вариантов использования. Самостоятельная аннотация роли варианта использования будет затем указывать на эту псевдотракт, в то время как псевдотрактовая аннотация собственного типа указывает на соответствующий класс данных.

Другие вопросы по тегам