F# 如何从外部功能转变为功能性镜片
在我开始更好地使用函数式编程的过程中,在SO家族的一位成员的帮助下,我发现了什么。我甚至通过下面的链接对其进行了一些研究,以了解更多关于它们的信息F# 如何从外部功能转变为功能性镜片,f#,functional-programming,lenses,F#,Functional Programming,Lenses,在我开始更好地使用函数式编程的过程中,在SO家族的一位成员的帮助下,我发现了什么。我甚至通过下面的链接对其进行了一些研究,以了解更多关于它们的信息 有了这些知识,我想我可以尝试一下,看看我是否能理解它们的功能以及它们在FP中有用的原因。目前我的问题是从定义的类型成员转移到访问和修改我的设备记录中的字段,我已经为我目前正在制作原型的游戏定义了这些字段。我会把设备记录的片段,以前在那里的成员和我试图创建的功能镜头,但就是不起作用。在第一次模式匹配之后,它希望代码具有相同的返回值,而我希望它是一
有了这些知识,我想我可以尝试一下,看看我是否能理解它们的功能以及它们在FP中有用的原因。目前我的问题是从定义的类型成员转移到访问和修改我的设备记录中的字段,我已经为我目前正在制作原型的游戏定义了这些字段。我会把设备记录的片段,以前在那里的成员和我试图创建的功能镜头,但就是不起作用。在第一次模式匹配之后,它希望代码具有相同的返回值,而我希望它是一个要返回的常规值,这取决于我已成功匹配的模式! 对于代码的剩余部分,我认为最好将重要的代码片段放在这里,并公开相关代码,这样您就可以在本地机器上编译它,而不是在您试图帮助我的时候忽略代码并使其不可编译!可以找到公开的要点。我的定义有很多,相关代码来自第916行
type Equipment = {
Helmet : Hat option
Armor : Armor option
Legs : Pants option
Gloves : Gauntlets option
Ring : Ring option
Weapon : Weaponry option
Shield : Shield option
Loot : ConsumableItem option
}
let equipPurchasedProtection newItem (inventory,equipment) =
match newItem with
| Helmet ->
match equipment.Helmet with
| None ->
let newEquipment = { equipment with Helmet = Some newItem }
(inventory,newEquipment)
| Some oldHelm
if (playerWantsToAutoEquip newItem) then
let newEquipment = { equipment with Helmet = Some newItem }
let newInventory = inventory |> addToInventory oldHelm
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
| Gloves ->
match equipment.Hands with
| None ->
let newEquipment = { equipment with Hands = Some newItem }
(inventory,newEquipment)
| Some oldGloves
if (playerWantsToAutoEquip newItem) then
let newEquipment = { equipment with Hands = Some newItem }
let newInventory = inventory |> addToInventory oldGloves
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
| Boots ->
match equipment.Feet with
| None ->
let newEquipment = { equipment with Boot = Some newItem }
(inventory,newEquipment)
| Some oldBoots
if (playerWantsToAutoEquip newItem) then
let newEquipment = { equipment with Boot = Some newItem }
let newInventory = inventory |> addToInventory oldBoots
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
let equipPurchasedItem newItem (inventory,equipment) =
let equipFunction =
match newItem with
| Protection(Helmet(_)) -> genericEquipFunction HelmetFun_
| Protection(Gloves(_)) -> genericEquipFunction GlovesFun_
| Protection(Legs(_)) -> genericEquipFunction LegsFun_
| Protection(Armor(_)) -> genericEquipFunction ArmorFun_
| Protection(Ring(_)) -> genericEquipFunction RingFun_
| Protection(Shield(_)) -> genericEquipFunction ShieldFun_
| Weapon _ -> genericEquipFunction WeaponFun_
| Consumable HealthPotion -> genericEquipFunction LootFun_
| Consumable HighHealthPotion -> genericEquipFunction LootFun_
| Consumable MegaHealthPotion -> genericEquipFunction LootFun_
| Consumable Elixir -> genericEquipFunction LootFun_
| Consumable HighElixir -> genericEquipFunction LootFun_
| Consumable MegaElixir -> genericEquipFunction LootFun_
| Consumable PhoenixFeather -> genericEquipFunction LootFun_
| Consumable MedicinalHerb -> genericEquipFunction LootFun_
let itemForInventory,newEquipment = equipFunction (Some newItem) equipment
match itemForInventory with
| None -> (inventory,newEquipment)
| Some item ->
let newInventory = inventory |> addToInventory { Item = item; Count = 1 }
(newInventory,newEquipment)
更新1
下面是我用来装备购买物品的镜头功能之一
let getArmorFun e = e.Armor
let equipArmorFun newArmor e = { e with Armor = newArmor }
let ArmorFun_ = (getArmorFun, equipArmorFun)
仔细看了你的模型后,我可以确认我最初的印象:你使用的类型比你应该使用的多得多。这些类型中的许多应该是实例;在本例中,记录实例。对于何时应该使用类型或实例,这里有一个很好的经验法则。如果这两个东西可以互换,那么它们应该是同一类型的两个实例。如果它们不能互换,那么(并且只有在那时)它们应该是两种不同的类型。这里有一个例子来说明我的意思。以下是占据整个屏幕的代码部分:
type Weaponry =
| Dagger of Dagger
| Sword of Sword
| Axe of Axe
| Spear of Spear
| Staff of Staff
| LongBlade of Blade
| Spellbook of Spellbook
with
member x.Name =
match x with
| Dagger d -> d.ToString()
| Sword s -> s.ToString()
| Axe a -> a.ToString()
| Spear s -> s.ToString()
| Staff s -> s.ToString()
| LongBlade lb -> lb.ToString()
| Spellbook sb -> sb.ToString()
member x.Price =
match x with
| Dagger w -> w.Price
| Sword w -> w.Price
| Axe w -> w.Price
| Spear w -> w.Price
| Staff w -> w.Price
| LongBlade w -> w.Price
| Spellbook w -> w.Price
member x.Weight =
match x with
| Dagger w -> w.Weight
| Sword w -> w.Weight
| Axe w -> w.Weight
| Spear w -> w.Weight
| Staff w -> w.Weight
| LongBlade w -> w.Weight
| Spellbook w -> w.Weight
member x.Stats =
match x with
| Dagger w -> w.WeaponStats :> IStats
| Sword w -> w.WeaponStats :> IStats
| Axe w -> w.WeaponStats :> IStats
| Spear w -> w.WeaponStats :> IStats
| Staff w -> w.WeaponStats :> IStats
| LongBlade w -> w.WeaponStats :> IStats
| Spellbook w -> w.SpellStats :> IStats
所有这些项目之间有什么不同?最后一行,其中法术书
具有法术书状态
,而不是武器状态
。就这样!至于你的其他武器类型——匕首、剑、斧、矛等等。。。它们的“形状”都是一样的。他们都有武器数据、价格、重量等
这是对整个武器模型的重新设计:
type ItemDetails = { Weight: float<kg>; Price: int<usd> }
type PhysicalWeaponType =
| Dagger
| Sword
| Axe
| Spear
| Staff
| LongBlade
type MagicalWeaponType =
| Spellbook
// Could later add wands, amulets, etc.
type WeaponDetails =
| PhysicalWeapon of PhysicalWeaponType * WeaponStat
| MagicalWeapon of MagicalWeaponType * SpellbookStats
type Weaponry =
{ Name: string
ItemDetails: ItemDetails
WeaponDetails: WeaponDetails }
with member x.Weight = x.ItemDetails.Weight
member x.Price = x.ItemDetails.Price
member x.Stats = match x.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats :> IStats
| MagicalWeapon (_, stats) -> stats :> IStats
// Now let's create some weapons. In the real game this would be read
// from a JSON file or something, so that the game is easily moddable
// by end users who want to add their own custom weapons.
let rustedDagger = {
Name = "Rusted dagger"
ItemDetails = { Weight = 2.10<kg>; Price = 80<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 5.60<dmg>; Defense = 1.20<def>; Intelligence = None; Speed = 1.00<spd>; Critical = 0.02<ctr>; HitLimit = 20<hl>; Rank = RankE })
}
let ironDagger = {
Name = "Iron dagger"
ItemDetails = { Weight = 2.80<kg>; Price = 200<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 9.80<dmg>; Defense = 2.30<def>; Intelligence = None; Speed = 1.10<spd>; Critical = 0.04<ctr>; HitLimit = 25<hl>; Rank = RankD })
}
let steelDagger = {
Name = "Steel dagger"
ItemDetails = { Weight = 4.25<kg>; Price = 350<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 13.10<dmg>; Defense = 3.00<def>; Intelligence = None; Speed = 1.15<spd>; Critical = 0.05<ctr>; HitLimit = 30<hl>; Rank = RankC })
}
let brokenSword = {
Name = "Broken sword"
ItemDetails = { Weight = 7.20<kg>; Price = 90<usd> }
WeaponDetails = PhysicalWeapon (Sword, { Damage = 5.40<dmg>; Defense = 2.50<def>; Intelligence = None; Speed = 1.20<spd>; Critical = 0.01<ctr>; HitLimit = 10<hl>; Rank = RankE })
}
let rustedSword = {
Name = "Rusted sword"
ItemDetails = { Weight = 8.50<kg>; Price = 120<usd> }
WeaponDetails = PhysicalWeapon (Sword, { Damage = 8.75<dmg>; Defense = 2.90<def>; Intelligence = None; Speed = 1.05<spd>; Critical = 0.03<ctr>; HitLimit = 20<hl>; Rank = RankD })
}
// And so on for iron and steel swords, plus all your axes, spears, staves and long blades.
// They should all be instances, not types. And spellbooks, too:
let rank1SpellbookDetails = { Weight = 0.05<kg>; Price = 150<usd> }
let rank2SpellbookDetails = { Weight = 0.05<kg>; Price = 350<usd> }
let bookOfFireball = {
Name = "Fireball"
ItemDetails = rank1SpellbookDetails
WeaponDetails = MagicalWeapon (Spellbook, { Damage = 8.0<dmg>; AttackRange = 1; Rank = RankE; Uses = 30 ; ManaCost = 12.0<mp> })
}
// Same for Thunder and Frost
let bookOfHellfire = {
Name = "Hellfire"
ItemDetails = rank2SpellbookDetails
WeaponDetails = MagicalWeapon (Spellbook, { Damage = 6.50<dmg>; AttackRange = 2; Rank = RankD; Uses = 25; ManaCost = 20.0<mp> })
}
// And so on for Black Fire and Storm of Blades
let computeCharacterOverallOffensive
// (rank: WeaponRank) // Don't need this parameter now
(weapon: Weaponry)
(cStats: CharacterStats) =
let weaponDamage =
match weapon.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats.Damage
| MagicalWeapon (_, stats) -> stats.Damage
let weaponRank =
match weapon.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats.Rank
| MagicalWeapon (_, stats) -> stats.Rank
// This should really be a method on the Rank type
let rankMultiplier =
match weaponRank with
| RankE -> 1.0100
| RankD -> 1.0375
| RankC -> 1.0925
| RankB -> 1.1250
| RankA -> 1.1785
| RankS -> 1.2105
cStats.Strength * rankMultiplier * weaponDamage
下面的内容有点模糊,但希望它会有一些用处:(1)
equipPurchasedProtection
看起来不像镜头。声明EquipmpurchasedProtection newItem(库存,设备)
建议库存设备对具有“正常”更新功能,而不具有作为透镜特征的目标方面或getter setter组合。(2) 除非我误解了F#(我是一个偶然降落在这里的Haskeller),否则您试图使用设备
记录的字段进行模式匹配,就像它们是一个歧视联盟中的案例一样。如果有人想知道genericequipffunction
是从哪里来的,这是我对@KevinAvignon的回答。阅读这个问题可能会给你一些理解这个问题的上下文。现在我看到了你的全部代码,我对你的模型有另一个评论。在一些情况下,您使用的是类型,而我认为您应该使用对象实例。例如,我不认为RustedDagger
、IronDagger
和SteelDagger
应该是单独的类型。它们应该是相同记录类型的不同实例。我现在没有时间对您的模型进行完整的重新设计,但一旦我完成工作,我将能够在大约7-8小时内完成。“在第一次模式匹配之后,它希望代码具有相同的返回值,而我希望返回的是一般值,这取决于我成功匹配的模式!”--这不是因为newItem
是一个GameItem
,而您的镜头使用的是Hat
s和Gauntlet
s等等吗?如果是这样的话,equipFunction
案例不应该看起来像,例如,|Protection(Helmet(Helmet))->genericEquipFunction HelmetFun(一些头盔)
?我很乐意向您展示如何将代码与镜头集成,但我需要一些时间来理解它。如果你能发布一个精简版(头盔、地球仪等不同类型的物体)但完整的版本(从编译的意义上讲)和一个小的使用代码,这将帮助我们帮助你。如果你抽搐着说“但是两个钢剑的角色会拥有同一把剑!”,那么我会说,“为什么这是一个问题?”请记住,您使用的是不变的数据:每次您更新角色的剑以减少其剩余命中1(假设您使用的是火焰徽章,如武器伤害模型),您将创建一个新记录,并且不会接触其他角色手中仍然崭新的剑的命中。因此,商店反复出售“相同”的剑是完全可以的:它不会引起任何数据问题。是的,我是这样想的!完全拥抱火徽章系统,类似于黑暗灵魂的重量。我想为了不让通用的auto Equipment代码工作,我应该回滚到以前的版本,并用Aether进行更新。我已经更新了我的答案,以显示我推荐的示例。这有点仓促,所以可能会有更多的改进。例如,我刚刚考虑添加一个getArmorStats(项目:CharacterProtection)
函数,该函数将将项目与头盔(帽子h)->h.ArmorStats |胸部保护(盔甲a)->a.ArmorStats |腿部(裤子p)->p.ArmorStats
相匹配。这样,您就不必在需要计算损伤缓解的任何地方重写match
语句。良好的函数式编程能力
type CharacterProtectionStats = {
Defense : float<def>
Resistance : float<res>
Intelligence : float<intel> option
MagicResist : float<mgres>
Speed : float<spd>
EquipmentUsage : int<eu>
}
with
interface IStats with
member x.showStat() =
sprintf "Defense : %O - Resistance : %O - Magic resistance : %O - Speed : %O - Equipment usage : %O" x.Defense x.Resistance x.MagicResist x.Speed x.EquipmentUsage
type CharacterProtectionDetails = {
Name : string
// No Type field here, because that's staying in the DU
ItemDetails : ItemDetails
ArmorStats : CharacterProtectionStats
}
type Hat = Hat of CharacterProtectionDetails
type Armor = Armor of CharacterProtectionDetails
type Pants = Pants of CharacterProtectionDetails
// etc.
type CharacterProtection =
| Shield of Shield
// | Ring of Ring // REMOVED. Rings are different; see below.
| Gloves of Gauntlets
| Legs of Pants
| Armor of Armor
| Helmet of Hat
let sorcererHat = Hat {
Name = "Sorcerer Hat"
ItemDetails = { Weight = 1.0<kg>; Price = 120<usd> }
ArmorStats = { Defense = 1.20<def>; Resistance = 1.30<res>; Intelligence = Some 3.00<intel>; MagicResist = 1.80<mgres>; Speed = 1.00<spd>; EquipmentUsage = 100<eu> }
}
// Other hats...
let steelArmor = Armor.Armor {
Name = "Steel Armor"
ItemDetails = { Weight = 15.0<kg>; Price = 450<usd> }
ArmorStats = { Defense = 17.40<def>; Resistance = 6.10<res>; Intelligence = None; MagicResist = 2.30<mgres>; Speed = 0.945<spd>; EquipmentUsage = 100<eu> }
}
// "Armor.Armor" is kind of ugly, but otherwise it thinks "Armor" is
// CharacterProtection.Armor. If we renamed the CharacterProtection DU
// item to ChestProtection instead, that could help.
type AccessoryStats = {
ExtraStrength : float<str> option
ExtraDamage : float<dmg> option
ExtraHealth : float<hp> option
ExtraMana : float<mp> option
}
with
interface IStats with
member x.showStat() =
sprintf ""
static member Initial =
{ ExtraDamage = None; ExtraStrength = None; ExtraHealth = None; ExtraMana = None }
type Ring = {
Name : string
ItemDetails : ItemDetails
RingStats : AccessoryStats
}
type Amulet = {
Name : string
ItemDetails : ItemDetails
AmuletStats : AccessoryStats
}
type AccessoryItems =
| Ring of Ring
| Amulet of Amulet
// Could add other categories too
let standardRingDetails = { Weight = 0.75<kg>; Price = 275<usd> }
let strengthRing = {
Name = "Extra strength ring"
ItemDetails = standardRingDetails
RingStats = { RingStats.Initial with ExtraStrength = Some 4.50<str> }
}
let damageRing = {
Name = "Extra damage ring"
ItemDetails = standardRingDetails
RingStats = { RingStats.Initial with ExtraDamage = Some 5.00<dmg> }
}