Wednesday, March 21, 2018

Functional Adventures in F# - Getting rid of loops


Solving problems without loops.....

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

This time lets look at a real issue when coming from an imperative language and trying functional programming.
The example problem (that I crashed into when rewriting some game logic in F#) is that of spawning new Ships in a space game. We do not want to spawn 2 ships on top of each other, so we generate the default spawning coordinate and then test if it is safe to use, if not we move the ship a bit and try again....

How I solved it in C#..
private Ship FindClosestFreeCoordinate(ImmutableList<Fleet> state, Ship newShip)
{
    var shipsWithinSafetyDistance = ShipsWithinSafetyDistance(state, newShip);
    while (shipsWithinSafetyDistance.Any())
    {
        var newCoordinates = newShip.Coordinates.Position.Add(Vector3.UnitXVector);
        newShip = newShip.Modify(coordinates: newShip.Coordinates.Modify(position: newCoordinates));
        shipsWithinSafetyDistance = ShipsWithinSafetyDistance(state, newShip);
    }
    return newShip;
}

private List<Ship> ShipsWithinSafetyDistance(ImmutableList<Fleet> state, Ship newShip)
{
    return (from fleet in state
        from
            ship in fleet.Ships
        where
            ship.Distance(newShip) < 5
        select ship).ToList();
}

Translating the ShipsWithinSafetyDistance function is no problem. We just use the Map.filter function...
static member private shipsWithinSafetyDistance (unit:Unit) (state:UnitStore) =
    Map.filter (fun key value -> unit.distanceToUnit value < 5.0) state.Units
Ah, yes.. I switched from a ImmutableList in C# to a Map in F#, just to keep things interesting (and to get faster Key lookups)


So, the tricky part here is the while loop. Most loops can be rewritten with Map/List functions like map, filter and fold etc... But this time I don't see how to use them as I don't have a Collection of any sort here so my brain keeps yelling "Write a loooooop", as that is the way I have solved this kind of problems for the past 20+ years... So...
How should we do it in F#??? We have some other constructs to use that fit the functional idea better.

static member private findClosestFreeCoordinate (unit:Unit) (state:UnitStore) =
 let rec findClosestFreeCoordinateRecursive (unit:Unit) (state:UnitStore) = 
  let shipsWithinSafetyDistance = shipsWithinSafetyDistance unit state
  match shipsWithinSafetyDistance with
  | a when a.IsEmpty -> unit
  | _ ->  let newUnit = { unit with Coordinates = unit.Coordinates + Vector3.UnitX }
    findClosestFreeCoordinateRecursive newUnit state
 findClosestFreeCoordinateRecursive unit state
Recursion!
At least for this problem, it seems to be a good option. Here we define a function, findClosestFreeCoordinateRecursive with the rec keyword to let the compiler know that it will be called recursively. In the function body we match the result list with IsEmpty and return the input value or else call the recursive function with a new copy of the unit with new coordinates.

Call stack.. Is there a risk for stack overflow with this?
Lets open the dll output with ILSpy to check what actually goes on behind the scenes
ILSpy of F# Recursive Function
Turns out, that the current F# compiler in the background uses a while loop. The key here is that we do not, instead we define a short and concise function where we state what it is that we want done, how it is solved behind the scene in detail is not up to us. At some point, there is always someone that needs to write the ifs and loops, but lets keep them away from the application code that we write.

So there, lets move the last shipsWithinSafetyDistance to inner scope of our function and we are set
static member private findClosestFreeCoordinate (unit:Unit) (state:UnitStore) =
 let shipsWithinSafetyDistance (unit:Unit) (state:UnitStore) =
  Map.filter (fun _ value -> unit.distanceToUnit value < 50.0) state.Units
 let rec findClosestFreeCoordinateRecursive (unit:Unit) (state:UnitStore) = 
  let shipsWithinSafetyDistance = shipsWithinSafetyDistance unit state
  match shipsWithinSafetyDistance with
  | a when a.IsEmpty -> unit
  | _ ->  let newUnit = { unit with Coordinates = unit.Coordinates + Vector3.UnitX }
    findClosestFreeCoordinateRecursive newUnit state
 findClosestFreeCoordinateRecursive unit state

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!


1 comment: