Updating nested immutable data structures

后端 未结 5 1870
长情又很酷
长情又很酷 2020-12-14 02:39

I want to update a nested, immutable data structure (I attached a small example of a hypothetical game.) And I wonder whether this can be done a little more elegantly.

5条回答
  •  抹茶落季
    2020-12-14 03:19

    I don't know why you want to use classes here. I think you can leverage the power of pattern matching if you use records for holding data and keeping them minimal:

    // Types
    type Monster = {
        Awake: bool
        }
        with override x.ToString() =
                if x.Awake then "awake" else "asleep"
    type Room = {
        Locked: bool;
        Monsters: Monster list
        }
        with override x.ToString() =
                let state = if x.Locked then "locked" else "unlocked"
                state + "\n" + (x.Monsters |> List.mapi (fun i m -> sprintf "    Monster %d is %s" i (string m)) |> String.concat "\n")
    
    type Level = {
        Illumination : int;
        Rooms : Room list
        }
        with override x.ToString() =
                  (string x.Illumination) + "\n" + (x.Rooms |> List.mapi (fun i r -> sprintf "  Room %d is %s" i (string r)) |> String.concat "\n")
    
    type Dungeon = {
        Levels: Level list;
        }
        with override x.ToString() =
                x.Levels |> List.mapi (fun i l -> sprintf "Level %d: Illumination %s" i (string l)) |> String.concat "\n"
    

    To me, putting functions for manipulating Dungeon inside the class is unnatural. The code looks better if you put them in a module and make use of above declarations:

    /// Utility functions
    let updateMonster (m: Monster) a =
        {m with Awake = a}
    
    let updateRoom (r: Room) l monstersFunc =
        {   Locked = l; 
            Monsters = r.Monsters |> monstersFunc}    
    
    let updateLevel (l: Level) il roomsFunc = 
        {Illumination = il; Rooms = l.Rooms |> roomsFunc}
    
    let updateDungeon (d: Dungeon) levelsFunc =
        {d with Levels = d.Levels |> levelsFunc}
    
    
    /// Update functions
    let mapMonstersOnLevel (d: Dungeon) nLevel =
        let monstersFunc = List.map (fun m -> updateMonster m (not m.Awake))
        let roomsFunc = List.map (fun r -> updateRoom r r.Locked monstersFunc)
        let levelsFunc = List.mapi (fun i l -> if i = nLevel then updateLevel l l.Illumination roomsFunc else l)
        updateDungeon d levelsFunc
    
    let removeSleptMonsters (d: Dungeon) =
        let monstersFunc = List.filter (fun m -> m.Awake)
        let roomsFunc = List.map (fun r -> if r.Locked then updateRoom r false monstersFunc else r)
        let levelsFunc = List.map (fun l -> if l.Illumination < 100 then updateLevel l l.Illumination roomsFunc else l)
        updateDungeon d levelsFunc
    

    Then you can see manipulating these nested data structures is much easier. However, above functions still have redundancy. You can refactor more if you use lenses which come very natural with records. Check out the insightful article by Mauricio Scheffer, which is really close to this formulation.

提交回复
热议问题