首页 > 解决方案 > 如何使用列表更新嵌套记录结构

问题描述

有人可以告诉我如何更新嵌套记录中的子项吗?我想为值为“B”的项目设置isSelectedtrue

type MyItem = {isSelected:bool; value:string}
type MyModel = {list:MyItem list}

let a = {isSelected = false; value = "A"}
let b = {isSelected = false; value = "B"} 
let c = {isSelected = false; value = "C"}
let m = {list = [a;b;c]}

let m2 = { m with list = { m.list with ???  = { ??? }}} 

我不会使用可变数据结构。

标签: f#

解决方案


不变性很棒,但是在处理嵌套的不可变结构时,它可能会有点麻烦。特别是如果它嵌套得很深。

解决这个问题的一种方法是所谓的镜头。

所以我稍微增加了示例的嵌套级别,以便让镜头的价值更加明显。

module Lenses =
  // This lens is a pair of function, a getter that get's inner value of an object
  //  and a setter that sets the inner value of an object
  //  The cool thing is that a lens is composable meaning we can create a lens
  //  that allows us to get and set a deeply nested property succinctly
  type Lens<'O, 'I> = L of ('O -> 'I)*('I -> 'O -> 'O)

  let lens (g : 'O -> 'I) (s : 'I -> 'O -> 'O) = L (g, s)

  // Gets an inner value
  let get     (L (g, _)) o    = g o
  // Sets an inner value
  let set     (L (_, s)) i o  = s i o
  // Updates an inner value given an updater function that sees the
  //  inner value and returns a new value
  let over    (L (g, s)) u o  = s (u (g o)) o

  // Compose two lenses into one, allows for navigation into deeply nested structures
  let compose (L (og, os)) (L (ig, is)) =
    let g o   = ig (og o)
    let s i o = os (is i (og o)) o
    L (g, s)

  type Lens<'O, 'I> with
    static member (-->) (o, i) = compose o i

open Lenses

// I made the model a bit more complex to show benefit of lenses

type MySelection =
  {
    isSelected: bool
  }

  // Define a lens that updates the property, this code can potentially be generated
  //  Scala does this with macros, in F# there are other possibilities
  static member isSelectedL : Lens<MySelection, bool> = lens (fun o -> o.isSelected) (fun i o -> { o with isSelected = i })

type MyValue =
  {
    value: string
  }

  static member valueL : Lens<MyValue, string> = lens (fun o -> o.value) (fun i o -> { o with value = i })

type MyItem   = 
  {
    selection : MySelection
    value     : MyValue
  }

  static member selectionL  : Lens<MyItem, MySelection> = lens (fun o -> o.selection) (fun i o -> { o with selection  = i })
  static member valueL      : Lens<MyItem, MyValue>     = lens (fun o -> o.value    ) (fun i o -> { o with value      = i })

type MyModel  = 
  {
    list: MyItem list
  }

  static member listL : Lens<MyModel, MyItem list> = lens (fun o -> o.list) (fun i o -> { o with list = i })

[<EntryPoint>]
let main argv =
  // Define example model
  let a = {selection = {isSelected = false}; value = {value = "A"}}
  let b = {selection = {isSelected = false}; value = {value = "B"}}
  let c = {selection = {isSelected = false}; value = {value = "C"}}
  let m = {list = [a;b;c]}

  // Print it
  printfn "%A" m

  // Update the model
  let m2 = 
    let mapper (v : MyItem) = 
      // Grabs the nest value using lens composition
      let nestedValue = v |> get (MyItem.valueL --> MyValue.valueL)
      let isSelected = nestedValue = "B"
      // Set the nested isSelected using lens composition
      v |> set (MyItem.selectionL --> MySelection.isSelectedL) isSelected
    // Maps nested list property
    m |> over MyModel.listL (List.map mapper)
  printfn "%A" m2

  0

推荐阅读