Continuing from last time, let’s look at how one goes from imperative pseudocode to pure functional using Gale-Shapely as an example.

Overall, to convert an algorithm from imperative to functional is a fairly simple process once you understand how to convert from a while loop to recursion with accumulators. This post is just a more advanced version of what I talked about in my From Imperative to Computation Expressions post. In fact, if you haven’t already I suggest you read it first. I’ll be assuming you understand how to perform that conversion in this article. If you’re still having trouble, another good article which is a bit easier is The Ted Neward F# Folding Challenge.

The conversion of an algorithm is a two stage process: first identify algorithmic cases, implied mutable data structures, and alternate immutable data structures. Then convert the given loops into recursion while leveraging pattern matching. I know it may seem difficult at first, but after reading this I think you’ll have a good idea of how to get there.

So, let’s identify the cases.

function stableMatching {

Initialize all m ? M and w ? W to free

// Loop Termination Case on

// check for “free m with unproposed w” and

// implicit ranking of W for each m in M

while ? free man m who still has a

woman w to propose to {

w = m’s highest ranked such woman

who he has not proposed to yet

// She’s Single Case

if w is free

(m, w) become engaged

// She’s Engaged Case

else some pair (m’, w) already exists

// She Picks m Sub-Case

if w prefers m to m’

(m, w) become engaged

m’ becomes free

// She Picks m’ Sub-Case

else

(m’, w) remain engaged

}

}



Starting from the top, we can just ignore the imperative initialization. However, you’ll notice that a great deal is going on in that while clause.

First, it defines our loop termination by looking for a “free m who still has a w to propose to”. This is interesting because it seems to be begging for some kind of data structure lookup. Second it defines a implicit ranking of each member of W by each member of M. Note that this is not mentioned in the initialization phase.

The rest of this algorithm reads like a simple list of cases. To make this plain, let’s list them out along with state changes.

no single m in M with unproposed w

— execution terminates m proposes to unengaged w

— w is no longer in m proposals

— m is no longer single

— m and w are engaged m proposes to w engaged to m’

a. She switches to m

— w is no longer in m proposals

— m is no longer single

— m and w are engaged

— m’ is single

b. She stays with m’

— w is no longer in m proposals

— m’ and w stay engaged

— m remains single

The mechanics of what exactly we do in these cases depends on our data structures. So we can’t get much further without figuring that out. To identify what data structures are needed we’ll need to identify what exactly it is that needs to be kept track of. Only once we know that can we pick our immutable data structures appropriately.

Looking above, it appears that we’ll need to keep track of:

Given a woman, is she engaged and, if so, who is she engaged to? (need to look it up) Which men are single? (just need one at a time, order doesn’t matter) For each man, which is his next most desirable match that he has not yet proposed to? (just need one at a time, order matters)

Note that we don’t have to keep track of which men are engaged to which women, the question is never asked. This is a minimal set of requirements here. No extra stuff allowed. I’m serious.

Moving on, in an imperative mutable version you might use these data structures:

An array indexed by w to the m index engaged to (-1 if single) An array indexed by m to the w index engaged to (-1 if single) An array indexed by m where each element is an ordered linked list of unproposed w indices.

However, what would satisfy these same requirements but be immutable? Well, if we step out of the mutation mindset it becomes obvious pretty quickly:

An engagement map of women to men An immutable linked list of single men Define a man to include his index as well as an ordered unproposed woman immutable linked list

Instead of scanning arrays, or querying mutable structures we simply decompose our data structures as much as needed and recompose them into the form we need on the next call. This may sound difficult, but it’s really not. The fact that we have pattern matching and our cases can be defined in terms of the shape of our data structures actually makes this very simple to do. That is, once you get the hang of it.

To see this in action, let’s break up the code in the last article and discuss each chunk.

1: open System 2: 3: // a Bachelor is an identity index and an 4: // ordered list of women indicies to approach. 5: type Bachelor = int * int list

Here we see the definition of a bachelor, it’s a tuple of an integer and a list of integers. This is effectively a pair containing an index and a list of woman indices. Note that in this example all men are of type Bachelor.

7: // Some notation: 8: // wi = woman index (int) 9: // mi = man index (int) 10: // mi' = woman's current partner index (int) 11: // m = man with index and unapproached women indices (Bachelor) 12: // mSingle = men that are single (Bachelor list) 13: // wEngaged = engagements from women to men (int, Bachelor)

We’ll keep this notation around just so you have easy reference.

15: let funGS ( comp : _ -> _ -> float ) ( M : _ array ) ( W : _ array ) = 16: let Windices = [ 0 .. W . Length - 1 ] 17: // List of men with women in order of desire 18: let Munproposed = 19: List . init M . Length 20: ( fun mi -> 21: let sortFun wi = 1.0 - ( comp M . [ mi ] W . [ wi ]) 22: mi , Windices |> List . sortBy sortFun )

Windices is just a list of numbers from 0 to the number of woman tokens. Doing this first makes the calculation of Munproposed less expensive.

Munproposed is our initial set of all single men. Here List.init is used to call the given lambda to generate each element. As you can see by the last line within that lambda, each element of the list is a tuple containing the index of that man and a list of women indices sorted by the given desirability function, sortFun.

23: // Recursively solve stable marriages 24: let rec findMarriages mSingle wEngaged = 25: match mSingle with 26: // No single guys left with desired women, we're done 27: | [] -> wEngaged 28: // Guy is out of luck, remove from singles 29: | ( mi , []) :: bachelors -> findMarriages bachelors wEngaged

These are our first two cases, if the mSingle list pattern matches with [], we know it’s empty and so we are done.

In the second case we are pattern matching on the structure of singles list as well as it’s first element. The syntax (mi, []) matches on a tuple of an index and an empty inner list. This list is the list of yet unproposed to women and so if it’s empty we know that this guy is out of options and so it’s ok to drop him from the list of bachelors. We do this by simply recursing on bachelors, which is the tail of the mSingle list.

30: // He's got options! 31: | ( mi , wi :: rest ) :: bachelors -> 32: let m = mi , rest

This is our general purpose match, and is the structure we expect to see in most cases. Here the head is fully decomposed into a man index (mi), his next first choice woman index (wi), the rest of the women (rest) and the rest of the single men (bachelors).

Immediately afterward we define m to be that given man index (mi) and the rest of the women tokens (rest). As two tokens are only ever compared once, m should no longer contain wi in his ordered list of proposals. This is the form he will take until the algorithm ends or he is evaluated again after being placed in the mSingle pool.

33: match wEngaged |> Map . tryFind wi with 34: // She's single! m is now engaged! 35: | None -> findMarriages bachelors ( wEngaged |> Map . add wi m ) 36: // She's already engaged, let the best man win! 37: | Some ( m' ) – >

38: let mi' , _ = m'

Now that we have fully matched out everything masculine, it’s time to turn to the feminine.

Here we look in the wEngaged map to find out if wi is single. This is done by a using Map.tryFind which returns None if the given key is not in the map and Some(value) if it is.

If the Map.tryFind call returns None we know she is single and so recurse with the rest of our single men (bachelors) and our wEngaged map is extended via Map.add to contain a new mapping from the woman index (wi) to the given man (m).

If she’s engaged our pattern match automatically decomposes the given option type and we know exactly who she is engaged to (m’).

39: if comp W . [ wi ] M . [ mi ] > comp W . [ wi ] M . [ mi' ] then 40: // Congrats mi, he is now engaged to wi 41: // The previous suitor (mi') is bested 42: findMarriages 43: ( m' :: bachelors ) 44: ( wEngaged |> Map . add wi m ) 45: else 46: // The current bachelor (mi) lost, better luck next time 47: findMarriages 48: ( m :: bachelors ) 49: wEngaged

This is the core comparison logic. Here we determine which bachelor gets her hand. This is pretty straightforward from an imperative perspective as we’re using the oh-so familiar if statement.

In the first case the new contender (m) wins. We append the loser (m’) back on to the head of what will be mSingle next time around with the syntax (m’ :: bachelors). Similar to what happens if she’s single, we add a mapping from the woman index (wi) to the man instance (m) via the call to Map.add. Note that this will override the former mapping from wi to m’.

If the current bachelor loses we simply append him back to the head of the bachelors list and try again next time around. You’ll get her next time bro.

50: findMarriages Munproposed Map . empty 51: // Before returning, remove unproposed lists from man instances 52: |> Map . map ( fun wi m -> let mi , _ = m in mi )

The final section is just how we set up our recursion. We start with the full list of single men and their ranked lady lists (Munproposed) and an empty map of relationships (Map.empty). When the algorithm exits we make one last pass through our returned wEngaged map in order to strip out all of the lists of unproposed to women. It’s just baggage now anyway.

Well that’s how you get from imperative pseudocode to a pure functional implementation. All of the code here, as well as a really nasty imperative version to compare it to is available on my GitHub page.

If you liked this post, have any questions, or feel like I missed something important I hope you’ll comment here on it. We’ll all benefit from the knowledge shared.

namespace System



type Bachelor = int * int list Full name: Snippet.Bachelor





val int : ‘T -> int (requires member op_Explicit) Multiple itemsval int : ‘T -> int (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.int ——————– type int<‘Measure> = int Full name: Microsoft.FSharp.Core.int<_> type: int<‘Measure> implements: IComparable implements: IConvertible implements: IFormattable implements: IComparable<int<‘Measure>> implements: IEquatable<int<‘Measure>> inherits: ValueType ——————– type int = int32 Full name: Microsoft.FSharp.Core.int type: int implements: IComparable implements: IFormattable implements: IConvertible implements: IComparable<int> implements: IEquatable<int> inherits: ValueType

type ‘T list = List Full name: Microsoft.FSharp.Collections.list<_> type: ‘T list implements: Collections.IStructuralEquatable implements: IComparable<List<‘T>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<‘T> implements: Collections.IEnumerable

val funGS : (‘a -> ‘a -> float) -> ‘a array -> ‘a array -> Map Full name: Snippet.funGS

val comp : (‘a -> ‘a -> float)







val float : ‘T -> float (requires member op_Explicit) Multiple itemsval float : ‘T -> float (requires member op_Explicit) Full name: Microsoft.FSharp.Core.Operators.float ——————– type float<‘Measure> = float Full name: Microsoft.FSharp.Core.float<_> type: float<‘Measure> implements: IComparable implements: IConvertible implements: IFormattable implements: IComparable<float<‘Measure>> implements: IEquatable<float<‘Measure>> inherits: ValueType ——————– type float = Double Full name: Microsoft.FSharp.Core.float type: float implements: IComparable implements: IFormattable implements: IConvertible implements: IComparable<float> implements: IEquatable<float> inherits: ValueType





val M : ‘a array Multiple itemsval M : ‘a array type: ‘a array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘a> implements: Collections.Generic.ICollection<‘a> implements: seq<‘a> implements: Collections.IEnumerable inherits: Array ——————– val M : ‘a array type: ‘a array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘a> implements: Collections.Generic.ICollection<‘a> implements: seq<‘a> implements: Collections.IEnumerable inherits: Array

type ‘T array = ‘T [] Full name: Microsoft.FSharp.Core.array<_> type: ‘T array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘T> implements: Collections.Generic.ICollection<‘T> implements: seq<‘T> implements: Collections.IEnumerable inherits: Array





val W : ‘a array Multiple itemsval W : ‘a array type: ‘a array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘a> implements: Collections.Generic.ICollection<‘a> implements: seq<‘a> implements: Collections.IEnumerable inherits: Array ——————– val W : ‘a array type: ‘a array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘a> implements: Collections.Generic.ICollection<‘a> implements: seq<‘a> implements: Collections.IEnumerable inherits: Array

val Windices : int list type: int list implements: Collections.IStructuralEquatable implements: IComparable<List<int>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<int> implements: Collections.IEnumerable

val W : ‘a array type: ‘a array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘a> implements: Collections.Generic.ICollection<‘a> implements: seq<‘a> implements: Collections.IEnumerable inherits: Array

property Array.Length: int



val Munproposed : (int * int list) list type: (int * int list) list implements: Collections.IStructuralEquatable implements: IComparable<List<int * int list>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<int * int list> implements: Collections.IEnumerable





module List Multiple itemsmodule List from Microsoft.FSharp.Collections ——————– type List<‘T> = | ( [] ) | ( :: ) of ‘T * ‘T list with interface Collections.IEnumerable interface Collections.Generic.IEnumerable<‘T> member Head : ‘T member IsEmpty : bool member Item : index:int -> ‘T with get member Length : int member Tail : ‘T list static member Cons : head:’T * tail:’T list -> ‘T list static member Empty : ‘T list end Full name: Microsoft.FSharp.Collections.List<_> type: List<‘T> implements: Collections.IStructuralEquatable implements: IComparable<List<‘T>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<‘T> implements: Collections.IEnumerable

val init : int -> (int -> ‘T) -> ‘T list Full name: Microsoft.FSharp.Collections.List.init

val M : ‘a array type: ‘a array implements: ICloneable implements: Collections.IList implements: Collections.ICollection implements: Collections.IStructuralComparable implements: Collections.IStructuralEquatable implements: Collections.Generic.IList<‘a> implements: Collections.Generic.ICollection<‘a> implements: seq<‘a> implements: Collections.IEnumerable inherits: Array

val mi : int type: int implements: IComparable implements: IFormattable implements: IConvertible implements: IComparable<int> implements: IEquatable<int> inherits: ValueType

val sortFun : (int -> float)



val wi : int type: int implements: IComparable implements: IFormattable implements: IConvertible implements: IComparable<int> implements: IEquatable<int> inherits: ValueType

val sortBy : (‘T -> ‘Key) -> ‘T list -> ‘T list (requires comparison) Full name: Microsoft.FSharp.Collections.List.sortBy

val findMarriages : ((int * int list) list -> Map<int,(int * int list)> -> Map<int,(int * int list)>)



val mSingle : (int * int list) list type: (int * int list) list implements: Collections.IStructuralEquatable implements: IComparable<List<int * int list>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<int * int list> implements: Collections.IEnumerable

val wEngaged : Map type: Map<int,(int * int list)> implements: IComparable implements: Collections.Generic.IDictionary<int,(int * int list)> implements: Collections.Generic.ICollection<Collections.Generic.KeyValuePair<int,(int * int list)>> implements: seq<Collections.Generic.KeyValuePair<int,(int * int list)>> implements: Collections.IEnumerable

val bachelors : (int * int list) list type: (int * int list) list implements: Collections.IStructuralEquatable implements: IComparable<List<int * int list>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<int * int list> implements: Collections.IEnumerable

val rest : int list type: int list implements: Collections.IStructuralEquatable implements: IComparable<List<int>> implements: IComparable implements: Collections.IStructuralComparable implements: Collections.Generic.IEnumerable<int> implements: Collections.IEnumerable

val m : int * int list







module Map Multiple itemsmodule Map from Microsoft.FSharp.Collections ——————– type Map<‘Key,’Value (requires comparison)> = class interface Collections.IEnumerable interface IComparable interface Collections.Generic.IEnumerable<Collections.Generic.KeyValuePair<‘Key,’Value>> interface Collections.Generic.ICollection<Collections.Generic.KeyValuePair<‘Key,’Value>> interface Collections.Generic.IDictionary<‘Key,’Value> new : elements:seq<‘Key * ‘Value> -> Map<‘Key,’Value> member Add : key:’Key * value:’Value -> Map<‘Key,’Value> member ContainsKey : key:’Key -> bool override Equals : obj -> bool member Remove : key:’Key -> Map<‘Key,’Value> member TryFind : key:’Key -> ‘Value option member Count : int member IsEmpty : bool member Item : key:’Key -> ‘Value with get end Full name: Microsoft.FSharp.Collections.Map<_,_> type: Map<‘Key,’Value> implements: IComparable implements: Collections.Generic.IDictionary<‘Key,’Value> implements: Collections.Generic.ICollection<Collections.Generic.KeyValuePair<‘Key,’Value>> implements: seq<Collections.Generic.KeyValuePair<‘Key,’Value>> implements: Collections.IEnumerable

val tryFind : ‘Key -> Map -> ‘T option (requires comparison) Full name: Microsoft.FSharp.Collections.Map.tryFind

union case Option.None: Option<‘T>



val add : ‘Key -> ‘T -> Map -> Map (requires comparison) Full name: Microsoft.FSharp.Collections.Map.add

union case Option.Some: ‘T -> Option<‘T>



val m’ : int * int list



val mi’ : int type: int implements: IComparable implements: IFormattable implements: IConvertible implements: IComparable<int> implements: IEquatable<int> inherits: ValueType

val empty : Map (requires comparison) Full name: Microsoft.FSharp.Collections.Map.empty

val map : (‘Key -> ‘T -> ‘U) -> Map -> Map (requires comparison) Full name: Microsoft.FSharp.Collections.Map.map

val alignJaroWinkler : (‘a array -> ‘a array -> Map ) Full name: Snippet.alignJaroWinkler

Enjoy this post? Continue the conversation with me on twitter.

Tags: Abstraction, Algorithms, F#, fsharp, Functional Programming, Gale-Shapely, Immutable Data Structures, Looping, Record Linkage, Recursion