F# 如何从外部功能转变为功能性镜片

F# 如何从外部功能转变为功能性镜片,f#,functional-programming,lenses,F#,Functional Programming,Lenses,在我开始更好地使用函数式编程的过程中,在SO家族的一位成员的帮助下,我发现了什么。我甚至通过下面的链接对其进行了一些研究,以了解更多关于它们的信息 有了这些知识,我想我可以尝试一下,看看我是否能理解它们的功能以及它们在FP中有用的原因。目前我的问题是从定义的类型成员转移到访问和修改我的设备记录中的字段,我已经为我目前正在制作原型的游戏定义了这些字段。我会把设备记录的片段,以前在那里的成员和我试图创建的功能镜头,但就是不起作用。在第一次模式匹配之后,它希望代码具有相同的返回值,而我希望它是一

在我开始更好地使用函数式编程的过程中,在SO家族的一位成员的帮助下,我发现了什么。我甚至通过下面的链接对其进行了一些研究,以了解更多关于它们的信息


  • 有了这些知识,我想我可以尝试一下,看看我是否能理解它们的功能以及它们在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> }
    }