Created
March 27, 2020 14:19
-
-
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#.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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