Types (工会)

Types (工会),types,f#,functional-programming,idioms,Types,F#,Functional Programming,Idioms,换句话说,OO使得在许多形状的数据上实现相同的操作变得很容易,但添加新的操作却很困难;FP使对数据执行许多不同的操作变得容易,但修改数据却很困难。这些方法是互补的。选择对你的问题最有意义的一个。F#的好处在于它对两者都有很大的支持;实际上,您需要在F#中默认为FP,因为语法更轻巧、更具表现力 2) 在C#中,打开类型被认为是一种不好的做法。这是最重要的吗 F#也一样?如果是,我应该写什么来代替 其他有相同位置的演员?使用SamePosition为实现OtherXs 从actor派生的每个类X看起

换句话说,OO使得在许多形状的数据上实现相同的操作变得很容易,但添加新的操作却很困难;FP使对数据执行许多不同的操作变得容易,但修改数据却很困难。这些方法是互补的。选择对你的问题最有意义的一个。F#的好处在于它对两者都有很大的支持;实际上,您需要在F#中默认为FP,因为语法更轻巧、更具表现力

2) 在C#中,打开类型被认为是一种不好的做法。这是最重要的吗 F#也一样?如果是,我应该写什么来代替 其他有相同位置的演员?使用SamePosition为实现OtherXs 从actor派生的每个类X看起来都不像一个可伸缩的解决方案

在OO中打开类型会导致代码脆弱,而在F#中则不会改变。你仍然会遇到同样的问题。 然而,在FP中打开数据类型是惯用的。如果使用DU而不是类层次结构,那么您将别无选择,只能在数据类型上切换(模式匹配)。这很好,因为编译器可以帮助您实现这一点,这与OO不同

一个玩家在地图上

  • 如果他与箭处于同一位置,他会受到1点伤害
  • 如果他和一个生物处于同一位置,他会受到相当于该生物生命的伤害
  • 如果他与硬币处于相同的位置,他得到1$
  • 如果他在服用药物后处于相同的姿势,他会在1点前痊愈
首先,让我们定义域模型。我发现您的设计有一个问题,那就是您将对象位置存储在actors中,而没有实际的map对象。我发现一个好的设计原则是让对象只存储其内在属性,并将外在属性移动到更有意义的地方,使域模型尽可能小。参与者的位置不是固有属性

因此,使用惯用的F#类型:

这漏掉的一个信息是符号,但这实际上只是渲染的一个问题,因此最好在助手函数中将其移到一边:

let symbol = function
| Arrow -> '/'
| Medication -> '♥'
| Creature c -> 
    match c with
    | Zombie _ -> 'X'
| Coin -> '$'
| Player _ -> '@'
现在,根据您的描述,一个磁贴上可以有多个参与者,因此我们将把我们的地图表示为
参与者列表[][]
,即参与者列表的二维地图

let width, height = 10, 10
let map = Array.init height (fun y -> Array.init width (fun x -> List.empty<Actor>))

// Let's put some things in the world
map.[0].[1] <- [Arrow]
map.[2].[2] <- [Creature(Zombie { Hp = 10; TargetLocation = None })]
map.[0].[0] <- [Player { Hp = 20; Score = 0}]
这遗漏了一个问题:我如何获得球员的位置?你可以缓存它,或者每次都动态计算。如果地图很小,这并不慢。下面是一个示例函数(未优化,如果广泛使用2D贴图,则希望实现不分配资源的快速泛型迭代器):


这是一个更适合codereview的问题。seI不认为这两种方法都是不好的(但请注意,当您可以依靠编译器告诉您您错过了一个可能的选项时,模式匹配是最有效的,例如,联合可能是比类更好的选择)。但将两者混合使用是可疑的——如果您使用的是类,为什么不使用虚拟方法呢?如果您使用的是函数+记录,为什么要使用类?您可以在联合中使用记录,这样您就可以像往常一样使用命名参数(必要时包括函数)。但是你确实错过了更重要的一点——你仍然在试图建立一个承载行为的继承层次结构,而你到底如何做到这一点并不重要。取而代之的是,现在常用的方法是使用组合,并用构建块(行为等)而不是通过继承来构建参与者。一旦处理了行为,交互就很简单了——当Damageable与Demaginal交互时,你不在乎谁是玩家,谁是箭头。
类和继承是为了在F#中使用吗?或者它们只是为了与.Net兼容答案非常主观。我知道有人会非常赞同这一点,而我则持相反的观点,尽可能多地采用构图。当需要兼容性或进行第一次翻译时,我将使用类和继承,但是一旦我有了代码,我就开始考虑通用性,并开始承担功能编程而不是OO设计的力量。@user2136963你可以使用smth,比如:
type-Actor=| x:int*y:int*symbol:char
很多!我非常感谢您所做的工作,但是
交互
的实现看起来非常神秘。我无法想象有人在享受
((((x,y),符号),生命值),健康,金钱:玩家)
而不是
(玩家:玩家)
。如果A | B | C实现了A、B和C属性的交集,这将更加优雅。我知道代码并不优雅,甚至不是真正惯用的F |。我试着把它放低一点,这样你就可以很容易地把它分解成你需要的东西,而不必去分解,然后重构以得到你需要的东西。还有
(((x,y),符号),生命值),health,money
只是解构了玩家,我本可以将名字缩短为一两个字符,并将不必要的属性改为
\u
,但保留了它们,这样代码对初学者来说更容易阅读。实际上,我会使用Asik答案中的一些要点,并进行更多重构,但之后我对于初学者来说,t可能不容易理解。此外,我可以将所有播放器构造函数移动到
interact
的末尾,将播放器和其他实体的位置移动到一个单独的结构中,这样interact就不必做所有的检查,将构造函数和析构函数中的所有元组转换为curried参数,删除所有类型,仅在DU和播放器定义中使用
int
char
,等等。还要注意,此代码中没有可变值,它都是不可变的,因此需要
交互
返回
播放器,而不是
()
。我不知道为什么这个示例会导致堆栈溢出,但可能在本周晚些时候,当我有更多时间时,我会这样做
Actor =
    | Medication {x:int;y:int;symbol:char} 
type Medication = {x:int;y:int;symbol:char}
Actor =
        | Medication
type Medication = {x:int;y:int;symbol:char}
Actor =
    | Medication of Medication
namespace Game

type Location = int * int
type TargetLocation = Location
type CurrentLocation = Location
type Symbol = char
type ActorType = CurrentLocation * Symbol
type HitPoints = int
type Health = int
type Money = int
type Creature = ActorType * HitPoints

// Player = Creature * Health * Money
//        = (ActorType * HitPoints) * Health * Money
//        = ((CurrentLocation * Symbol) * HitPoints) * Health * Money
//        = ((Location * Symbol) * HitPoints) * Health * Money
//        = (((int * int) * char) * int) * int * int
type Player = Creature * Health * Money 

type Actor =
    | Medication of ActorType
    | Coin of ActorType
    | Arrow of Creature * TargetLocation    // Had to give arrow hit point damage
    | Zombie of Creature * TargetLocation

module main =

    [<EntryPoint>]
    let main argv = 

        let player = ((((0,0),'p'),15),0,0)  

        let actors : Actor List = 
            [
                 Medication((0,0),'♥'); 
                 Zombie((((3,2),'Z'),3),(0,0)); 
                 Zombie((((5,1),'Z'),3),(0,0)); 
                 Arrow((((4,3),'/'),3),(2,1));
                 Coin((4,2),'$'); 
            ]

        let updatePlayer player (actors : Actor list) : Player =
            let interact (((((x,y),symbol),hitPoints),health,money) : Player) otherActor = 
                match (x,y),otherActor with
                | (playerX,playerY),Zombie((((opponentX,opponentY),symbol),zombieHitPoints),targetLocation) when playerX = opponentX && playerY = opponentY -> 
                    printfn "Player is hit by creature for %i hit points." zombieHitPoints
                    ((((x,y),symbol),hitPoints - zombieHitPoints),health,money)
                | (playerX,playerY),Arrow((((opponentX,opponentY),symbol),arrowHitPoints),targetLocation)  when playerX = opponentX && playerY = opponentY ->  
                    printfn "Player is hit by arrow for %i hit points." arrowHitPoints
                    ((((x,y),symbol),hitPoints - arrowHitPoints),health,money)
                | (playerX,playerY),Coin((opponentX,opponentY),symbol)  when playerX = opponentX && playerY = opponentY ->  
                    printfn "Player got 1$." 
                    ((((x,y),symbol),hitPoints),health,money + 1)
                | (playerX,playerY),Medication((opponentX,opponentY),symbol)  when playerX = opponentX && playerY = opponentY ->  
                    printfn "Player is healed by 1."
                    ((((x,y),symbol),hitPoints),health+1,money)
                | _ ->  
                    // When we use guards in matching, i.e. when clause, F# requires a _ match 
                    ((((x,y),symbol),hitPoints),health,money) 
            let rec updatePlayerInner player actors =
                match actors with
                | actor::t ->
                    let player = interact player actor
                    updatePlayerInner player t
                | [] -> player
            updatePlayerInner player actors

        let rec play player actors =
            let player = updatePlayer player actors
            play player actors

        // Since this is example code the following line will cause a stack overflow.
        // I put it in as an example function to demonstrate how the code can be used.
        // play player actors

        // Test

        let testActors : Actor List = 
            [
                Zombie((((0,0),'Z'),3),(0,0))
                Arrow((((0,0),'/'),3),(2,1))
                Coin((0,0),'$')
                Medication((0,0),'♥')
            ]

        let updatedPlayer = updatePlayer player testActors

        printf "Press any key to exit: "
        System.Console.ReadKey() |> ignore
        printfn ""

        0 // return an integer exit code
Player is hit by creature for 3 hit points.
Player is hit by arrow for 3 hit points.
Player got 1$.
Player is healed by 1.
type Player = 
    { Hp: int
      Score: int }
type Zombie =
    { Hp: int
      TargetLocation: (int*int) option }

type Creature =
| Zombie of Zombie

type Actor =
| Arrow
| Medication
| Creature of Creature
| Coin
| Player of Player
let symbol = function
| Arrow -> '/'
| Medication -> '♥'
| Creature c -> 
    match c with
    | Zombie _ -> 'X'
| Coin -> '$'
| Player _ -> '@'
let width, height = 10, 10
let map = Array.init height (fun y -> Array.init width (fun x -> List.empty<Actor>))

// Let's put some things in the world
map.[0].[1] <- [Arrow]
map.[2].[2] <- [Creature(Zombie { Hp = 10; TargetLocation = None })]
map.[0].[0] <- [Player { Hp = 20; Score = 0}]
let applyEffects { Hp = hp; Score = score } actor =
    let originalPlayer = { Hp = hp; Score = score }
    match actor with
    | Arrow -> { originalPlayer with Hp = hp - 1 }
    | Coin -> { originalPlayer with Score = score + 1 }
    | Medication -> { originalPlayer with Hp = hp + 1 }
    | Creature(Zombie z) -> { originalPlayer with Hp = hp - z.Hp }
    | _ -> originalPlayer
let getPlayer: Player * (int * int) =
    let mapIterator = 
        map 
        |> Seq.mapi(fun y row -> 
            row |> Seq.mapi(fun x actors -> actors, (x, y))) 
        |> Seq.collect id
    mapIterator 
    |> Seq.pick(fun (actors, (x, y)) -> 
        actors |> Seq.tryPick(function 
                              | Player p -> Some (p, (x, y)) 
                              | _ -> None))