Concise way of "updating" element in nested collections Imagine simple game:

type Combatant = {
    hp : int
    attack : int
}

type CombatantGroup = Combatant list
type CombatantGroups = CombatantGroup list

type Battle = {
    combatantGroups : CombatantGroups
}



there is some battle, in which some amount groups participate. In each group there is several combatants. Now I want to implement a function for one combatant  to attack another. Due to immutability, I have to replace whole attacked target, and whole group and whole battle. If the battle was just between two participants, I could write code like this:

if target = battle.combatant1 then
    { battle with combatant1 = { battle.combatant1 with hp = battle.combatant1.hp - attacker.attack }}
else
    { battle with combatant2 = { battle.combatant2 with hp = battle.combatant2.hp - attacker.attack }}


which is also terrible code, I would appreciate any advice on it. But with nested collections it gets even more complicated, and event doesn't look functional to me anymore:

let containsTarget = List.contains target        
let transformCombatant combatant = if combatant = target then { combatant with hp = combatant.hp - attacker.attack } else combatant        
let transformGroup = List.map transformCombatant                
let checkGroup group =
    if group |&gt; containsTarget then
        group |&gt; transformGroup
    else
        group        
let transformGroups = List.map checkGroup

{ battle with combatantGroups = transformGroups battle.combatantGroups }


Can you please give me advise on how can I use features of F# and functional programming, to make given code more pretty and concise?
I think you need an id field to be sure that you're updating the correct combatant. I assume it's possible to have multiple combatants with the same hp and attack.
And in that case it makes sense to store a group as a Map of id to combatant. This makes it really simple to update a value by id and there's no need to check for existence first. See the tryUpdateCombatantById function below.
You can add small helper functions to update specific record fields with a function that is passed in.


Here's the code with all of those changes:

type Combatant = {
    id : int
    hp : int
    attack : int
}

type Battle = { combatantGroups : Map&lt;int, Combatant&gt; list }

let updateHp f combatant = { combatant with hp = f combatant.hp }
let removeHp attack = updateHp (fun hp -&gt; hp - attack)

let tryUpdateCombatantById combatantId f group =
    group
    |&gt; Map.tryFind combatantId
    |&gt; Option.map (fun c -&gt; group |&gt; Map.add c.id (f c))
    |&gt; Option.defaultValue group

let applyAttack targetId attacker battle =
    { battle with
        combatantGroups =
            battle.combatantGroups
            |&gt; List.map (tryUpdateCombatantById targetId (removeHp attacker.attack)) }