我一直很难在Scala中完成任务,这在像Java这样的可变世界中很容易做到。
我有List
个实例。 (让我们称他们List
为Item
,或List[Item]
}。随着程序的继续,我们会获得有关每个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)
即使这种方式有所作为,但还有很多其他问题。 (可读性,很多样板,多次创建新实例等等)
我的问题是,希望以功能性方式解决这些问题的最佳方法是什么?
答案 0 :(得分:1)
我认为在这种情况下,可变性是可以的(-ish),但我建议使用经典的构建器模式,使用可变类来收集所有信息。收集所有信息后,您将从该构建器生成不可变对象(包括您确实拥有所需信息的测试)。请注意,Scala API本身就是这样做的(例如,填写ListBuffer
,并在完成后创建一个不可变的List
。如果你想要花哨,甚至有type safe builder pattern。
尽管如此,有一些纯粹的功能性方法来处理这种情况,例如:作家monad。但是,Scala不是Haskell,所以你可以(而且应该)在更容易的情况下先熟悉函数编程,然后再拔出大枪。