On my journey to start getting better with functional programming, I discovered, with the help of a member of the SO family, what lens. I even made some research on it with the links down below to understand more about them.
https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/basic-lensing
http://fluffynukeit.com/how-functional-programming-lenses-work/
https://medium.com/#dtipson/functional-lenses-d1aba9e52254#.27yw4gnwk
With all that knowledge, I thought I could give them a try and see whether or not I could understand their functionnality and the reasons why they're useful in FP. My problem at the moment is moving from the type members that were define to access and modify fields in my equipment record that I've define for a game that I'm prototyping at the moment. I will put snippets of the Equipment records, the members that were there before and the functional lens I'm trying to create but just won't work. After the first pattern matching, it expects the code to have the same return value, when I'd like it to be a general value to be returned, depending on a pattern that I had successfully matched!
For the the remain of the code, instead of omitting the code and making it not compile while you're trying to give me a hand, I've thought it be best to put the important snippets here and a public to the relevant code so you can compile it on your local machine ! The public gist can be found here. It's a lot of for my definitions, the relevant code is from line 916.
type Equipment = {
Helmet : Hat option
Armor : Armor option
Legs : Pants option
Gloves : Gauntlets option
Ring : Ring option
Weapon : Weaponry option
Shield : Shield option
Loot : ConsumableItem option
}
let equipPurchasedProtection newItem (inventory,equipment) =
match newItem with
| Helmet ->
match equipment.Helmet with
| None ->
let newEquipment = { equipment with Helmet = Some newItem }
(inventory,newEquipment)
| Some oldHelm
if (playerWantsToAutoEquip newItem) then
let newEquipment = { equipment with Helmet = Some newItem }
let newInventory = inventory |> addToInventory oldHelm
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
| Gloves ->
match equipment.Hands with
| None ->
let newEquipment = { equipment with Hands = Some newItem }
(inventory,newEquipment)
| Some oldGloves
if (playerWantsToAutoEquip newItem) then
let newEquipment = { equipment with Hands = Some newItem }
let newInventory = inventory |> addToInventory oldGloves
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
| Boots ->
match equipment.Feet with
| None ->
let newEquipment = { equipment with Boot = Some newItem }
(inventory,newEquipment)
| Some oldBoots
if (playerWantsToAutoEquip newItem) then
let newEquipment = { equipment with Boot = Some newItem }
let newInventory = inventory |> addToInventory oldBoots
(newInventory,newEquipment)
else
let newInventory = inventory |> addToInventory newItem
(newInventory,equipment)
let equipPurchasedItem newItem (inventory,equipment) =
let equipFunction =
match newItem with
| Protection(Helmet(_)) -> genericEquipFunction HelmetFun_
| Protection(Gloves(_)) -> genericEquipFunction GlovesFun_
| Protection(Legs(_)) -> genericEquipFunction LegsFun_
| Protection(Armor(_)) -> genericEquipFunction ArmorFun_
| Protection(Ring(_)) -> genericEquipFunction RingFun_
| Protection(Shield(_)) -> genericEquipFunction ShieldFun_
| Weapon _ -> genericEquipFunction WeaponFun_
| Consumable HealthPotion -> genericEquipFunction LootFun_
| Consumable HighHealthPotion -> genericEquipFunction LootFun_
| Consumable MegaHealthPotion -> genericEquipFunction LootFun_
| Consumable Elixir -> genericEquipFunction LootFun_
| Consumable HighElixir -> genericEquipFunction LootFun_
| Consumable MegaElixir -> genericEquipFunction LootFun_
| Consumable PhoenixFeather -> genericEquipFunction LootFun_
| Consumable MedicinalHerb -> genericEquipFunction LootFun_
let itemForInventory,newEquipment = equipFunction (Some newItem) equipment
match itemForInventory with
| None -> (inventory,newEquipment)
| Some item ->
let newInventory = inventory |> addToInventory { Item = item; Count = 1 }
(newInventory,newEquipment)
UPDATE 1
Here's a look at one of the lens function that I'm using to equip purchased items.
let getArmorFun e = e.Armor
let equipArmorFun newArmor e = { e with Armor = newArmor }
let ArmorFun_ = (getArmorFun, equipArmorFun)
Having looked at your model more closely, I can confirm my initial impression: you're using a lot more types than you should. Many of those types should be instances; in this case, record instances. Here's a good rule-of-thumb for when you should use a type or an instance. If the two things are interchangeable, they should be two instances of the same type. If they're NOT interchangeable, then (and only then) they should be two different types. Here's an example of what I mean. Here's a section of your code that takes up an entire screen:
type Weaponry =
| Dagger of Dagger
| Sword of Sword
| Axe of Axe
| Spear of Spear
| Staff of Staff
| LongBlade of Blade
| Spellbook of Spellbook
with
member x.Name =
match x with
| Dagger d -> d.ToString()
| Sword s -> s.ToString()
| Axe a -> a.ToString()
| Spear s -> s.ToString()
| Staff s -> s.ToString()
| LongBlade lb -> lb.ToString()
| Spellbook sb -> sb.ToString()
member x.Price =
match x with
| Dagger w -> w.Price
| Sword w -> w.Price
| Axe w -> w.Price
| Spear w -> w.Price
| Staff w -> w.Price
| LongBlade w -> w.Price
| Spellbook w -> w.Price
member x.Weight =
match x with
| Dagger w -> w.Weight
| Sword w -> w.Weight
| Axe w -> w.Weight
| Spear w -> w.Weight
| Staff w -> w.Weight
| LongBlade w -> w.Weight
| Spellbook w -> w.Weight
member x.Stats =
match x with
| Dagger w -> w.WeaponStats :> IStats
| Sword w -> w.WeaponStats :> IStats
| Axe w -> w.WeaponStats :> IStats
| Spear w -> w.WeaponStats :> IStats
| Staff w -> w.WeaponStats :> IStats
| LongBlade w -> w.WeaponStats :> IStats
| Spellbook w -> w.SpellStats :> IStats
What's different between all these items? The last line, where Spellbooks have SpellbookStats instead of WeaponStats. That's it! As for your other weapon types -- dagger, sword, axe, spear, etc... they're ALL identical in "shape". They all have weapon stats, price, weight, etc.
Here's a redesign of that entire weapon model:
type ItemDetails = { Weight: float<kg>; Price: int<usd> }
type PhysicalWeaponType =
| Dagger
| Sword
| Axe
| Spear
| Staff
| LongBlade
type MagicalWeaponType =
| Spellbook
// Could later add wands, amulets, etc.
type WeaponDetails =
| PhysicalWeapon of PhysicalWeaponType * WeaponStat
| MagicalWeapon of MagicalWeaponType * SpellbookStats
type Weaponry =
{ Name: string
ItemDetails: ItemDetails
WeaponDetails: WeaponDetails }
with member x.Weight = x.ItemDetails.Weight
member x.Price = x.ItemDetails.Price
member x.Stats = match x.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats :> IStats
| MagicalWeapon (_, stats) -> stats :> IStats
// Now let's create some weapons. In the real game this would be read
// from a JSON file or something, so that the game is easily moddable
// by end users who want to add their own custom weapons.
let rustedDagger = {
Name = "Rusted dagger"
ItemDetails = { Weight = 2.10<kg>; Price = 80<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 5.60<dmg>; Defense = 1.20<def>; Intelligence = None; Speed = 1.00<spd>; Critical = 0.02<ctr>; HitLimit = 20<hl>; Rank = RankE })
}
let ironDagger = {
Name = "Iron dagger"
ItemDetails = { Weight = 2.80<kg>; Price = 200<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 9.80<dmg>; Defense = 2.30<def>; Intelligence = None; Speed = 1.10<spd>; Critical = 0.04<ctr>; HitLimit = 25<hl>; Rank = RankD })
}
let steelDagger = {
Name = "Steel dagger"
ItemDetails = { Weight = 4.25<kg>; Price = 350<usd> }
WeaponDetails = PhysicalWeapon (Dagger, { Damage = 13.10<dmg>; Defense = 3.00<def>; Intelligence = None; Speed = 1.15<spd>; Critical = 0.05<ctr>; HitLimit = 30<hl>; Rank = RankC })
}
let brokenSword = {
Name = "Broken sword"
ItemDetails = { Weight = 7.20<kg>; Price = 90<usd> }
WeaponDetails = PhysicalWeapon (Sword, { Damage = 5.40<dmg>; Defense = 2.50<def>; Intelligence = None; Speed = 1.20<spd>; Critical = 0.01<ctr>; HitLimit = 10<hl>; Rank = RankE })
}
let rustedSword = {
Name = "Rusted sword"
ItemDetails = { Weight = 8.50<kg>; Price = 120<usd> }
WeaponDetails = PhysicalWeapon (Sword, { Damage = 8.75<dmg>; Defense = 2.90<def>; Intelligence = None; Speed = 1.05<spd>; Critical = 0.03<ctr>; HitLimit = 20<hl>; Rank = RankD })
}
// And so on for iron and steel swords, plus all your axes, spears, staves and long blades.
// They should all be instances, not types. And spellbooks, too:
let rank1SpellbookDetails = { Weight = 0.05<kg>; Price = 150<usd> }
let rank2SpellbookDetails = { Weight = 0.05<kg>; Price = 350<usd> }
let bookOfFireball = {
Name = "Fireball"
ItemDetails = rank1SpellbookDetails
WeaponDetails = MagicalWeapon (Spellbook, { Damage = 8.0<dmg>; AttackRange = 1; Rank = RankE; Uses = 30 ; ManaCost = 12.0<mp> })
}
// Same for Thunder and Frost
let bookOfHellfire = {
Name = "Hellfire"
ItemDetails = rank2SpellbookDetails
WeaponDetails = MagicalWeapon (Spellbook, { Damage = 6.50<dmg>; AttackRange = 2; Rank = RankD; Uses = 25; ManaCost = 20.0<mp> })
}
// And so on for Black Fire and Storm of Blades
let computeCharacterOverallOffensive
// (rank: WeaponRank) // Don't need this parameter now
(weapon: Weaponry)
(cStats: CharacterStats) =
let weaponDamage =
match weapon.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats.Damage
| MagicalWeapon (_, stats) -> stats.Damage
let weaponRank =
match weapon.WeaponDetails with
| PhysicalWeapon (_, stats) -> stats.Rank
| MagicalWeapon (_, stats) -> stats.Rank
// This should really be a method on the Rank type
let rankMultiplier =
match weaponRank with
| RankE -> 1.0100
| RankD -> 1.0375
| RankC -> 1.0925
| RankB -> 1.1250
| RankA -> 1.1785
| RankS -> 1.2105
cStats.Strength * rankMultiplier * weaponDamage
Notice how all the details of the Weaponry type fit on one screen now? And there's WAY less duplication. I kept the distinction between different types of physical weapons (daggers, swords, etc) since it's likely that you'll have characters that specialize in one or two types: a sword specialist can't use an axe, or he takes a 50% strength penalty when he uses an axe, and so on. But I doubt that you're ever going to have a character who can only use iron daggers but can't use steel daggers. Different types of daggers are completely interchangeable in this kind of game -- the player would be VERY surprised if they weren't. So they shouldn't be different types. And the various types of physical weapons are almost interchangeable, so their models should be as similar as possible, too. Put the stats in the part that doesn't differ, and leave the type (Dagger, Sword, Axe) as the only difference between the physical weapons.
This has been a really long answer and I still haven't gotten into your actual question about lenses! But since I winced on looking at the code and thought, "He is making WAY too much work for himself", I had to address this part first.
I think you'd benefit from taking your code over to https://codereview.stackexchange.com/ and asking people there to take a look at it and suggest ways to tighten up your model. Once your model is improved, I think you'll find the lens code to be a lot easier to write as well. And as I said before, DON'T try to write the lens code on your own! Use a library like Aether or F#+ to help you. In your shoes, I'd probably go with Aether simply because it has more documentation than F#+ seems to have; F#+ seems (AFAICT) to be more aimed at people who have already used Haskell lenses and don't need any reminders about how to use them.
UPDATE 1: Have another snippet for how I'd suggest you do armor:
type CharacterProtectionStats = {
Defense : float<def>
Resistance : float<res>
Intelligence : float<intel> option
MagicResist : float<mgres>
Speed : float<spd>
EquipmentUsage : int<eu>
}
with
interface IStats with
member x.showStat() =
sprintf "Defense : %O - Resistance : %O - Magic resistance : %O - Speed : %O - Equipment usage : %O" x.Defense x.Resistance x.MagicResist x.Speed x.EquipmentUsage
type CharacterProtectionDetails = {
Name : string
// No Type field here, because that's staying in the DU
ItemDetails : ItemDetails
ArmorStats : CharacterProtectionStats
}
type Hat = Hat of CharacterProtectionDetails
type Armor = Armor of CharacterProtectionDetails
type Pants = Pants of CharacterProtectionDetails
// etc.
type CharacterProtection =
| Shield of Shield
// | Ring of Ring // REMOVED. Rings are different; see below.
| Gloves of Gauntlets
| Legs of Pants
| Armor of Armor
| Helmet of Hat
let sorcererHat = Hat {
Name = "Sorcerer Hat"
ItemDetails = { Weight = 1.0<kg>; Price = 120<usd> }
ArmorStats = { Defense = 1.20<def>; Resistance = 1.30<res>; Intelligence = Some 3.00<intel>; MagicResist = 1.80<mgres>; Speed = 1.00<spd>; EquipmentUsage = 100<eu> }
}
// Other hats...
let steelArmor = Armor.Armor {
Name = "Steel Armor"
ItemDetails = { Weight = 15.0<kg>; Price = 450<usd> }
ArmorStats = { Defense = 17.40<def>; Resistance = 6.10<res>; Intelligence = None; MagicResist = 2.30<mgres>; Speed = 0.945<spd>; EquipmentUsage = 100<eu> }
}
// "Armor.Armor" is kind of ugly, but otherwise it thinks "Armor" is
// CharacterProtection.Armor. If we renamed the CharacterProtection DU
// item to ChestProtection instead, that could help.
type AccessoryStats = {
ExtraStrength : float<str> option
ExtraDamage : float<dmg> option
ExtraHealth : float<hp> option
ExtraMana : float<mp> option
}
with
interface IStats with
member x.showStat() =
sprintf ""
static member Initial =
{ ExtraDamage = None; ExtraStrength = None; ExtraHealth = None; ExtraMana = None }
type Ring = {
Name : string
ItemDetails : ItemDetails
RingStats : AccessoryStats
}
type Amulet = {
Name : string
ItemDetails : ItemDetails
AmuletStats : AccessoryStats
}
type AccessoryItems =
| Ring of Ring
| Amulet of Amulet
// Could add other categories too
let standardRingDetails = { Weight = 0.75<kg>; Price = 275<usd> }
let strengthRing = {
Name = "Extra strength ring"
ItemDetails = standardRingDetails
RingStats = { RingStats.Initial with ExtraStrength = Some 4.50<str> }
}
let damageRing = {
Name = "Extra damage ring"
ItemDetails = standardRingDetails
RingStats = { RingStats.Initial with ExtraDamage = Some 5.00<dmg> }
}
Related
Given the following:
type IFruit = interface end
type Avocado = { color : string; age : int } interface IFruit
let (|AvocadoTexture|) (a : Avocado) = if a.age < 7 then "firm" else "mushy"
... Why does this work:
let texture (f : IFruit) =
match f with
| :? Avocado as a -> if a.age < 7 then "firm" else "mushy"
| _ -> String.Empty
... but not this?
let texture (fruit : IFruit) =
match fruit with
| AvocadoTexture t -> t // "The type IFruit does not match the type Avocado"
| _ -> String.Empty
fruit may be any IFruit, but the AvocadoTexture Active Pattern only accepts the specific implementation Avocado, as per the type annotation of a.
If you want the Active Pattern to accept any IFruit, but only return a useful value for an Avocado, you can make it partial:
let (|AvocadoTexture|_|) (f : IFruit) =
match f with
| :? Avocado as a ->
if a.age < 7 then "firm" else "mushy"
|> Some
| _ -> None
Now your texture function works as you wanted:
let texture (fruit : IFruit) =
match fruit with
| AvocadoTexture t -> t
| _ -> String.Empty
Just bear in mind that there are Partial Active Patterns and Active Patterns. Active Patterns have up to 7 tags that something can be concretely matched against. Both forms are useful.
Active Patterns are better if you want the compiler to tell you all the places where you've missed handling a case after you've decided that you need an extra one. The compiler can be configured to flag this as an error rather than a warning if you want to be extra strict about it.
open System
type IFruit = interface end
type Avocado =
{ color : string; age : int }
interface IFruit
static member tryFromIFruit(x:IFruit) =
match x with
| :? Avocado -> Some(x:?>Avocado)
| _ -> None
let (|Firm|Mushy|) (a : Avocado) = if a.age < 7 then Firm else Mushy
let texture (fruit : IFruit) =
match fruit |> Avocado.tryFromIFruit with // we're not sure if it's an Avocado.
| Some(Firm) -> "firm" // use Some(SomethingElse()) when you want to collapse an extra layer of "match" statements.
| Some(Mushy) -> "mushy"
| None -> ""
texture ( { color = "green"; age = 4 } :> IFruit)
documentation: https://learn.microsoft.com/en-us/dotnet/articles/fsharp/language-reference/active-patterns
Given the following parametric type
type SomeDU2<'a,'b> =
| One of 'a
| Two of 'a * 'b
I have to functions that check if the given param is the respective union case without regard to params
let checkOne x =
match x with
| One _ -> true
| _ -> false
let checkTwo x =
match x with
| Two _ -> true
| _ -> false
This works pretty nice and as expected
let oi = checkOne (One 1)
let os = checkOne (One "1")
let tis = checkTwo (Two (1, "1"))
let tsi = checkTwo (Two ("1", 1))
I can switch the types as I like.
Now However I like to combine those two functions into one creation function
let makeUC () = (checkOne, checkTwo)
and then instantiate like this
let (o,t) = makeUC ()
only it gives me this error message now
Value restriction. The value 'o' has been inferred to have generic type
val o : (SomeDU2<'_a,'_b> -> bool)
Either make the arguments to 'o' explicit or, if you do not intend for it to be generic, add a type annotation.
val o : (SomeDU2<obj,obj> -> bool)
Actually I dont want that - nor do I need that.
Probably its a instance of missing higher kinded types in F#
Is there a way around this?
Edit
Actually me question wasnt complety as per #johns comment below.
Obviously I can do the following
let ro1 = o ((One 1) : SomeDU2<int,int>)
let rt1 = t (Two (1,2))
which then will backwards infer o and t to be of type SomeDU2<int,int> -> bool (I find this backwards inference very strange thou). The problem then is that o wont allow for the below anymore.
let ro2 = o ((One "1") : SomeDU2<string,int>)
So I'd have to instantiate a specific o instance for every combination of generic parameters of SomeDU2.
You would run into the value restriction even without the tuple:
let o = (fun () -> checkOne)()
If you need the results of invoking a function to be applicable to values of any type, then one solution would be to create instances of a nominal type with a generic method:
type DU2Checker =
abstract Check : SomeDU2<'a,'b> -> bool
let checkOne = {
new DU2Checker with
member this.Check(x) =
match x with
| One _ -> true
| _ -> false }
let checkTwo = {
new DU2Checker with
member this.Check(x) =
match x with
| Two _ -> true
| _ -> false }
let makeUC() = checkOne, checkTwo
let o,t = makeUC()
let false = o.Check(Two(3,4))
I am not sure about "exclusive state management" thing in the title, I did my best making it up trying to put the problem concisely.
I am porting some of my C# code to F# trying to do it as idiomatic as I can. I have an entity that requests a number of ID's from a sequence in my database and then dispenses these ID to anyone in need. Once an id is given out it should no longer be available for anybody else. Hence there must be some sort of state associated with that entity that keeps track of the remaining number of IDs. Since using a mutable state is not idiomatic, what I can do is to write something like this:
let createIdManager =
let idToStartWith = 127
let allowed = 10
let givenOut = 0
(idToStartWith, allowed, givenOut)
-
let getNextAvailableId (idToStartWith, allowed, givenOut) =
if givenOut< allowed
then ((idToStartWith, allowed, givenOut+ 1), Some(idToStartWith + givenOut))
else ((idToStartWith, allowed, givenOut), None)
let (idManager, idOpt) = getNextAvailableId createIdManager()
match idOpt with
| Some(id) -> printf "Yay!"
| None -> reloadIdManager idManager |> getNextAvailableId
This approach is idiomatic (as far as I can tell) but extremely vulnerable. There are so many ways to get it messed up. My biggest concern is that once an id is advanced and a newer copy of id manager is made, there is no force that can stop you from using the older copy and get the same id again.
So how do I do exclusive state management, per se, in F#?
If you only need to initialize the set of ids once then you can simply hide a mutable reference to a list inside a local function scope, as in:
let nextId =
let idsRef = ref <| loadIdsFromDatabase()
fun () ->
match idsRef.Value with
| [] ->
None
| id::ids ->
idsRef := ids
Some id
let id1 = nextId ()
let id2 = nextId ()
You could use a state-monad(Computational Expression).
First we declare the state-monad
type State<'s,'a> = State of ('s -> 'a * 's)
type StateBuilder<'s>() =
member x.Return v : State<'s,_> = State(fun s -> v,s)
member x.Bind(State v, f) : State<'s,_> =
State(fun s ->
let (a,s) = v s
let (State v') = f a
v' s)
let withState<'s> = StateBuilder<'s>()
let runState (State f) init = f init
Then we define your 'IdManager' and a function to get the next available id as well as the new state after the execution of the function.
type IdManager = {
IdToStartWith : int
Allowed : int
GivenOut : int
}
let getNextId state =
if state.Allowed > state.GivenOut then
Some (state.IdToStartWith + state.GivenOut), { state with GivenOut = state.GivenOut + 1 }
else
None, state
Finally we define our logic that requests the ids and execute the state-monad.
let idStateProcess =
withState {
let! id1 = State(getNextId)
printfn "Got id %A" id1
let! id2 = State(getNextId)
printfn "Got id %A" id2
//...
return ()
}
let initState = { IdToStartWith = 127; Allowed = 10; GivenOut = 0 }
let (_, postState) =
runState
idStateProcess
initState //This should be loaded from database in your case
Output:
Got id Some 127
Got id Some 128
I'm working through the book Land of Lisp in F# (yeah weird, I know). For their first example text adventure, they make use of global variable mutation and I'd like to avoid it. My monad-fu is weak, so right now I'm doing ugly state passing like this:
let pickUp player thing (objects: Map<Location, Thing list>) =
let objs = objects.[player.Location]
let attempt = objs |> List.partition (fun o -> o.Name = thing)
match attempt with
| [], _ -> "You cannot get that.", player, objs
| thing :: _, things ->
let player' = { player with Objects = thing :: player.Objects }
let msg = sprintf "You are now carrying %s %s" thing.Article thing.Name
msg, player', things
let player = { Location = Room; Objects = [] }
let objects =
[Room, [{ Name = "whiskey"; Article = "some" }; { Name = "bucket"; Article = "a" }];
Garden, [{ Name = "chain"; Article = "a length of" }]]
|> Map.ofList
let msg, p', o' = pickUp player "bucket" objects
// etc.
How can I factor out the explicit state to make it prettier? (Assume I have access to a State monad type if it helps; I know there is sample code for it in F# out there.)
If you want to use the state monad to thread the player's inventory and world state through the pickUp function, here's one approach:
type State<'s,'a> = State of ('s -> 'a * 's)
type StateBuilder<'s>() =
member x.Return v : State<'s,_> = State(fun s -> v,s)
member x.Bind(State v, f) : State<'s,_> =
State(fun s ->
let (a,s) = v s
let (State v') = f a
v' s)
let withState<'s> = StateBuilder<'s>()
let getState = State(fun s -> s,s)
let putState v = State(fun _ -> (),v)
let runState (State f) init = f init
type Location = Room | Garden
type Thing = { Name : string; Article : string }
type Player = { Location : Location; Objects : Thing list }
let pickUp thing =
withState {
let! (player, objects:Map<_,_>) = getState
let objs = objects.[player.Location]
let attempt = objs |> List.partition (fun o -> o.Name = thing)
match attempt with
| [], _ ->
return "You cannot get that."
| thing :: _, things ->
let player' = { player with Objects = thing :: player.Objects }
let objects' = objects.Add(player.Location, things)
let msg = sprintf "You are now carrying %s %s" thing.Article thing.Name
do! putState (player', objects')
return msg
}
let player = { Location = Room; Objects = [] }
let objects =
[Room, [{ Name = "whiskey"; Article = "some" }; { Name = "bucket"; Article = "a" }]
Garden, [{ Name = "chain"; Article = "a length of" }]]
|> Map.ofList
let (msg, (player', objects')) =
(player, objects)
|> runState (pickUp "bucket")
If you want to use mutable state in F#, then the best way is just to write a mutable object. You can declare a mutable Player type like this:
type Player(initial:Location, objects:ResizeArray<Thing>) =
let mutable location = initial
member x.AddThing(obj) =
objects.Add(obj)
member x.Location
with get() = location
and set(v) = location <- v
Using monads to hide mutable state isn't as common in F#. Using monads gives you essentially the same imperative programming model. It hides the passing of state, but it doesn't change the programming model - there is some mutable state that makes it impossible to parallelize the program.
If the example uses mutation, then it is probably because it was designed in an imperative way. You can, change the program architecture to make it more functional. For example, instead of picking the item (and modifying the player), the pickUp function could just return some object representing a request to pick the item. The world would then have some engine that evaluates these requests (collected from all players) and calculates the new state of the world.
How could nested pattern matching, such as the following example, be re-written so that None is specified only once? I think the Maybe monad solves this problem. Is there something similar in the F# core library? Or, is there an alternative approach?
match a with
| Some b ->
let c = b.SomeProperty
match c with
| Some d ->
let e = d.SomeProperty
//and so on...
| None -> ()
| None -> ()
you can solve this using built-in capabilities: Option.bind
type A =
member this.X : B option = Unchecked.defaultof<_>
and B =
member this.Y : С option = Unchecked.defaultof<_>
and С =
member this.Z : string option = Unchecked.defaultof<_>
let a : A = Unchecked.defaultof<_>
let v =
match
a.X
|> Option.bind (fun v -> v.Y)
|> Option.bind (fun v -> v.Z) with
| Some s -> s
| None -> "<none>"
Frankly, I doubt that introducing full-fledged 'maybe' implementation (via computation expressions) here can shorten the code.
EDIT: Dream mode - on
I think that version with Option.bind can be made smaller if F# has more lightweight syntax for the special case: lambda that refer to some member of its argument:
"123" |> fun s -> s.Length // current version
"123" |> #.Length // hypothetical syntax
This is how the sample can be rewritten in Nemerle that already has such capabilities:
using System;
using Nemerle.Utility; // for Accessor macro : generates property for given field
variant Option[T]
{
| Some {value : T}
| None
}
module OptionExtensions
{
public Bind[T, U](this o : Option[T], f : T -> Option[U]) : Option[U]
{
match(o)
{
| Option.Some(value) => f(value)
| Option.None => Option.None()
}
}
}
[Record] // Record macro: checks existing fields and creates constructor for its initialization
class A
{
[Accessor]
value : Option[A];
}
def print(_)
{
// shortened syntax for functions with body -> match over arguments
| Option.Some(_) => Console.WriteLine("value");
| Option.None => Console.WriteLine("none");
}
def x = A(Option.Some(A(Option.Some(A(Option.None())))));
print(x.Value.Bind(_.Value)); // "value"
print(x.Value.Bind(_.Value).Bind(_.Value)); // "none"
I like desco's answer; one should always favor built-in constructs. But FWIW, here's what a workflow version might look like (if I understand the problem correctly):
type CE () =
member this.Bind (v,f) =
match v with
| Some(x) -> f x
| None -> None
member this.Return v = v
type A (p:A option) =
member this.P
with get() = p
let f (aIn:A option) = CE () {
let! a = aIn
let! b = a.P
let! c = b.P
return c.P }
let x = f (Some(A(None)))
let y = f (Some(A(Some(A(Some(A(Some(A(None)))))))))
printfn "Your breakpoint here."
I don't suggest this, but you can also solve it with exception handling:
try
<code that just keeps dotting into option.Value with impunity>
with
| :? System.NullReferenceException -> "None"
I just wanted to point out the rough equivalence of exception-handling to the Maybe/Either monads or Option.bind. Typically prefer one of them to throwing and catching exceptions.
Using Option.maybe from FSharpx:
open FSharpx
type Pet = { Name: string; PreviousOwner: option<string> }
type Person = { Name: string; Pet: option<Pet> }
let pers = { Name = "Bob"; Pet = Some {Name = "Mr Burns"; PreviousOwner = Some "Susan"} }
Option.maybe {
let! pet = pers.Pet
let! prevOwner = pet.PreviousOwner
do printfn "%s was the previous owner of %s." prevOwner pet.Name
}
Output:
Susan was the previous owner of Mr Burns.
But, e.g. with this person instead there is just no output:
let pers = { Name = "Bob"; Pet = None }