如何从外部功能转移到功能镜头 [英] How to move from external function to functional lens

查看:91
本文介绍了如何从外部功能转移到功能镜头的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我开始通过函数式编程获得更好的旅程中,我发现在SO系列的成员的帮助下, 镜头。我甚至用下面的链接对它进行了一些研究,以更多地了解它们。


  1. https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing


  2. http://fluffynukeit.com/how-functional-programming-lenses-work/

  3. https://medium.com/@dtipson/functional-lenses-d1aba9e52254#.27yw4gnwk 对于代码中的剩余部分,不要忽略代码,并且在您尝试帮助我时不编译代码,我认为最好将重要的代码片段放在这里,并且公开发布相关代码,以便您可以在本地机器上编译它!公共要点可在此处找到。这是很多我的定义,相关的代码是从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 = 20 Rank = 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镜头的人,并且不需要任何关于如何使用它们的提示。



    更新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.

    1. https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing

    2. http://fluffynukeit.com/how-functional-programming-lenses-work/

    3. https://medium.com/@dtipson/functional-lenses-d1aba9e52254#.27yw4gnwk

    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 Spellbooks 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆