派生类型的模式匹配是否为F#惯用?

use*_*963 4 f# types functional-programming idiomatic

我想以最惯用的方式实现以下内容

玩家在地图上.

  1. 如果他用箭头处于同一位置,他将获得1点伤害

  2. 如果他与一个生物处于相同的位置,他的伤害等于生物的hp

  3. 如果他用硬币处于同一位置,他就得到1美元

  4. 如果他与药物处于相同的位置,他会治愈1

这是为交互编写的存根:

open System
[<AbstractClass>]
type ActorBase(x,y,symbol)=
    member this.X:int=x
    member this.Y:int=y
    member this.Symbol:char=symbol

type Medication(x,y)=
    inherit ActorBase(x,y,'?')
type Coin(x,y)=
    inherit ActorBase(x,y,'$') 

type Arrow(x,y,symbol,targetX,targetY) =
    inherit ActorBase(x,y,symbol)
    member this.TargetX=targetX
    member this.TargetY=targetY

[<AbstractClass>]
type CreatureBase(x,y,symbol,hp) =
    inherit ActorBase(x,y,symbol)
    member this.HP:int=hp

type Player(x,y,hp, score) =
    inherit CreatureBase(x,y,'@',hp)
    member this.Score = score
type Zombie(x,y,hp,targetX,targetY) =
    inherit CreatureBase(x,y,'z',hp)
    member this.TargetX=targetX
    member this.TargetY=targetY

let playerInteraction (player:Player) (otherActor:#ActorBase):unit =
    printfn "Interacting with %c" otherActor.Symbol
    match (otherActor :> ActorBase) with
            | :? CreatureBase as creature -> printfn "Player is hit by %d by creature %A" (creature.HP) creature
            | :? Arrow -> printfn "Player is hit by 1 by arrow" 
            | :? Coin -> printfn "Player got 1$" 
            | :? Medication -> printfn "Player is healed by 1"
            | _ -> printfn "Interaction is not recognized" 

let otherActorsWithSamePosition (actor:#ActorBase) =
    seq{
        yield new Zombie(0,0,3,1,1) :> ActorBase
        yield new Zombie(0,1,3,1,1) :> ActorBase
        yield new Arrow(0,0,'/',1,1) :> ActorBase
        yield new Coin(0,0) :> ActorBase
        yield new Medication(0,0) :> ActorBase
    } 
        |> Seq.where(fun a -> a.X=actor.X && a.Y=actor.Y)
[<EntryPoint>]
let main argv = 
    let player = new Player(0,0,15,0)
    for actor in (otherActorsWithSamePosition player) do
        playerInteraction player actor
    Console.ReadLine() |> ignore
    0
Run Code Online (Sandbox Code Playgroud)

1)类和继承是否意味着在F#中使用?或者他们只是为了兼容.Net?我应该使用记录,如果是的话,怎么样?

2)在C#中,切换类型被认为是不好的做法.对F#来说是一样的吗?如果是,我应该写什么而不是otherActorsWithSamePosition?从actor派生的otherXsWithSamePosition每个类的实现X看起来不像是可伸缩的解决方案

更新:

我试图用一个有区别的联合实现它,但没有设法编译:

type IActor =
    abstract member X:int
    abstract member Y:int
    abstract member Symbol:char
type IDamagable =
    abstract member Damaged:int->unit
type IDamaging =
    abstract member Damage:int
type Player =
    {
        X:int
        Y:int
        HP:int
        Score:int
    }
    interface IActor with
        member this.X=this.X
        member this.Y=this.Y
        member this.Symbol='@'
    interface IDamagable with
        member this.Damaged damage = printfn "The player is damaged by %d" damage
    interface IDamaging with
        member this.Damage = this.HP
type Coin =
    {
        X:int
        Y:int
    }
    interface IActor with
        member this.X=this.X
        member this.Y=this.Y
        member this.Symbol='$'
type Medication =
    {
        X:int
        Y:int
    }
    interface IActor with
        member this.X=this.X
        member this.Y=this.Y
        member this.Symbol='?'
type Arrow =
    {
        X:int
        Y:int
        DestinationX:int
        DestinationY:int
        Symbol:char
    }
    interface IActor with
        member this.X=this.X
        member this.Y=this.Y
        member this.Symbol=this.Symbol
    interface IDamaging with
        member this.Damage = 1
type Zombie =
    {
        X:int
        Y:int
        DestinationX:int
        DestinationY:int
        HP:int
    }
    interface IActor with
        member this.X=this.X
        member this.Y=this.Y
        member this.Symbol='z'
    interface IDamaging with
        member this.Damage = this.HP
type Actor =
    |Player of Player
    |Coin of Coin
    |Zombie of Zombie
    |Medication of Medication
    |Arrow of Arrow
let otherActorsWithSamePosition (actor:Actor) =
    seq{
        yield Zombie {X=0;Y=0; HP=3;DestinationX=1;DestinationY=1}
        yield Zombie {X=0;Y=1; HP=3;DestinationX=1;DestinationY=1}
        yield Arrow {X=0;Y=0; Symbol='/';DestinationX=1;DestinationY=1}
        yield Coin {X=0;Y=0}
        yield Medication {X=0;Y=0}
    } 
        //Cannot cast to interface
        |> Seq.where(fun a -> (a:>IActor).X=actor.X && (a:>IActor).Y=actor.Y)
let playerInteraction player (otherActor:Actor) =
    match otherActor with
            | Coin coin -> printfn "Player got 1$" 
            | Medication medication -> printfn "Player is healed by 1"
            //Cannot check this
            | :?IDamaging as damaging -> (player:>IDamagable).Damaged(damaging.Damage)

[<EntryPoint>]
let main argv = 
    let player = Player {X=0;Y=0;HP=15;Score=0}
    for actor in (otherActorsWithSamePosition player) do
        playerInteraction player actor
    Console.ReadLine() |> ignore
    0
Run Code Online (Sandbox Code Playgroud)

问题:

1)更重要的是:

我无法对现有记录进行有区别的联合

Actor =
    | Medication {x:int;y:int;symbol:char} 
Run Code Online (Sandbox Code Playgroud)

引发弃用构造的错误

type Medication = {x:int;y:int;symbol:char}
Actor =
        | Medication
Run Code Online (Sandbox Code Playgroud)

考虑MedicationActor.Medication不同的类型

我使用了一个相当丑陋的构造

type Medication = {x:int;y:int;symbol:char}
Actor =
    | Medication of Medication
Run Code Online (Sandbox Code Playgroud)

但它阻止我在接口上匹配.

2)F#中没有隐式接口重写.这个鳕鱼已经有很多样板元素,比如'member this.X = this.X'.有了比'IActor'更复杂的东西,它会产生越来越多的问题.

能否请您在F#中正确使用Discriminated Unions参数的命名参数?这种情况有帮助吗?

Guy*_*der 6

派生类型的模式匹配是否为F#惯用?

我会说不,因为你有一个基本的谬误,即对象层次结构中的类型代码是惯用的F#.

我不认为类型的对象层次结构是惯用的F#或功能.所以这个问题无效.我从历史角度看待F#来自ML和OCaml,而不是来自OO方面.正如我在做功能代码时总是建议的那样,忘掉你对OO的了解,因为它只会引导你走上混乱的道路.如果你必须与OO接口,那么你将不得不咬紧牙关,但在可能的情况下将OO留出.

类和继承是否意味着在F#中使用?
或者他们只是为了兼容.Net?

如果你在MSDN文章看看F#类下的部分 When to Use Classes, Unions, Records, and Structures,你会看到

鉴于可供选择的类型种类繁多,您需要充分了解每种类型的设计,以便为特定情况选择合适的类型.类设计用于面向对象的编程上下文.面向对象编程是在为.NET Framework编写的应用程序中使用的主要范例.如果您的F#代码必须与.NET Framework或其他面向对象的库密切配合,特别是如果您必须从面向对象的类型系统(如UI库)扩展,那么类可能是合适的.

如果您没有与面向对象的代码紧密互操作,或者您正在编写自包含的代码并因此避免与面向对象代码的频繁交互,则应考虑使用记录和区分联合.单个经过深思熟虑的区分联合以及适当的模式匹配代码通常可以用作对象层次结构的简单替代方法.有关受歧视联盟的更多信息,请参阅歧视联盟(F#).

.

我应该使用记录,如果是的话,如何使用记录?

首先不要使用有区别的联合,然后如果数据变得更复杂,请查看记录.在某些情况下,我会大量使用记录,但大部分时间都没有.这是一个it depends问题.

记录具有比类更简单的优点,但是当类型的需求超过其简单性可以实现的记录时,记录是不合适的.记录基本上是简单的值聚合,没有可以执行自定义操作的单独构造函数,没有隐藏字段,也没有继承或接口实现.虽然可以将属性和方法等成员添加到记录中以使其行为更复杂,但存储在记录中的字段仍然是值的简单聚合.有关记录的更多信息,请参阅记录(F#).

结构对于小型数据聚合也很有用,但它们与类和记录的不同之处在于它们是.NET值类型.类和记录是.NET引用类型.值类型和引用类型的语义不同,因为值类型是按值传递的.这意味着当它们作为参数传递或从函数返回时,它们会逐位复制.它们也存储在堆栈中,或者如果它们用作字段,则嵌入在父对象内,而不是存储在堆上它们自己的单独位置.因此,当访问堆的开销是个问题时,结构适合于频繁访问的数据.有关结构的更多信息,请参阅结构(F#).

.

在C#中,打开类型被认为是一种不好的做法.对F#来说是一样的吗?

看到答案 Is pattern matching on derived types idiomatic for F#?

怎么样?

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
Run Code Online (Sandbox Code Playgroud)

由于这不是一个完整的游戏,我做了一些测试,以显示玩家与其他演员互动的基本知识.

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.
Run Code Online (Sandbox Code Playgroud)

如果您对此如何运作有具体问题,请提出新问题并再次回答此问题.

希望OO的人们能够理解为什么我们这些转向函数式编程的人会喜欢它,以及为什么在从头开始编写功能代码时,你不应该在脑子里想到任何OO想法.再次,如果您正在与其他OO代码交互,那么有OO的想法是好的.