我一直在艰难地完成在Scala中完成工作的时间,这在像Java这样的可变世界中很容易做到。
我有 List
的实例。(我们将它们称为Item
类或List[Item]
的List
(,随着程序的进行,我们将获得有关每个Item
的更多信息。(让我们称这些类ItemAttributeA
,ItemAttributeB
和等等……(为了不忘记哪个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
}
,如果您使用Option
和copy
,也许您不需要可变的字段,但是它会变得有些混乱。尽管如此,它还是有一些问题。
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,因此您可以(并且应该(在更轻松的情况下(在拔出大型枪支之前(对功能性编程感到满意。