使用镜头更新集合中元素的最佳方法是什么?例如:
case class Ingredient(name: String, quantity: Int)
case class Recipe(val ingredients: List[Ingredient])
如果我想使用镜片来制作新配方,改变单一成分的数量,最好的方法是什么?
我尝试的方法是动态制作镜头:Lens[List[Ingredient], Ingredient]
。虽然这感觉有点笨拙:
case class Recipe(val ingredients: List[Ingredient]) {
import Recipe._
def changeIngredientQuantity(ingredientName: String, newQuantity: Int) = {
val lens = ingredientsLens >=> ingredientLens(ingredientName) >=> Ingredient.quantityLens
lens.set(this, newQuantity)
}
}
object Recipe {
val ingredientsLens = Lens.lensu[Recipe, List[Ingredient]](
(r, i) => r.copy(ingredients = i),
r => r.ingredients
)
def ingredientLens(name: String) = Lens.lensu[List[Ingredient], Ingredient](
(is, i) => is.map (x => if (x.name == name) i else x),
is => is.find(i => i.name == name).get
)
}
case class Ingredient(name: String, quantity: Int)
object Ingredient {
val quantityLens = Lens.lensu[Ingredient, Int](
(i, q) => i.copy(quantity = q),
i => i.quantity
)
}
答案 0 :(得分:6)
您无法在给定索引处的List [T]和T之间创建镜头,因为镜头需要您关注的对象始终存在。但是,如果在List或另一个集合中查找,索引中可能没有元素。
然而,你可以使用Traversal,一种专注于0到多个元素的镜头。使用Monocle,您将使用索引函数创建从List到特定索引处的元素的遍历:
import monocle.SimpleLens
import monocle.syntax.lens._ // to use |-> and |->> instead of composeLens, composeTraversal
import monocle.functions.Index._ // to use index Traversal
// monocle also provides a macro to simplify lens creation
val ingredientsLens = SimpleLens[Recipe, List[Ingredient]](_.ingredients, (recipe, newIngredients) => recipe.copy(ingredients = newIngredients))
val quantityLens = SimpleLens[Ingredient, Int](_.quantity , (ingredient, newQuantity) => ingredient.copy(quantity = newQuantity))
val applePie = Receipe(List(Ingredient("apple", 3), Ingredient("egg", 2), ...))
applePie |-> ingredientsLens |->> index(0) headOption // Some(Ingredient("apple", 3))
applePie |-> ingredientsLens |->> index(999) headOption // None
applePie |-> ingredientsLens |->> index(0) |->> quantityLens headOption // 3
applePie |-> ingredientsLens |->> index(0) |->> quantityLens set 5
// Receipe(List(Ingredient("apple", 5), Ingredient("egg", 2), ...))
答案 1 :(得分:1)
如果您想根据名称进行更新,请Map
name -> quantity
怎么样?然后你可以使用这里描述的解决方案:
Scalaz: how to compose a map lens with a value lens?
如果坚持List
,仍然可以使用partial lenses from Scalaz。函数listLookupByPLens
看起来很有希望。