Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is pattern matching on derived types idiomatic for F#?

I want to implement the following in most idiomatic way possible

A player is on a map.

  1. If he is in the same position with an arrow, he gets 1 damage

  2. If he is in the same position with a creature, he gets damage equal to hp of creature

  3. If he is in the same position with a coin, he gets 1$

  4. If he is in the same position with a medication, he heals by 1

Here is a stub written for interaction:

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

1) Are classes and inheritance meant to be used in F#? Or are they just for compatibility with .Net? Sould I use records instead, and, if yes, how?

2)Switching on types is considered a bad practice in C#. Is it the same for F#? If yes what should I write instead of otherActorsWithSamePosition? Implementing otherXsWithSamePosition for each class X derived from actor doesn't look like a scalable solution

UPDATE:

I tried to implement it with a discriminated union, but didn't manage to compile:

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

The problems:

1)More important:

I don't manage to make a discriminated union of existing records

Actor =
    | Medication {x:int;y:int;symbol:char} 

raises error about deprecated construct

type Medication = {x:int;y:int;symbol:char}
Actor =
        | Medication

considers Medication and Actor.Medication different types

I used a rather ugly construct of

type Medication = {x:int;y:int;symbol:char}
Actor =
    | Medication of Medication

but it prevents me from matching on interfaces.

2)No implicit interface imlementations in F#. This cod already has a lot of boilerplate elements like 'member this.X=this.X'. With something more complex than 'IActor' it will create more and more problems.

Can you please provide an example of correct usage of named parameters of Discriminated Unions parameters in F#? Does it help in this case?

like image 902
user2136963 Avatar asked Apr 10 '16 11:04

user2136963


2 Answers

Is pattern matching on derived types idiomatic for F#?

I would say no because you have a basic fallacy that code with types in an object hierarchy is idiomatic F#.

I don't consider an object hierarchy of types to be idiomatic F# or functional. So the question is invalid. I take a historical perspective with F# coming from ML and OCaml as opposed to coming from an OO side. As I always suggest when doing functional code, forget what you know about OO as it will only lead you down the path of confusion. If you have to interface with OO then you will have to bite the bullet but leave OO out when possible.

Are classes and inheritance meant to be used in F#?
Or are they just for compatibility with .Net?

If you take a look at the MSDN article on F# classes under the section When to Use Classes, Unions, Records, and Structures you will see

Given the variety of types to choose from, you need to have a good understanding of what each type is designed for to select the appropriate type for a particular situation. Classes are designed for use in object-oriented programming contexts. Object-oriented programming is the dominant paradigm used in applications that are written for the .NET Framework. If your F# code has to work closely with the .NET Framework or another object-oriented library, and especially if you have to extend from an object-oriented type system such as a UI library, classes are probably appropriate.

If you are not interoperating closely with object-oriented code, or if you are writing code that is self-contained and therefore protected from frequent interaction with object-oriented code, you should consider using records and discriminated unions. A single, well thought–out discriminated union, together with appropriate pattern matching code, can often be used as a simpler alternative to an object hierarchy. For more information about discriminated unions, see Discriminated Unions (F#).

.

Should I use records instead, and, if yes, how?

No use a discriminated union at first and then if the data gets more complicated take a look at records. In certain situations I use records a lot but most of the time do not. This is a it depends question.

Records have the advantage of being simpler than classes, but records are not appropriate when the demands of a type exceed what can be accomplished with their simplicity. Records are basically simple aggregates of values, without separate constructors that can perform custom actions, without hidden fields, and without inheritance or interface implementations. Although members such as properties and methods can be added to records to make their behavior more complex, the fields stored in a record are still a simple aggregate of values. For more information about records, see Records (F#).

Structures are also useful for small aggregates of data, but they differ from classes and records in that they are .NET value types. Classes and records are .NET reference types. The semantics of value types and reference types are different in that value types are passed by value. This means that they are copied bit for bit when they are passed as a parameter or returned from a function. They are also stored on the stack or, if they are used as a field, embedded inside the parent object instead of stored in their own separate location on the heap. Therefore, structures are appropriate for frequently accessed data when the overhead of accessing the heap is a problem. For more information about structures, see Structures (F#).

.

Switching on types is considered a bad practice in C#. Is it the same for F#?

See the answer to Is pattern matching on derived types idiomatic for F#?

How?

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

Since this is not a complete game I did a few test to show the basics of a player interacting with the other actors.

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.

If you have specific questions on how this works, please ask a new question and refer back to this question.

Hopefully people from OO will see why those of us who switched to functional programming enjoy it and why you should not have any OO thoughts in your mind when doing functional code from the ground up. Again if you are interacting with other OO code then having OO thoughts is fine.

like image 84
Guy Coder Avatar answered Sep 21 '22 05:09

Guy Coder


1) Are classes and inheritance meant to be used in F#? Or are they just for compatibility with .Net? Sould I use records instead, and, if yes, how? Yes, when it makes sense to structure your program in an object-oriented way.

  • OO defines closed sets of operations (interfaces) over an open set of data (classes).
  • FP defines open sets of operations (functions) over a closed set of data (discriminated unions).

In other words, OO makes it easy to implement the same operation over many shapes of data, but hard to add new operations; FP makes it easy to implement many different operations on data, but hard to modify your data. The approaches are complementary. Pick the one that makes the most sense for your problem. The nice thing about F# is that it has great support for both; you'll actually want to default to FP in F#, as the syntax is lighter and more expressive.

2)Switching on types is considered a bad practice in C#. Is it the same for F#? If yes what should I write instead of otherActorsWithSamePosition? Implementing otherXsWithSamePosition for each class X derived from actor doesn't look like a scalable solution

Switching on types leads to brittle code in OO, and that doesn't change in F#. You'll still run into the same issues. Switching on data types in FP is idiomatic however. If instead of class hierarchies you use DUs, then you'll have no choice but to switch (pattern-match) on your data types. And that's fine, because the compiler is there to help you with that, unlike in OO.

A player is on a map.

  • If he is in the same position with an arrow, he gets 1 damage
  • If he is in the same position with a creature, he gets damage equal to hp of creature
  • If he is in the same position with a coin, he gets 1$
  • If he is in the same position with a medication, he heals by 1

First let's define our domain model. One aspect of your design I find problematic is that you store object positions in the actors and have no actual map object. I find it a good design principle to have an object store only its intrinsic properties, and move the extrinsic properties where it makes more sense, keeping the domain model as small as possible. An actor's location is not an intrinsic property.

So, using idiomatic F# types:

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

One information this leaves out is the symbol, but this is really only a concern for rendering and thus best moved aside in a helper function:

let symbol = function
| Arrow -> '/'
| Medication -> '♥'
| Creature c -> 
    match c with
    | Zombie _ -> 'X'
| Coin -> '$'
| Player _ -> '@'

Now since from your description there can be multiple actors on a single tile, we'll represent our map as an Actor list [][], i.e. a 2D map of lists of actors.

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}]

Note that this not a very functional approach as we'll mutate the array rather than create new ones, but in game programming this is common, for obvious performance reasons.

And now your playerInteraction function looks like this (actually implementing the spec rather printing out strings):

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

A question this leaves out is: how do I get the player's position? You could cache it, or just compute it on the fly every time. If the map is small this is not slow. Here's an example function for that (not optimized, if you use 2D maps extensively you'll want to implement fast generic iterators that don't allocate):

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))
like image 21
Asik Avatar answered Sep 22 '22 05:09

Asik