Bit stumped as to why this code compiles but doesn't work

I have just started picking up F# as part of a personal project to implement Conways Game of Life in many different languages, and I’ve run into an issue when trying to set a value on a Dictionary within a class.

module World

open System.Collections.Generic

type World() =
  member private this.cells = new Dictionary<string, int>()
  
  member this.add_cell(): int =
    printfn "%A" this.cells
    this.cells.Add("0-0", 123)
    printfn "%A" this.cells
    this.cells["0-0"]

let world = new World()
world.add_cell() |> ignore

This gives me the following output:

seq []
seq []
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key '0-0' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at World.World.add_cell()

As you can see, there are some weird things happening.

  1. The this.cells, which should be a dictionary, is outputting as a seq ?
  2. Immediately after adding a value to the dictionary, the output of this.cells is still blank
  3. When trying to get the dictionary value using a key I just added, it does a runtime exception

Can anyone help me understand why this is the case? What am I missing here?

Thanks.

1 Like

That is because this.cells is a member. Thus every time you call this.cells, it returns a new Dictionary, and thus you cannot persist the items you add.
If you use a private variable, it should work:

type World() =
  let cells = new Dictionary<string, int>()
  
  member this.add_cell(): int =
    printfn "%A" cells
    cells.Add("0-0", 123)
    printfn "%A" cells
    cells["0-0"]

let world = new World()
world.add_cell() |> ignore

In F#, all class members are re-evaluated every time they are called. So your cells member is returning a new dictionary each time it is accessed. It’s quite a common for newcomers to find this behaviour to be unexpected.

To create a single private value you can a plain let inside the class. Here’s an example where I’ve also updated the casing and indentation to be idiomatic for F#:

open System.Collections.Generic

type World() =
    let cells = new Dictionary<string, int>()
  
    member _.AddCell(): int =
        printfn "%A" cells
        cells.Add("0-0", 123)
        printfn "%A" cells
        cells["0-0"]

let world = new World()
world.AddCell() |> ignore

// seq []
// seq [[0-0, 123]]

PS: Welcome to the forums :wave:t5::blush:

2 Likes

Welcome to the forums from me too.

This is a bit ‘off-topic’, and I don’t know how you will be using your code, but trying to implement a class-based workflow in F# can give you a ‘bad taste in the mouth’ when it comes to reviewing the F# code in relation to other languages (if that’s what you are doing).

You can often end up ‘jumping through hoops’ to get what you want, rather than re-working the workflow to utilise the benefits which the language can give you, ending up with something way more complicated than it needs to be.

I’ve written a more F#-idiomatic version of your code which, to me (with some limited F# experience, I’m still very much in the learning phase), is easier to read and understand:

type World = { Cells : Map<string,int> } 

let addCell key value (world : World) = 
    { world with Cells = world.Cells |> Map.add key value }

The type World defines a record type, rather than a class, containing Cells (which is an immutable map – kind of a dictionary but different – with a string key and int value), instances of which will always remain the same (immutable) and therefore can be totally trusted to always have the same value as they had when they were created.

The addCell function takes a World instance and creates a new World instance which is the same as the original but with the new cell added to it giving a new (again, immutable) World as output.

You could use the code like this:

let startWorld = { Cells = Map.empty } 

let newWorld = startWorld |> addCell "0-0" 123 

let anotherWorld = newWorld |> addCell "0-1" 234 

let oneTwoThree = anotherWorld.Cells["0-0"] 

// val startWorld: World = { Cells = map [] }
// val newWorld: World = { Cells = map [("0-0", 123)] }
// val anotherWorld: World = { Cells = map [("0-0", 123); ("0-1", 234)] }    
// val oneTwoThree: int = 123

Hopefully you can see how much more ‘clean’ the code is by using an immutable type rather than a class.

1 Like

Hey Prash. Thank you for your clear explanation. The fact that class members re-evaluate each time they are called was surprising. I’ve not yet come across another language that does that.

With this change and use of member val ... with get,set, I was able to complete my F# implementation, which you’re welcome to view here: https://github.com/KieranP/Game-Of-Life-Implementations/tree/master/f%23

I understand the casing and indentation are not idiomatic, but I’ve intentionally done that to be consistent with the other implementations I’ve done for comparison (unless a language enforces it to be different). Besides those things, I’d be keen to hear any thoughts on some small syntax things.

e.g. is this the best/fastest way to append to a List?

cell.neighbours <- neighbour.Value :: cell.neighbours

or is that the best/fastest way to increment an integer?

alive_neighbours <- alive_neighbours + 1

Hey Garry. I complete agree that using a functional language to implement OO-like structure is not the best approach, and if I were making just a F# program that needed immutability in order to prevent data race issues, I would go about it completely differently than I have, utilising patterns like your example.

But in this case, the motivation is to have implementations of the same code be as identical as possible for comparison. You’re welcome to checkout the final result here: https://github.com/KieranP/Game-Of-Life-Implementations/tree/master/f%23

Using :: is the fastest and ultimately the only way to add items to a list. Any other method you use will end up appending in this way once or more because it’s fundamental to the data structure. It’s a bit unusual to assign an immutable list to a mutable value, but there might be reasons for doing so. You might consider just using an mutable data structure like ResizeArray.

Yep.

1 Like

It is indeed surprising, though C# kinda does that too, it just uses => instead of =. The F# program you posted would translate to this C# program:

using System;
using System.Linq;
using System.Collections.Generic;

var world = new World();
world.add_cell();

class World {
  private Dictionary<string, int> cells => new();
  
  public int add_cell() {
    Console.WriteLine($"[{String.Join(";", this.cells.Select(a => $"{a.Key}: {a.Value}"))}]");
    this.cells.Add("0-0", 123);
    Console.WriteLine($"[{String.Join(";", this.cells.Select(a => $"{a.Key}: {a.Value}"))}]");
    return this.cells["0-0"];
  }
}

which outputs

[]
[]
Unhandled exception. System.Collections.Generic.KeyNotFoundException: The given key '0-0' was not present in the dictionary.
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at World.add_cell() in C:\Users\nathan.wilson\workspace\fs-discourse-help\CsPropReevaluate\Program.cs:line 16
   at Program.<Main>$(String[] args) in C:\Users\nathan.wilson\workspace\fs-discourse-help\CsPropReevaluate\Program.cs:line 7
1 Like