如何从外部功能镜头转向功能镜头

2023-12-25

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

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

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

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

有了这些知识,我想我可以尝试一下,看看我是否能理解它们的功能以及它们在 FP 中有用的原因。我目前的问题是从定义的类型成员转移到访问和修改我为目前正在制作原型的游戏定义的设备记录中的字段。我将放置设备记录的片段、之前存在的成员以及我试图创建但无法工作的功能镜头。在第一次模式匹配之后,它期望代码具有相同的返回值,而我希望它是要返回的一般值,具体取决于我成功匹配的模式! 对于代码的其余部分,当您试图帮助我时,不要省略代码并使其无法编译,我认为最好将重要的片段放在这里并公开相关代码,以便你可以在本地机器上编译它!公共要点可以找到here https://gist.github.com/Kavignon/e1c8805c798538ff3555e4fadc5e95a3。我的定义很多,相关代码来自第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这是我用来装备购买的物品的镜头功能之一。

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

所有这些项目之间有什么不同?这最后一行, where Spellbooks have SpellbookStats代替WeaponStats。就是这样!至于你的其他武器类型——匕首、剑、斧头、矛等……它们的“形状”都是相同的。它们都有武器统计数据、价格、重量等。

这是整个武器模型的重新设计:

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

请注意武器类型的所有详细信息现在如何显示在一个屏幕上?而且重复的情况要少得多。我保留了不同类型的物理武器(匕首、剑等)之间的区别,因为你可能会拥有专门从事一种或两种类型的角色:剑专家不能使用斧头,或者他需要 50%使用斧头时的力量惩罚,等等。但我怀疑你是否会拥有一个只能使用铁匕首而不能使用钢匕首的角色。不同类型的匕首有完全可互换在这种游戏中——如果他们不这样做,玩家会感到非常惊讶。所以它们不应该是不同的类型。以及各种类型的物理武器almost可互换,因此它们的模型也应该尽可能相似。将统计数据放在不不同的部分,并保留类型 (Dagger, Sword, Axe)作为物理武器之间的唯一区别。

这是一个非常长的答案,我仍然没有回答你关于镜头的实际问题!但由于我在查看代码时畏缩并想,“他为自己做了太多的工作”,所以我必须首先解决这部分问题。

我认为您可以将代码移交给https://codereview.stackexchange.com/ https://codereview.stackexchange.com/并要求那里的人看一下并提出加强模型的方法。一旦你的模型得到改进,我想你会发现镜头代码也更容易编写。正如我之前所说,不要尝试自己编写镜头代码!使用类似的库Aether https://xyncro.tech/aether or F#+ https://github.com/gmpl/FSharpPlus来帮你。站在你的立场上,我可能会选择 Aether,因为它比 F#+ 拥有更多的文档; F#+ 似乎(AFAICT)更针对那些已经使用过 Haskell 镜头并且不需要任何关于如何使用它们的提醒的人。

更新1:我建议你如何制作盔甲,还有另一个片段:

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> }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何从外部功能镜头转向功能镜头 的相关文章

随机推荐

  • /bin/sh 的 .bashrc 是否有替代方案?

    我需要一个在启动时运行的脚本 bin sh 如同 bashrc for bin bash 有什么办法可以做到这一点吗 EDIT 我都尝试过 etc profile and profile 我写echo hello world 到两个文件 这
  • 重新创建 Win32 标头是个好主意吗?

    我发现自己最近针对 Win32 编写了更多的 C C 代码 并且来自 C 背景的我对完全一致的 干净代码 产生了痴迷 因此从漂亮的 System 命名空间回到了构成 Win32 API 头文件的 defines 的混杂有点文化冲击 在阅读了
  • std::mutex 锁在覆盖 new 运算符时挂起

    我们有一个与我们的产品之一一起使用的内部内存管理器 内存管理器覆盖new and delete运算符 并且在单线程应用程序中运行良好 然而 我现在的任务是让它也适用于多线程应用程序 据我了解 以下伪代码应该可以工作 但即使使用try loc
  • 在 MYSQL / PHP 中设置最大执行时间

    我有一个 XML 文档 其中包含大约 48 000 个子文档 50MB 我运行一个 INSERT MYSQL 查询 为每个子项创建新条目 问题是由于它的大小 需要花费很多时间 执行后我收到这个 Fatal error Maximum exe
  • 如何将十六进制字符串转换为双精度型?

    我从 BLE 获取 0x0000 到 0x01c2 范围内的十六进制值到我的手机 aString 为了将其绘制在图表中 我必须将其转换为double 我已经尝试过这个方法 https stackoverflow com a 10708508
  • 在 bash 中查找并删除 .txt 文件 [重复]

    这个问题在这里已经有答案了 最近 我的外部硬盘驱动器上存有我的照片 大多数都在 DVD 上 但是 因为某些分区被破坏了 幸运的是 我能够使用 PhotoRec 另一个 Unix 分区实用程序 和 PDisk 将所有内容重新组合在一起 Pho
  • 在 Dart 中监听 JS CustomEvent

    我知道我的问题并不新鲜 但我在这里和互联网上找到的所有解决方案都不起作用 或者 我正在做一些完全错误的事情 我需要在 Dart 和 JS 之间创建通信 并且我很想使用事件 因为这个想法看起来很简洁 所以 我尝试了这个教程 https dar
  • SQL Server 2008 CTE 递归

    我正在尝试使用 SQL Server 2008 的 CTE 来执行我认为是困难的递归 我似乎无法理解这个问题 在下面的示例中 您可以假设固定深度为 3 不会低于该深度 在现实生活中 深度 更深 但仍然是固定的 在这个例子中我试图简化它一些
  • Scala 集合转发器和代理的用例

    Scala 的集合库包含转发器IterableForwarder http www scala lang org api current scala collection generic IterableForwarder html Tra
  • 从python查找Windows上程序的安装目录

    python 程序需要找到 openoffice org 的安装位置 该软件安装在 Windows XP 计算机上 做这个的最好方式是什么 您可以使用 winregWindows 上的模块 首先找到注册表中的路径 例如启动regedit e
  • Docker Compose 主机名命令不起作用

    我无法获取 Docker Composehostname https docs docker com compose compose file domainname hostname ipc mac address privileged r
  • Android 中的错误 -- 无法找到以下项的检测信息:ComponentInfo

    例如 我有一个应用程序将调用联系人并且必须选择其中一个联系人 但它并没有完全按照我想要的方式做 它向我显示错误Unable to find instrumentation info for ComponentInfo com sample
  • QSplitter 在 QWidget 和 QTabWidget 之间变得无法区分

    我将 QWidget 和 QTabWidget 放在一个水平拆分器中并排放置 并且分离器失去了形状 只有将鼠标悬停在分离器上才能知道有分离器 如何让它可见 Thanks 由于 QSplitterHandle 大多数人认为是 分割器 是从 Q
  • VS 2010 Tools 的 gacutil.exe 在哪里安装程序集?

    我使用 Visual Studio 2010 命令提示符的 gacutil exe 来安装程序集 我希望将程序集添加到C WINDOWS assemblies 但它被添加到C WINDOWS Microsoft NET assembly G
  • 如何在 IntelliJ 中运行同一个应用程序两次?

    我正在使用 IntelliJ 开发我的客户端 服务器应用程序 并且刚刚发现了Compounds 基本上我可以同时运行我的客户端和服务器 并且每次我想测试时它都可以节省我无用的操作 但是 我想用 2 个客户端和 1 个服务器来测试我的应用程序
  • Java 2D:将点 P 移动到靠近另一个点一定距离?

    将 Point2D Double x 距离移近另一个 Point2D Double 的最佳方法是什么 编辑 尝试编辑 但因维护而停机 不 这不是作业 我需要将飞机 A 移向跑道末端 C 并将其指向正确的方向 角度 a 替代文本http im
  • 兑换 + 点击一次 = :-(

    我有一个普通的 Windows 窗体程序 不是 VSTO 它使用单击一次进行部署 问题是 很多用户都遇到随机错误的问题 通常会指出 由于以下错误 IClassFactory 失败 80004005 我通过将模式更改为 隔离 来部署救赎 这似
  • 以百分比形式给出省略号

    我正在尝试省略span里面的元素td元素 问题是省略号有效当且仅当我给出span元素具有固定宽度 即宽度 以像素为单位 但在我的项目中 我不能使用固定宽度span元素 这span元素必须完全拉伸到各自的内部td可以通过使用的元素width
  • Unix - 在 shell 脚本中排序

    如何根据字段位置对文件进行排序 例如 我需要对下面给定的文件进行排序 基于第 4 5 和 8 名位置 请帮忙 我尝试了以下命令 它不起作用 sort d k 3 42 44 k 4 47 57 k 5 59 70 k 8 73 82 010
  • 如何从外部功能镜头转向功能镜头

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