Может ли 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 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 достаточно выразительна, чтобы сделать что-то подобное, но я не могу понять это.

+5
источник поделиться
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
}
+1
источник

Если я правильно понял ваш вопрос (что я не уверен), вы хотите 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 = [email protected]    scala> Foo.create(Role1).bar

scala> Foo.create(Role1).bar
res2: Bar = [email protected]

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 = [email protected]

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 }
}

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

0
источник

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

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

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

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

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

val myUseCaseObject = new Data with Role

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

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

0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос