Scala镜头用于收集参数

Geo*_*off 5 functional-programming scala scalaz lenses

使用镜头更新集合中元素的最佳方法是什么?例如:

case class Ingredient(name: String, quantity: Int)
case class Recipe(val ingredients: List[Ingredient])
Run Code Online (Sandbox Code Playgroud)

如果我想使用镜片来创建一个单一成分的数量改变的新配方,那么最好的方法是什么?

我试过的方法是动态制作镜头: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
  )
}
Run Code Online (Sandbox Code Playgroud)

Jul*_*aut 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), ...))
Run Code Online (Sandbox Code Playgroud)


Jam*_*abe 1

如果您想根据名称进行更新,那么使用Mapof怎么样name -> quantity?然后您可以使用此处描述的解决方案:

Scalaz:如何将地图镜头与价值镜头组合起来?

如果您坚持使用List,仍然可以使用Scalaz 的部分镜头。该功能listLookupByPLens看起来很有前途。