如何从外部功能转移到功能镜头 [英] How to move from external function to functional lens
问题描述
在我开始通过函数式编程获得更好的旅程中,我发现在SO系列的成员的帮助下,
-
https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing
-
http://fluffynukeit.com/how-functional-programming-lenses-work/
-
https://medium.com/@dtipson/functional-lenses-d1aba9e52254#.27yw4gnwk 有了这些知识,我想我可以尝试一下,看看我是否可以理解它们的功能性和他们在FP中有用的原因。我目前的问题是从定义的类型成员转移到我的设备记录中访问和修改字段,这些字段是我为目前正在进行原型设计的游戏定义的。我会把设备记录片断,以前那里的成员以及我正在尝试创建的功能镜头放在一起,但不会起作用。在第一次模式匹配后,它希望代码具有相同的返回值,当我希望它是一个普通的值来返回时,取决于我已成功匹配的模式!
对于代码中的剩余部分,不要忽略代码,并且在您尝试帮助我时不编译代码,我认为最好将重要的代码片段放在这里,并且公开发布相关代码,以便您可以在本地机器上编译它!公共要点可在此处找到。这是很多我的定义,相关的代码是从916行。type设备= {
头盔:帽子选项
护甲:护甲选项
腿部:长裤选项
手套:护手选项
戒指:戒指选项
武器:武器装备选项
盾牌:盾牌选项
Loot:ConsumableItem选项
}
让equipPurchasedProtection newItem(库存,设备)=
匹配newItem和
|头盔 - >
匹配equipment.Helmet与
|无 - >
let newEquipment = {带头盔的设备=一些newItem}
(inventory,newEquipment)
|一些oldHelm
if(playerWantsToAutoEquip newItem)然后
let newEquipment = {带头盔的设备=一些newItem}
let newInventory = inventory |> addToInventory oldHelm
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
|手套 - >
匹配equipment.Hands
|无 - >
let newEquipment = {Equipment with Hands = Some newItem}
(inventory,newEquipment)
|一些oldGloves
if(playerWantsToAutoEquip newItem)然后
let newEquipment = {使用Hands = Some newItem的设备}
let newInventory = inventory |> addToInventory oldGloves
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
|靴子 - >
与
|匹配equipment.Feet无 - >
let newEquipment = {使用Boot = Some newItem的设备}
(inventory,newEquipment)
|一些oldBoots
if(playerWantsToAutoEquip newItem)然后
let newEquipment = {使用Boot = Some newItem的设备}
let newInventory = inventory |> addToInventory oldBoots
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
让equipPurchasedItem newItem(库存,设备)=
let equipFunction =
匹配newItem和
|保护(头盔(_)) - > genericEquipFunction HelmetFun_
|保护(手套(_)) - > genericEquipFunction GlovesFun_
|保护(腿(_)) - > genericEquipFunction LegsFun_
|保护(装甲(_)) - > genericEquipFunction ArmorFun_
|保护(环(_)) - > genericEquipFunction RingFun_
|保护(屏蔽(_)) - > genericEquipFunction ShieldFun_
|武器_ - > genericEquipFunction WeaponFun_
|消费型HealthPotion - > genericEquipFunction LootFun_
|消费品HighHealthPotion - > genericEquipFunction LootFun_
|消费型MegaHealthPotion - > genericEquipFunction LootFun_
|消耗药剂 - > genericEquipFunction LootFun_
|消耗品HighElixir - > genericEquipFunction LootFun_
|消耗品MegaElixir - > genericEquipFunction LootFun_
|消耗品PhoenixFeather - > genericEquipFunction LootFun_
|消耗品MedicinalHerb - > genericEquipFunction LootFun_
let itemForInventory,newEquipment = equipFunction(一些newItem)设备
将itemForInventory与
匹配|无 - > (库存,新设备)
|一些项目 - >
let newInventory = inventory |> addToInventory {Item = item; Count = 1}
(newInventory,newEquipment)
UPDATE 1
下面看看我用来装备购买物品的镜头功能之一。
let getArmorFun e = e.Armor
让equipArmorFun newArmor e = {e with Armor = newArmor}
让ArmorFun_ =(getArmorFun,equipArmorFun)
解决方案在你的模型中,我可以确认我最初的印象:你使用的类型比你应该多的多。许多这些类型应该是实例;在这种情况下,记录实例。当你应该使用类型或实例时,这是一个很好的经验法则。如果两件事是可以互换的,它们应该是两个相同类型的实例。如果它们不可互换,那么(并且只有那时)它们应该是两种不同的类型。这是我的意思的一个例子。以下是一段代码,它占用了整个屏幕:
type Weaponry =
|匕首匕首
|剑之剑
| Ax
|矛Spear
|工作人员
|刀片的LongBlade
| Spellbook Spellbook
with
member x.Name =
match x with
|匕首d - > d.ToString()
|剑s - > s.ToString()
| Ax a - > a.ToString()
| Spear s - > s.ToString()
|员工 - > s.ToString()
| LongBlade lb - > lb.ToString()
| Spellbook sb - > sb.ToString()
成员x.Price =
匹配x与
| Dagger w - > w.Price
|剑w - > w.Price
| Ax w - > w.Price
| Spear w - > w.Price
|员工w - > w.Price
| LongBlade w - > w.Price
|魔法书w - > w.Price
会员x.Weight =
与x
匹配x Dagger w - >重量
|剑w - >重量
| Ax w - >重量
| Spear w - >重量
|员工w - >重量
| LongBlade w - >重量
|魔法书w - > w.Weight
会员x.Stats =
匹配x与
| Dagger w - > w.WeaponStats:> IStats
|剑w - > w.WeaponStats:> IStats
| Ax w - > w.WeaponStats:> IStats
| Spear w - > w.WeaponStats:> IStats
|员工w - > w.WeaponStats:> IStats
| LongBlade w - > w.WeaponStats:> IStats
|魔法书w - > w.SpellStats:> IStats
所有这些项目有什么不同? 最后一行,其中
Spellbook
s有SpellbookStats
而不是WeaponStats
。而已!至于你的其他武器类型 - 匕首,剑,斧,矛等......它们在形状中都是相同的。他们都有武器属性,价格,体重等。
这是对整个武器模型的重新设计:
type ItemDetails = {Weight:float< kg>;价格:int< usd> }
类型PhysicalWeaponType =
|匕首
|剑
| Ax
| Spear
|职员
| LongBlade
类型MagicalWeaponType =
|魔法书
//以后可以添加魔杖,护身符等。
类型WeaponDetails =
| PhysicalWeaponType的物理武器* WeaponStat
| MagicalWeaponType * SpellbookStats
类型Weaponry =
{Name:string
ItemDetails:ItemDetails
WeaponDetails:WeaponDetails}
成员x.Weight = x .ItemDetails.Weight
member x.Price = x.ItemDetails.Price
member x.Stats = match x.WeaponDetails with
| PhysicalWeapon(_,stats) - >统计信息:> IStats
|魔法武器(_,stats) - >统计信息:> IStats
//现在我们来创建一些武器。在真实游戏中,这将从JSON文件或其他东西中读取
//,以便游戏可以通过想要添加自己的自定义武器的最终用户轻松修改
//。
让rustedDagger = {
Name =生锈的匕首
ItemDetails = {Weight = 2.10< kg>价格= 80美元> }
WeaponDetails = PhysicalWeapon(Dagger,{Damage = 5.60< dmg> ;; Defense = 1.20< def> ;; Intelligence = None; Speed = 1.00 spd; Critical = 0.02< ctr> HitLimit = 20Rank = RankE})
}
let ironDagger = {
Name =Iron dagger
ItemDetails = {Weight = 2.80< kg> ;;价格= 200美元}
WeaponDetails = PhysicalWeapon(Dagger,{Damage = 9.80< dmg> ;; Defense = 2.30< def> ;; Intelligence = None; Speed = 1.10 spd; Critical = 0.04 ctr; HitLimit = 25; Rank = RankD})
}
let steelDagger = {
Name =Steel dagger
ItemDetails = {Weight = 4.25< kg> ;;价格= 350美元}
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})
}
让brokenSword = {
Name =Broken sword
ItemDetails = {Weight = 7.20< kg> ;;价格= 90< usd> }
WeaponDetails = PhysicalWeapon(Sword,{Damage = 5.40; Defense = 2.50 ; Intelligence = None; Speed = 1.20 spd; Critical = 0.01 ctr; HitLimit = 10 Rank = RankE})
}
让rustedSword = {
Name =生锈的剑
ItemDetails = {Weight = 8.50< kg> ;;价格= 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})
}
//钢铁剑等等,再加上你所有的斧头,长矛,长柄和长刀片。
//他们应该都是实例,而不是类型。还有法术书:
let rank1SpellbookDetails = {Weight = 0.05< kg> ;;价格= 150美元}
let rank2SpellbookDetails = {Weight = 0.05< kg> ;;价格= 350美元}
让bookOfFireball = {
Name =Fireball
ItemDetails = rank1SpellbookDetails
WeaponDetails = MagicalWeapon(Spellbook,{Damage = 8.0< dmg>; AttackRange = 1; Rank = RankE;用途= 30; ManaCost = 12.0mpmp)}
}
//同雷霆和冰霜
let bookOfHellfire = {
Name =Hellfire
ItemDetails = rank2SpellbookDetails
WeaponDetails = MagicalWeapon(Spellbook,{Damage = 6.50< dmg> ;; AttackRange = 2; Rank = RankD;使用= 25; ManaCost = 20.0< mp>})
}
//对于黑色火焰和刀锋风暴等等
让computeCharacterOverallOffensive
//(等级:WeaponRank)//现在不需要这个参数
(武器:武器装备)
(cStats:CharacterStats)=
let weaponDamage =
match weapon.WeaponDetails与
| PhysicalWeapon(_,stats) - > stats.Damage
|魔法武器(_,stats) - > stats.Damage
让weaponRank =
匹配weapon.WeaponDetails with
| PhysicalWeapon(_,stats) - > stats.Rank
|魔法武器(_,stats) - > stats.Rank
//这应该是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
注意所有细节武器装备类型现在适合一个屏幕?而且有更少的重复。我保留了不同类型的物理武器(匕首,剑等)之间的区别,因为你很可能会拥有一种或两种类型的角色:剑专家不能使用斧子,或者他需要50%当他使用斧头时的强度惩罚,等等。但我怀疑你会有一个只能使用铁匕首但不能使用钢匕首的角色。不同类型的匕首在这种游戏中可以完全互换 - 如果玩家不是,玩家会非常惊讶。所以他们不应该是不同的类型。而且各种类型的物理武器几乎可以互换,所以它们的模型也应该尽可能相似。将统计数据放在没有差异的部分,并保留类型(
Dagger
,Sword
,<$ c
这是一个很长的回答,我还没有得到进入你关于镜头的实际问题!但是,自从我看到代码并思考起来后,我就开始思考:他为自己做了太多的工作,我必须先解决这个问题。
我想你通过将您的代码转移到 https://codereview.stackexchange.com/ 并让其中的人员查看它并提出了加强模型的方法。一旦你的模型得到改进,我认为你会发现镜头代码也更容易编写。正如我之前所说,不要尝试自己写镜头代码!使用像 Aether 或 F#+ 来帮助你。在你的鞋子里,我可能会和Aether一起走,因为它有比F#+更多的文档; F#+似乎(AFAICT)更加针对那些已经使用过Haskell镜头的人,并且不需要任何关于如何使用它们的提示。
https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing
http://fluffynukeit.com/how-functional-programming-lenses-work/
https://medium.com/@dtipson/functional-lenses-d1aba9e52254#.27yw4gnwk
更新1:有另一个片段,我建议你做装甲:
type CharacterProtectionStats = {
防御:浮动< def>
阻力:浮动< res>
智能:浮动< intel>选项
MagicResist:float< mgres>
速度:浮动< spd>
EquipmentUsage:int< eu>
with
接口IStats with
成员x.showStat()=
sprintfDefense:%O - Resistance:%O - 魔法抵抗:%O - 速度:%O - 设备使用情况:%Ox.Defense x.Resistance x.MagicResist x.Speed x.EquipmentUsage
type CharacterProtectionDetails = {
名称:string
/ / No类型字段,因为它停留在DU $ b $ ItemDetails:ItemDetails
ArmorStats:CharacterProtectionStats
}
类型Hat = CharacterProtectionDetails的帽子
类型护甲= CharacterProtectionDetails的护甲
类型裤子= CharacterProtectionDetails的裤子
//等等
类型CharacterProtection =
|盾牌之盾
// |戒指戒指/ /删除。戒指是不同的;见下文。
|手套手套
|裤子的腿
|装甲护甲
|帽子头盔
let sorcererHat =帽子{
名称=巫师帽子
ItemDetails = {Weight = 1.0< kg>价格= 120< usd> }
ArmorStats = {Defense = 1.20< def> ;;电阻=1.30Ω;智能=大约3.00< intel>; MagicResist = 1.80< mgres>;速度= 1.00< spd>;设备使用量= 100 eu> }
}
//其他帽子...
让钢铁甲= Armor.Armor {
Name =Steel Armor
ItemDetails = {Weight = 15.0< kg>;价格= 450美元}
ArmorStats = {Defense = 17.40< def> ;;电阻= 6.10 ;情报=无; MagicResist = 2.30< mgres>;速度= 0.945< spd>;设备使用量= 100 eu> }
}
//Armor.Armor有点难看,但否则认为Armor是
// CharacterProtection.Armor。如果我们将CharacterProtection DU
//项目重命名为ChestProtection,那可能会有所帮助。
类型AccessoryStats = {
ExtraStrength:float< str>选项
ExtraDamage:float< dmg>选项
ExtraHealth:float< hp>选项
ExtraMana:float< mp>选项
}
with
接口IStats with
成员x.showStat()=
sprintf
静态成员初始=
{ExtraDamage =无; ExtraStrength =无; ExtraHealth =无; ExtraMana = None}
类型Ring = {
名称:string
ItemDetails:ItemDetails
RingStats:AccessoryStats
}
类型Amulet = {
Name:string
ItemDetails:ItemDetails
AmuletStats:AccessoryStats
}
类型AccessoryItems =
|戒指
|护身符护身符
//可以添加其他类别
让standardRingDetails = {Weight = 0.75< kg> ;;价格= 275美元> }
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> }
}
On my journey to start getting better with functional programming, I discovered, with the help of a member of the SO family, what lens. I even made some research on it with the links down below to understand more about them.
With all that knowledge, I thought I could give them a try and see whether or not I could understand their functionnality and the reasons why they're useful in FP. My problem at the moment is moving from the type members that were define to access and modify fields in my equipment record that I've define for a game that I'm prototyping at the moment. I will put snippets of the Equipment records, the members that were there before and the functional lens I'm trying to create but just won't work. After the first pattern matching, it expects the code to have the same return value, when I'd like it to be a general value to be returned, depending on a pattern that I had successfully matched! For the the remain of the code, instead of omitting the code and making it not compile while you're trying to give me a hand, I've thought it be best to put the important snippets here and a public to the relevant code so you can compile it on your local machine ! The public gist can be found here. It's a lot of for my definitions, the relevant code is from line 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)
UPDATE 1 Here's a look at one of the lens function that I'm using to equip purchased items.
let getArmorFun e = e.Armor
let equipArmorFun newArmor e = { e with Armor = newArmor }
let ArmorFun_ = (getArmorFun, equipArmorFun)
Having looked at your model more closely, I can confirm my initial impression: you're using a lot more types than you should. Many of those types should be instances; in this case, record instances. Here's a good rule-of-thumb for when you should use a type or an instance. If the two things are interchangeable, they should be two instances of the same type. If they're NOT interchangeable, then (and only then) they should be two different types. Here's an example of what I mean. Here's a section of your code that takes up an entire screen:
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
What's different between all these items? The last line, where Spellbook
s have SpellbookStats
instead of WeaponStats
. That's it! As for your other weapon types -- dagger, sword, axe, spear, etc... they're ALL identical in "shape". They all have weapon stats, price, weight, etc.
Here's a redesign of that entire weapon model:
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
Notice how all the details of the Weaponry type fit on one screen now? And there's WAY less duplication. I kept the distinction between different types of physical weapons (daggers, swords, etc) since it's likely that you'll have characters that specialize in one or two types: a sword specialist can't use an axe, or he takes a 50% strength penalty when he uses an axe, and so on. But I doubt that you're ever going to have a character who can only use iron daggers but can't use steel daggers. Different types of daggers are completely interchangeable in this kind of game -- the player would be VERY surprised if they weren't. So they shouldn't be different types. And the various types of physical weapons are almost interchangeable, so their models should be as similar as possible, too. Put the stats in the part that doesn't differ, and leave the type (Dagger
, Sword
, Axe
) as the only difference between the physical weapons.
This has been a really long answer and I still haven't gotten into your actual question about lenses! But since I winced on looking at the code and thought, "He is making WAY too much work for himself", I had to address this part first.
I think you'd benefit from taking your code over to https://codereview.stackexchange.com/ and asking people there to take a look at it and suggest ways to tighten up your model. Once your model is improved, I think you'll find the lens code to be a lot easier to write as well. And as I said before, DON'T try to write the lens code on your own! Use a library like Aether or F#+ to help you. In your shoes, I'd probably go with Aether simply because it has more documentation than F#+ seems to have; F#+ seems (AFAICT) to be more aimed at people who have already used Haskell lenses and don't need any reminders about how to use them.
UPDATE 1: Have another snippet for how I'd suggest you do armor:
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> }
}
这篇关于如何从外部功能转移到功能镜头的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!