Skip to content

Enumoji implementation in C# 14.0#2

Open
jbtule wants to merge 5 commits into
khalidabuhakmeh:masterfrom
jbtule:master
Open

Enumoji implementation in C# 14.0#2
jbtule wants to merge 5 commits into
khalidabuhakmeh:masterfrom
jbtule:master

Conversation

@jbtule

@jbtule jbtule commented Sep 10, 2020

Copy link
Copy Markdown
  • Lighter weight syntax reduces LOC by half with identical calls. (direct port)
  • Enumojis
  • Switched to blog emojis (compound 🧑‍🚀 emoji in master wasn't overwriting properly on Mac OS X terminal)

Comment thread GameOfLife/Program.fs Outdated

@jbtule jbtule left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example wasn't ported functionally, just syntactically from C#. It about half the lines of code of the original C#. I've annotated this PR to clarify the differences in F#. For further information how how F# is actually a great general purpose programing language, and that it expresses functional, imperative and object oriented code more tersely that C# check out this slideshow: C# Light

Comment thread GameOfLife/Program.fs
let timeout = 500
let mutable runSimulation = true

type Status = ``💀`` = 0 | ``😁`` = 1

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Enumoji's we don't have to use conditional expressions for the 3 data types needed for two states Alive|Dead in this port. We can then use the F# operators string, int, and enum to convert to what we need.

Comment thread GameOfLife/Program.fs
Comment on lines +7 to +9
let rows = 15
let columns = 15
let timeout = 500

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can infer types on our statically scoped variables!

Comment thread GameOfLife/Program.fs
let rows = 15
let columns = 15
let timeout = 500
let mutable runSimulation = true

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F# requires you to qualify any variable you want to mutate. You save so much boiler plate with type inference, adding this extra 6 let word hardly seems like a thing, and makes it easier to reason about variables. Notice we only have to use the keyword 3 times in this program ported directly from C#.

Comment thread GameOfLife/Program.fs

type Status = ``💀`` = 0 | ``😁`` = 1

let private nextGeneration (currentGrid: Status [,]) =

@jbtule jbtule Sep 12, 2020

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although there is a recursive modifier (rec) that can be applied to a module or namespace, typically you declare your functions before you use them in F#. So nextGeneration helper function was moved to the top of the file in this port.

Comment thread GameOfLife/Program.fs
@@ -0,0 +1,67 @@
module GameOfLife.Program

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although you can do C# style static classes in F# with some boilerplate. A module is the preferred vehicle for static functions. CLI wise it will look like a static class in C#.

Comment thread GameOfLife/Program.fs
| Status.``💀`` when aliveNeighbors = 3 -> Status.``😁``
// stays the same
| unchanged -> unchanged
nextGeneration

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In F# the last line in an expression is the returned value. No return keyword necessary, but that also means no easy short circuiting.

Comment thread GameOfLife/Program.fs
let sb = StringBuilder()
for row in 0..(rows-1) do
for column in 0..(columns-1) do
future.[row, column] |> string |> sb.Append |> ignore

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pipe operator |> makes it easy to describe data flowing from one function to another. Otherwise it would have been written with nested parentheses:

ignore(sb.Append(string(future.[row, column])))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The string operator calls ToString() on an object. And the ignore operator is how you discard return values that you are not going to use (Like Append returns a reference to the string builder it's mutating. You'll get a warning if you have an expression that returns a value that isn't unit and it's not the last line of it's enclosing expression.

Comment thread GameOfLife/Program.fs
[<EntryPoint>]
let main _ =
// randomly initialize our grid
let mutable grid = Array2D.init rows columns (fun _ _ -> RandomNumberGenerator.GetInt32(0, 2) |> enum)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F# array creation functions let you pass a lambda function to be used in initialization. Lambda's are prefixed with fun in F#, it's fun! Since we are randomly filling out the array, I discard the row and column arguments in the lambda. We can use the enum operator which is actually able to infer the enum type statically in this case!!

Comment thread GameOfLife/Program.fs
Comment on lines +56 to +59
Console.CancelKeyPress.Add(
fun _ ->
runSimulation <- false
Console.WriteLine("\n👋 Ending simulation."))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I read somewhere that C# designers wished they never special cased += and -= operators to be AddHandlerand RemoveHandler methods. So those methods are what you can use in F# instead of the operators. To make things simpler without explicitly creating delegates, you can use the Add method with a lambda, as long as you don't need to remove the handler later.

Comment thread GameOfLife/Program.fs
while runSimulation do
print grid
grid <- nextGeneration(grid)
0

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Main function is expected to return an integer. So last line is a zero.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant