如何使用列表更新嵌套记录结构

时间:2019-11-19 21:27:42

标签: f#

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

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 ???  = { ??? }}} 

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

2 个答案:

答案 0 :(得分:1)

使用List.map

let m2 = 
  { m with list = 
    List.map (fun item -> 
      if item.value = "B" then 
        { item with isSelected = true }
      else
        item)
      m.list
  }

这将创建一个新列表,其中每个项目均与以前相同,但我们要“更新”的项目除外,因为我们将其替换为isSelectedtrue的新项目。

答案 1 :(得分:1)

不可移植性很好,但是当处理嵌套的不可更改结构时,它可能会变得有些毛茸茸。尤其是如果它嵌套得很深。

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

因此,我稍微提高了示例的嵌套级别,以使镜头的值更清晰可见。

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