oncise way of “updating” element in nested collections

#1

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 |> containsTarget then
        group |> 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?

#2

Can you providing verifiable code? From the snippet it’s not obvious where attacker from the transformCombatant function and target for containsTarget are defined.

Yet I can write few comments:

I’d put { combatant with hp = combatant.hp - attacker.attack } into separate function or even operator:

let update combatant attacker = 
    { combatant with hp = combatant.hp - attacker.attack }

(with meaningful naming, of course - update here is just for example)

Also not sure it is really good using of List.contains. It can even give worst performance in some cases.

Edit:

If you’re looking for some functional concepts that fits for updating of nested type then there are lenses and prisms:

Lenses

Prisms

Libraries:

Aether - optics for F#
FSharpPlus - A complete and extensible base library for F#

I didn’t use them but who knows maybe it help you.