Skip to content

Instantly share code, notes, and snippets.

@Kavignon
Created March 27, 2020 14:19
Show Gist options
  • Save Kavignon/4fc816e6ac2795d20368647e82630c12 to your computer and use it in GitHub Desktop.
Save Kavignon/4fc816e6ac2795d20368647e82630c12 to your computer and use it in GitHub Desktop.
Immutability concept with Record (value & reference) + Discriminated Union (value & reference) along with objects. The purpose of the gist is to help others understand how immutability works through the use of F#.
module GameDomain
[<Measure>] type gold
[<Measure>] type dmg // damage
[<Measure>] type ctr // critical
[<Measure>] type hl // hit limit
[<Measure>] type kg // weight in kilograms
type HeroClass =
| Archer
| Assassin
| Knight
| Sorcerer
| Swordsman
type ConsumableItem =
| HealthPotion
| Elixir
| PhoenixFeather
| MedicinalHerb
type CombatStyle =
| BladeMaster
| DualWielder of int
| MaceUser of int
| Archer of int
with
member x.actionPoints =
match x with
| DualWielder ap -> ap
| MaceUser ap -> ap
| Archer ap -> ap
[<Struct>]
type Dimension = {
Lenght: float
Width: float
Height: float
}
type ItemDetails = {
Weight: float<kg>
Price: int<gold>
}
[<Struct>]
type WeaponRank =
| RankB
| RankA
| RankS
[<Struct>]
type WeaponStat = {
Damage : float<dmg>
CriticalHitProbability : float<ctr>
Durability : int<hl>
Rank : WeaponRank
}
type Weapon = {
Name: string
Stats: WeaponStat
Details: ItemDetails
Dimensions: Dimension
CombatStyle: CombatStyle
}
[<Struct>]
type GameTreasure =
| Health of health: ConsumableItem
| Mana of mana: ConsumableItem
| MagicFeather of feather: ConsumableItem
| Weapon of weapon: Weapon
| Currency of currency: float<gold>
// Currently, the object GameHero is immutable
// All class members are public by default contrarly to C# (private)
// Let bindings in a class are private and cannot be made public
// If you are defining classes that need to interop with other .NET code, do not define them inside a module! Define them in a namespace instead
// F# modules are exposed as static classes
// and any F# classes defined inside a module are then defined as nested classes within the static class, which can mess up your interop
type GameHero(name: string, heroClass: HeroClass, consummables: ConsumableItem list, collectedTreasures: GameTreasure array, weapons: Weapon list) =
let mutable consummables = consummables // consummables can now be mutated in the immutable type.
member _.Name = name
member _.Class = heroClass
member _.CollectedTreasures = collectedTreasures
member _.WeaponStash = weapons
// mutable auto property
member val CurrentConsummables = consummables with get, set
member _.AddConsummable consummable = consummables <- consummable :: consummables
// Learn more about F# at http://fsharp.org
open System
open GameDomain
[<EntryPoint>]
let main argv =
let healthPotionConsummable = HealthPotion
let elixirConsummable = Elixir
printfn "health consummable: %A elixir consummable: %A \n\n\n" healthPotionConsummable elixirConsummable
let healthPotionTreasure = Health healthPotionConsummable
let elixirTreasure = Mana elixirConsummable
printfn "health potion: \n%A elixir potion: \n%A \n\n\n" healthPotionTreasure elixirTreasure
printfn "Things will get funky starting here. :)"
let mutable secondHealthConsummable = healthPotionConsummable
secondHealthConsummable <- PhoenixFeather // I lied :D
let areReferenceEqual = obj.ReferenceEquals(healthPotionConsummable, secondHealthConsummable)
printfn "First health consummable: %A Second health consummable: %A \n\n\n\n" healthPotionConsummable secondHealthConsummable
printfn "Are the reference of both consummable equal? %A" areReferenceEqual // No
// Confirmed: Second health consummable; although referring back to the first one doesn't affect the first reference pointer even though it's a mutable refenrece pointer
let consummablesList = [ healthPotionConsummable; elixirConsummable; secondHealthConsummable ]
let mutable secondConsummablesList = consummablesList
secondConsummablesList <- secondConsummablesList |> List.filter(fun x -> x <> secondHealthConsummable)
printfn "Original list: %A \n\n\n mutable list copy: %A \n\n\n" consummablesList secondConsummablesList
let areReferenceEqual = obj.ReferenceEquals(consummablesList, secondConsummablesList)
printfn "Are the reference of both collections equal? %A\n\n" areReferenceEqual // No
// Confirmed yet again: The orginal isn't affected by the copy.
let kitanaStats = { Damage = 83.6<dmg>; CriticalHitProbability = 0.06<ctr>; Durability = 75<hl>; Rank = RankS }
let kitanaDetails = { Weight = 4.3<kg>; Price = 3500<gold> }
let kitanaDimensions = { Lenght = 1.00; Width = 0.3; Height = 0.3 }
let kitana = { Name = "Kitana"; Stats = kitanaStats; Details = kitanaDetails; Dimensions = kitanaDimensions; CombatStyle = BladeMaster }
let secondKitana = { kitana with Name = "Second Kitana"; CombatStyle = Archer 4 }
let areReferenceEqual = obj.ReferenceEquals(kitana, secondKitana)
printfn "Are the reference of both kitana equal? %A\n\n" areReferenceEqual // No (Now the record [reference type] are both immutable and the copy is updated.
let gameHero = GameHero("The F# Swordsman", Swordsman, consummablesList, [| healthPotionTreasure; elixirTreasure|], [ kitana; secondKitana ])
let secondHero = gameHero
let someCheck = obj.ReferenceEquals(gameHero, secondHero)
printfn "Are the reference of both heroes equal? %A\n\n" someCheck
secondHero.CurrentConsummables <- [ MedicinalHerb ]
0 // return an integer exit code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment