2015-02-24 2 views
4

Я читаю структурированный JSON, используя JSON для Play Frameworks, чтобы создать граф объектов с классами case.Как украсить неизменяемый граф объектов из классов шкалы scala

Пример:

case class Foo (
         id: Int, 
         bar_id: Int, 
         baz_id: Int, 
         x: Int, 
         y: String 
         ) 
{ 
    var bar: Bar = null 
    var baz: Baz = null 
} 

После построения Foo, я должен вернуться позже и украсить его, установив бар и БАЗ. Они определены в других файлах JSON и известны только при завершении всего разбора. Но это означает, что Foo не может быть неизменным.

Что такое «правильный» способ в Scala сделать неизменный объект, а затем украшенную его версию, не повторяя каждое поле Foo несколько раз снова и снова?

Я знаю несколько способов, которые чувствуют себя неправильно:

  • грим «бар: Вариант [Бар]» и «БАЗ: Опция [Баз]» параметры класса регистра, а затем я могу использовать «копировать», чтобы сделать новые версии класса Foo с их набором чего-то; но тогда я должен проверять их каждый раз, когда они обращаются - неэффективны, небезопасны, не могут сделать DecoratedFoo, который, как гарантируется, имеет правильную структуру.
  • сделать второй класс case, который является копией всех структура в первом, но добавление двух дополнительных декорированных параметров - но это означает повторение всего списка параметров в определении и снова при создании экземпляров его.
  • Наследование класса класса, по-видимому, противоречиво и в любом случае также представляется требуют, чтобы я повторял каждый параметр в любом случае, в конструкторе подкласса?
  • Внесите супер-класс, отличный от случая, в котором перечислены общие параметры класса случая. Затем расширьте его в классе case. Но это все равно потребует повторения каждого отдельного параметра в конструкторе подкласса.
  • Я вижу блоги с людьми, которые говорят об этой проблеме и используют отражение во время выполнения, чтобы заполнить базовые атрибуты их украшенных копий - это позволяет избежать эха, но теперь у вас нет безопасности типа, указав имена атрибутов как строки, накладные расходы и т. Д. ...

Уверен, что у Scala должен быть способ, чтобы люди составляли более сложные неизменяемые объекты из более простых, без необходимости копировать каждую их часть вручную?

+1

Это больная точка для меня. Мне кажется, что общая проблема заключается в объявлении базовой модели данных, а затем, сухим способом, определении производных/дополненных моделей, которые являются преобразованиями исходного. До сих пор я не нашел общего решения проблемы. – acjay

ответ

1

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

import scala.language.higherKinds 

object Acme { 
    case class Foo[T[X] <: Option[X] forSome { type X }](a: Int, 
                 b: String, 
                 c: T[Boolean], 
                 d: T[Double]) 

    // Necessary, Foo[None] won't compile 
    type Unprocessed[_] = None.type 
    // Just an alias 
    type Processed[X] = Some[X] 
} 

Пример использования:

import Acme._ 

val raw: Foo[Unprocessed] = Foo[Unprocessed](42, "b", None, None) 

def process(unprocessed: Foo[Unprocessed]): Foo[Processed] = 
    unprocessed.copy[Processed](c = Some(true), d = Some(42d)) 

val processed: Foo[Processed] = process(raw) 

// No need to pattern match, use directly the x from the Some case class 
println(processed.c.x) 
println(processed.d.x) 

Я использовал этот раз в мой текущий проект. Основная проблема, с которой я столкнулся, - это когда я хочу, чтобы Foo был ковариантным.


В качестве альтернативы, если вы не заботитесь о границе T:

case class Foo[+T[_]](a: Int, b: String, c: T[Boolean], d: T[Double]) 

, то вы можете использовать Foo[Unprocessed] или Foo[Processed], когда вам нужно Foo[Option].

scala> val foo: Foo[Option] = processed 
foo: Acme.Foo[Option] = Foo(42,b,Some(true),Some(42.0)) 
+0

Я как бы изумлен Скалой, что это возможно. К сожалению, я не могу его скомпилировать. Операторы вашего типа для Unprocessed and Processed терпят неудачу с «ожидаемым определением класса или объекта». Я заметил, что если я поместил их в тело Foo {}, они компилируются, но тогда ваш пример использования не работает, так как обработанные/необработанные символы неизвестны. – user2057354

+0

@ user2057354 Да, вы должны поместить объявления типа внутри объекта (не внутри класса Foo). Затем вы можете импортировать их при необходимости. Это может быть в пакете объекта пакета Foo, например, или сопутствующего объекта Foo. – Dimitri

+0

Я думаю, что это так здорово. И это делает его компиляцией и запуском. Но в сценарии, который я описал, он не работает с JSON Reads. Наличие дополнительных полей/параметров ломает Reads («Отсутствующие аргументы для метода применяются в объекте Foo» - поскольку теперь у меня есть дополнительные поля/атрибуты на Foo, которые не находятся в JSON). Это так, даже если я укажу значение по умолчанию «Нет» для c или d, что кажется неправильным. – user2057354

1

Еще одна стратегия может быть, чтобы создать еще один случай класс:

case class Foo(
    id: Int, 
    bar_id: Int, 
    baz_id: Int, 
    x: Int, 
    y: String 
) 

case class ProcessedFoo(
    foo: Foo, 
    bar: Bar, 
    baz: Baz 
) 
+0

Я вижу. И, возможно, это наименьшее из всех зол, хотя для каждой ссылки для foo требуется косвенное обращение. – user2057354

1

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

case class Foo(bar: Int) 

trait HasBaz { 
    val baz: Int 
} 

class FooWithBaz(val foo: Foo, val baz: Int) extends HasBaz 

object FooWithBaz { 
    implicit def innerFoo(fwb: FooWithBaz): Foo = fwb.foo 

    implicit class RichFoo(val foo: Foo) extends AnyVal { 
     def withBaz(baz: Int) = new FooWithBaz(foo, baz) 
    } 
} 

Итак, вы можете сделать:

import FooWithBaz._ 
Foo(1).withBaz(5) 

И, хотя withBaz возвращает FooWithBaz, мы можем обрабатывать возвращаемое значение, например, Foo, из-за неявного преобразования.

+0

Я очарован этим подходом. Но он не компилируется, и я боюсь, поскольку я все еще просто борюсь с тем, что вы пытаетесь сделать здесь, что я пока не могу решить проблему. – user2057354

+0

Ошибка: (13, 16) Play 2 Компилятор: Foo.scala: 13: RichFoo уже определена как (сгенерированного компилятором) Метод RichFoo неявного класса RichFoo (Val Foo: Foo) продолжается AnyVal { ^ – user2057354

+0

Эта ошибка кажется совершенно странный и бесполезный - я все еще изучаю неявное преобразование и извиняюсь, если решение очевидно? – user2057354

Смежные вопросы