6

レンズを使用してコレクション内の要素を更新する最良の方法は何ですか? 例えば:

case class Ingredient(name: String, quantity: Int)
case class Recipe(val ingredients: List[Ingredient])

レンズを使用して、1 つの材料の量を変更した新しいレシピを作成したい場合、どのように行うのが最適ですか?

私が試したアプローチは、オンザフライでレンズを作成することです: 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
  )
}
4

2 に答える 2

6

特定のインデックスで List[T] と T の間にレンズを作成することはできません。これは、Lens ではフォーカスしているオブジェクトが常に存在する必要があるためです。ただし、リストまたは別のコレクションでのルックアップの場合、インデックスに要素がない可能性があります。

ただし、0 から多くの要素に焦点を合わせる一種のレンズである Traversal を使用することもできます。Monocleでは、 index 関数を使用して、リストから特定のインデックスの要素へのトラバーサルを作成します。

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), ...))
于 2014-04-30T15:45:44.757 に答える
1

名前に基づいて更新したい場合は、Mapname -> quantity? 次に、ここで説明するソリューションを使用できます。

Scalaz: バリューレンズでマップレンズを構成する方法は?

に固執すれば、ScalazListの部分的なレンズを引き続き使用できます。機能は有望に見えます。listLookupByPLens

于 2013-10-31T22:46:59.047 に答える