在程序中,应该如何添加您在程序中获得的新信息,以不可变的语言进行实例



我一直在艰难地完成在Scala中完成工作的时间,这在像Java这样的可变世界中很容易做到。

我有 List的实例。(我们将它们称为Item类或List[Item]List(,随着程序的进行,我们将获得有关每个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.

我目前的最佳解决方案是使用只有一个案例类作为字段的特征。

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)

即使以某种方式起作用,还有很多其他问题。(可读性,很多锅炉,创建了很多次新实例等等……(

(

我的问题是,希望以功能性的方式解决这些问题的最佳方法是什么?

我认为在这种情况下是可以的(-ish(的,但是我建议使用经典的构建器模式,并使用可变的类来收集所有信息。当您收集所有信息时,您会从该构建器中生成不变的对象(包括一个您真正需要的所有信息的测试(。请注意,Scala API本身这样做(例如,填充ListBuffer,并在完成后创建一个不变的List(。如果您想花哨,甚至还有类型的安全构建器图案。

也就是说,有一些纯粹有效的方法可以处理这种情况,例如作家蒙达德。但是,Scala不是Haskell,因此您可以(并且应该(在更轻松的情况下(在拔出大型枪支之前(对功能性编程感到满意。

最新更新