2017-05-30 5 views
0

Javaのような変更可能な世界で簡単にできることをScalaで手に入れようと苦労しています。プログラムとして取得する新しい情報を追加する方法

私はListのインスタンスを持っています。 (ListのクラスItem、またはList[Item]としましょう)プログラムが進行するにつれて、それぞれについてItemについての詳細情報が得られます。 (これらのクラスをItemAttributeAItemAttributeBなどと呼ぼう)ItemAttributeAがどのItemに対応しているかを忘れないように、私はそれらをListのようにList[(Item, ItemAttributeA, ItemAttributeB)]のようにしておきたいと思います。

このタプルのリストは既にかなり醜いように見えますが、いくつかのケースクラスがあれば、はるかにひどいことになります。 (のような変更可能な世界ではList[(Item, ItemAttributeA, ItemAttributeB, ItemAttributeC, ItemAttributeD, ItemAttributeE, ItemAttributeF, ...)]

、私たちは以下のようなクリーンなコードを書くことができます。

case class Item(id: Int) { 
    var attrA: ItemAttributeA = null // 
    var attrB: ItemAttributeB = null // fill these fields later 
} 
val getItemAttributeA: List[Item] => List[ItemAttributeA] 
val getItemAttributeB: List[Item] => List[ItemAttributeB] 

val items = List(Item(1)) 
items.zip(getItemAttributeA(items)).foreach { case (item, itemAttrA) => 
    item.attrA = itemAttrA 
} 
items.zip(getItemAttributeB(items)).foreach { case (item, itemAttrB) => 
    item.attrB = itemAttrB 
} 

たぶん、あなたはOptioncopyを使用する場合は、変更可能なフィールドを必要としないが、それは少し厄介になってきた。そして、まだ、それはいくつかの問題を抱えている。

case class Item(
    id: Int, 
    attrA: Option[ItemAttributeA], 
    attrB: Option[ItemAttributeB] 
) 

val getItemAttributeA: List[Item] => List[ItemAttributeA] // I get list of information at once for a performance reason. 
val getItemAttributeB: List[Item] => List[ItemAttributeB] 

val items = List(Item(1, None, None)) 
val itemsWithA = items.zip(getItemAttributeA(items)).map { case (item, itemAttrA) => 
    item.copy(attrA = Some(itemAttrA)) 
} 
val itemsWithAandB = itemsWithA.zip(getItemAttributeB(itemsWithA)).map { case (item, itemAttrB) => 
    item.copy(attrB = Some(itemAttrB)) 
} 

// All item, itemWithAttrA, itemWithAttrB have same type Item. 
// This doesn't sound good because we can't know which instance have the information we want by just looking at the type. 

私の現在の最善の解決策は、フィールドとしてのみ1ケースクラスを持っている特性を利用している。

trait HasItem { val item: Item } 
trait HasItemAttributeA { val itemAttrA: ItemAttributeA } 
trait HasItemAttributeB { val itemAttrB: ItemAttributeB } 

val getItemAttributeA: List[HasItem] => List[HasItem with HasItemAttributeA] 
val getItemAttributeB: List[HasItem with HasItemAttributeA] => List[HasItem with HasItemAttributeA with HasItemAttributeB] 

val hasItem = new HasItem { val item = Item(1) } 
val hasItemWithA = getItemWithA(hasItem) 
val hasItemWithAandB = getItemAttributeB(hasItemWithA) 

まあ、それほど気にならない。しかし、少なくともそれはあなたがタプルで満足できないいくつかのニーズを満たしています。たとえば、このようなケース。

// you want to add ItemAttributeX to each Item in the list. 
// but you only need Item and ItemAttributeB to get ItemAttributeX 
// you can express that by using type parameters 
def getItemAttributeX[I < HasItem with HasItemAttributeB](listI: List[I]): List[I with HasItemAttributeX] 

val itemsWithManyAttributes: List[HasItem with HasItemAttributeA with HasItemAttributeB with HasItemAttributeC with HasItemAttributeD with HasItemAttributeE] 
val itemsWithManyAttributesAndX = getItemAttributeX(itemsWithManyAttributes) 

これは何とか動作しますが、他にも多くの問題があります。 (可読性、多くの定型文、新しいインスタンスを何度も作成するなど)

私の質問は、うまくいけばうまくいけば、機能的なやり方でこれらの問題を解決する最良の方法は何ですか?

答えて

1

私はこのケースでは可変性は大丈夫(-ish)だと思いますが、すべての情報を収集するためだけの可変クラスで古典的なビルダーパターンを使用することをお勧めします。すべての情報を収集すると、そのビルダーから不変オブジェクトが生成されます(実際に必要なすべての情報が含まれているテストを含む)。 Scala API自体はそのようにします(たとえば、ListBufferを記入し、完了したら不変のListを作成します)。あなたが空想的になりたいならば、type safe builder patternさえあります。つまり、このような状況に対処する純粋に機能的な方法があります。作家のモナド。しかし、ScalaはHaskellではないので、大砲を抜く前に、より簡単な状況での関数型プログラミングに慣れることができます(そして、そうすべきです)。

関連する問題