Thursday, March 22, 2018

Functional Adventures in F# - Getting rid of temp variables


This time we will look at how to get rid of temporary variables when updating an object in multiple steps.

This post is part of a series:
Functional Adventures in F# - A simple planner
Functional Adventures in F# - Calling F# from C#
Functional Adventures in F# - Using Map Collections
Functional Adventures in F# - Application State
Functional Adventures in F# - Types with member functions
Functional Adventures in F# - Getting rid of loops
Functional Adventures in F# - Getting rid of temp variables
Functional Adventures in F# - The MailboxProcessor
Functional Adventures in F# - Persisting Application State
Functional Adventures in F# - Adding Snapshot Persisting
Functional Adventures in F# - Type-safe identifiers
Functional Adventures in F# - Event sourcing lessons learned

In my game that I am trying to write with F# (at least the core model) I have some functions that look like the following
static member HandleTick (state:UnitStore) (action:Tick) =     
 let queueUpdatedState = UnitStore.handleTickBuildQueues state action
 let unitsUpdatedState = UnitStore.handleTickUnitUpdates queueUpdatedState action
 unitsUpdatedState
A function that takes a state and an action and in turn it uses other functions to update that state with the help of the action.
I don't know, but I grew tired of naming temporary variables after a while and started googling for a better solution. Turns out there is one. By using the forward-pipe operator '|>' we can take the output of one function and send it as input to the next function.

static member HandleTick (state:UnitStore) (action:Tick) =     
 UnitStore.handleTickBuildQueues action state 
 |> UnitStore.handleTickUnitUpdates action
To make this work, the order of parameters need to be switched so that Action is the first argument followed by the State.
Turns out I have to refactor all the functions in my Stores to take the action first and state as second argument to get this working, but I guess that is a small price to pay for cleaner code.

Looking at the assembly in ILSpy gives us that in the background there will be temporary variables passed around, but our code is clean of them.
public static UnitStore HandleTick(Actions.Tick action, UnitStore state)
{
 UnitStore unitStore = UnitStore.handleTickBuildQueues(action, state);
 UnitStore state2 = unitStore;
 return UnitStore.handleTickUnitUpdates(action, state2);
}

All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment, not required but appreciated! :)

Hope this helps someone out there!

7 comments:

  1. If I remember correctly queueUpdatedState must be the last argument to UnitStore.handleTickUnitUpdates() function for the piping operator to work.
    Also IMHO the refactored version is a bit less readable and now it is impossible to debug the intermediate values.

    ReplyDelete
    Replies
    1. You are right, need to change the order of the arguments for it to work. Think I mentioned it after the second code block.
      When it comes to debugging, I'm all with you usually, readability and maintainability are key concepts for all production code.
      But this time I try to figure out how to use all these nice built in constructs in F#, and in the end we will see what I will end up using. Need to know what's there to get the experience.
      Thanks for taking the time for commenting! :)

      Delete
  2. > I mentioned it after the second code
    Sorry, you're right, I misunderstood that statement.

    For me the pipe operator worked really well in places where I'd write a lot of Linq chained functions, like someEnumerable.Where(...).Select(...).Join(...).Aggregate() etc.

    Thanks for sharing your experience and keep the good work!

    ReplyDelete
    Replies
    1. Added a better statement to clarify things, thanks for the input. : )

      Delete
  3. If you switch the parameters of HandleTick also, you can use the compose operator instead of the pipe operator. e.g. `let handleTick action = UnitStore.handleTickBuildQueues action >> UnitStore.handleTickUnitUpdates action`

    ReplyDelete
    Replies
    1. Then you can:
      let handleTick = UnitStore.handleTickBuildQueues >> UnitStore.handleTickUnitUpdates

      Delete
    2. That sounds like a neat trick, didn't know about the compose operator!
      Thanks for taking the time to comment! :)

      Delete