函数式编程:如何避免生命值扣除场景下变量的可变性——宝可梦currentHP问题求解
嘿,很高兴你已经把大部分变量都改成不可变了——这在F#里绝对是值得坚持的好实践!针对currentHP的可变问题,核心思路其实很简单:别直接修改原对象,而是创建一个带着更新后HP的新宝可梦实例,毕竟不可变类型的设计初衷就是通过返回新值来实现状态变化的,而不是原地突变。
第一步:修改战斗函数,用不可变方式更新HP
首先看你原来的代码,直接通过<-修改原宝可梦的currentHP,这打破了不可变的原则。我们可以利用F#记录类型的with语法(假设你的Domain.Pokemon是记录类型,这也是F#里定义数据结构的常规方式),创建一个更新后的新宝可梦实例,再返回新的战斗配对列表:
let angriffFighter (fighterPair: list<Domain.Pokemon>) = // 先把配对里的攻受双方解构出来,代码更清晰 let attacker, defender = fighterPair.[0], fighterPair.[1] let dmg = hit attacker defender // 创建新的defender实例,只更新currentHP,其他属性保持不变 let updatedDefender = { defender with currentHP = defender.currentHP - dmg } // 返回全新的战斗配对列表,原列表和原defender实例都不会被修改 [attacker; updatedDefender]
这里的{ defender with currentHP = ... }是关键——它会生成一个和原defender完全一样的新实例,只有currentHP被替换成我们计算后的值,原对象丝毫不被改动。
第二步:战斗结束后重置HP
同理,重置HP的时候也不需要修改原实例,直接生成一个currentHP设为maxHP的新宝可梦就行:
let resetPokemonHP (pokemon: Domain.Pokemon) = { pokemon with currentHP = pokemon.maxHP }
调用方式的变化
因为现在函数是返回新的状态,而不是修改原状态,所以调用的时候需要接收返回的新值:
// 初始战斗配对 let initialFighters = [myCharizard; opponentBlastoise] // 调用攻击函数,得到攻击后的新配对 let afterAttackFighters = angriffFighter initialFighters // 战斗结束后重置HP,得到重置后的新宝可梦 let resetCharizard = resetPokemonHP afterAttackFighters.[0]
这样一来,initialFighters始终保持初始状态,afterAttackFighters是攻击后的状态,resetCharizard是重置后的状态,所有状态都不可变,完全符合你的需求。
额外小提示
如果你的Domain.Pokemon不是记录类型(比如是类),那更建议改成记录类型——F#的记录天生支持不可变和便捷的更新语法,非常适合这种需要追踪状态变化的场景。如果实在要保留类,那可以给类添加一个返回新实例的方法,比如:
type Pokemon(name: string, maxHP: int, currentHP: int) = member val Name = name with get member val MaxHP = maxHP with get member val CurrentHP = currentHP with get // 添加方法返回更新HP后的新实例 member this.WithUpdatedHP newHP = Pokemon(this.Name, this.MaxHP, newHP)
然后在战斗函数里用defender.WithUpdatedHP (defender.CurrentHP - dmg)来生成新实例,原理和记录类型是一样的。
内容的提问来源于stack exchange,提问作者Psyman2

