Coaching Engineers – A Review

One of my regular responsibilities at my new job at the Credit Union is coaching developers, engineers, sdets and QA folks. Today, I got to be involved in three different coaching sessions that all had unique subjects and discussion points.

Session 1: How to get to Senior – Developing Expertise

This is a fairly common situation. A developer wants to go from Software Developer to Senior Software Developer.

The process of making Senior Software Developer generally comes down to adding more responsibility and influence to your day-to-day job. To get to a senior role, you can do one of the following:

  1. Take on a team lead role. In this case, you are the point person and responsible for more of the project work itself. You T-shape your skill set, but become the primary point person for the whole project.
  2. Take on a manager role. In this case, you’re trying to mentor and grow the skills of the folks around you. You may not be directly responsible to all the functions in the project, but you help and mentor those folks around you.
  3. Take on an expert role. In this case, you target getting deeply technical and specialized. Your plan is to become a known leader and expert on a particular technology.

The developer in question was interested in learning more about this third pattern of developing her expertise, and what it would take to continue that progression. She expressed interest in web user interfaces with Angular, and spent the session showing me what she had learned and worked on, and where she was going next.

To coach, sometimes you just need to be the accountability buddy.

Session 2 : Whose Design is Right?

In this session, a team of software developers had some questions about the nature of their solutions. They did not agree about the approach to a problem, and this particular session was with one side of that argument.

Side note: I love these sorts of discussions. Folks getting passionate about the way they choose to solve a problem is WONDERFUL.

The best part is that there was not a clear winner in the design of the application itself. They were different designs, to be sure, but they each had technical merits that could very easily be seen.

At the core, this one came down to coaching back to the engineering. The crux of the problem was that there was no data proving one solution better than another. The quantitative features of the respected solutions had not yet been tested, and that was the end state I coached towards here.

If your design is better, prove it with data. Otherwise, GTFO of the way.

Session 3 : SDETs in the Credit Union

Initially, this one was setup to be a discussion about how to write code to use a Windows Application automation tool (Selenium with WinAppDriver), but after the first session, it was apparent many of the SDETs present already had a lot of experience with those libraries. There were four SDETs and one analyst in this session, so it became a larger discussion about the nature of testing. We started collaborating on ideas about the about the best ways we could automate some of the harder tests to deal with.

Finally, it came down to discussions about the AAA pattern of testing, the kind of test code we wanted across the org, and even some of the difficulties in teams where SDEs and SDETs have a combative relationship.


Coaching engineers is exhausting and inspiring all at once. It was a great day, and I feel blessed to be able to do it!

Protests

This morning, like most of the rest of the US population, I saw protests against police brutality in our cities. Most protests have been nothing but peaceful displays of solidarity. Some, less so, with police responding to property destruction and graffiti with violence, including pepper spraying an eight year old.

I am a pacifist; I do not believe in the use of force, in any case.

Recent news has shown, in clear and not uncertain terms, that being a white male shows that I am not a target. People of color do not enjoy that privilege. It is easy to be a pacifist when systemic racism does not target me.

Statistics back me up here. I am unlikely to be arrested, injured or killed by a police officer. I don’t need to send messages to my friends and family when I have been pulled over by a police officer, as that police officer is unlikely to believe me to be ‘aggressive.’ **

** See the book So You Want to Talk About Race, by Ijeoma Oluo, for details here. Also, it’s just a fascinating book, you should buy it.


In general, I trust police officers to keep us safe. I fully accept that my privileged position supports that trust. That said, I want all people; people of color, LGBTQ people, differently-abled, and any marginalized group I’m (as of yet) unaware of to feel the same.

The police should make people feel safe.

Everyone’s life should matter. However, saying #alllivesmatter’ is fundamentally ignoring systemic racism. Use of force by police statistically impacts people-of-color drastically differently than it impacts white people.


So, being unable to protest myself in the time of COVID19 (I am high risk, heart condition), I will say emphatically here: Black Lives Matter.

Two Rules to Estimating Software Features

OK, you’re a software developer, and someone’s asked you to quick look at a feature, and give them an estimate on how long it’s going to take to develop. For this example, I will refer to that feature as ‘SuperFeature’, and we will have two estimating developer examples; Gina, who examples good estimates, and Lisa, who examples less good estimates. Priya is our product owner, and since Priya owns the product, good estimates give her the information required to make a intelligent decisions. Well-formed estimates enable her to prioritize work well, and manage expectations of customers and stakeholders.

Here’s two rules to estimating features successfully.

Rule 1: Your estimate should represent doing the ‘work done in a vacuum.’

It is counter-intuitive to estimate work in this way, but creating an estimate, as if that work is being done in a vacuum is the best way for your product owner to assign the feature a priority.

Example:

Gina, who estimates in a vacuum – “SuperFeature will take about 2 weeks to do.”

Lisa, who estimates based off of her current workload – “I will need 6-8 weeks to do this.”

In the first case, Priya knows how long the feature will take to develop. She knows that Gina and Lisa are both working on very high priority items, so she gets Liam to work on that feature.

In the second case, the Priya knows how long Lisa will take to get it done, but has very little awareness of the what priority Lisa is putting on the work. At next week’s stand-up, imagine Priya’s surprise to know that Lisa hasn’t even started work on it yet!

The lesson: Your product owner owns priority of the features. An estimate should give your product owner the information required to set that priority.

Rule 2: The larger an estimate, the more detail it needs.

If your feature is large, your product owner needs to know and understand why. In order to understand the work needing to be done, it should be broken down into tasks.

Example:

Lisa, who doesn’t break the work down – “SuperFeature will take about 4 months to work on.”

Gina, who realizes the work is complicated, and Priya needs to understand the details. – “In all, SuperFeature will take about 4 months. We’ll need 3 days to start building the catalytic converter, and then a week to refit and install the Whizzbang…” etc, etc.

In the first case, it’s hard to really even start the work, or even know how to break it up. Is it 4 months altogether? Can you break the work apart? Can you create multiple work streams?

In the second, Gina gives Priya all the details she needs to break up the work accordingly. She also does so succinctly, so that ordering tasks and dependencies are clear.


Following the two rules above will help make your estimates more valuable and your relationship with your product owner more beneficial.

5 months, abridged

Quite a bit has changed since my end of December post. The first two months of 2020 were a blur. {Redacted} went into a full change-over, a new CTO, and a new org structure early in the year, just as I had received word from a regional credit union that they wanted me to be their new, and only, Principal Software Engineer. I had to make a excruciating decision; to leave a company and people I loved, to move over to a new-idea-to-the-org position. Instead of building things and fixing things, I would be responsible for fixing the org’s developers and development processes.

One of the things I will take away is {Redacted}’s dedication to making the customer right, if there was a mistake or error.

Nothing is more freeing than the knowledge that, no matter the mistake, we will make the client right.

Knowing that we would always make the client whole allowed for experimentation and mistakes. Engineers are free there to do the best they can. There are many orgs who strive to be the very thing that {Redacted} has been.

Still, careers should not stagnate, and there was no place for me to go at {Redacted}. I had reached a space that the only place to go was if my superior retired or quit.

It has been three and a half months since I left, and I still miss everyone terribly.


Well, with the whirlwind of changes that occurred in February, naturally, a pandemic ensued which caused me to get a whopping three weeks of face-time with my new team. There are some outstanding people at my credit union, and with this past three months, I have been working to carve out my new-to-the-org role.

Some notes from the first

  1. Larger organizations have larger org problems. Each subteam has its’ own micro-climate.
  2. Empathy, empathy, empathy. You simply have to assume everyone is trying their best.
  3. TDD is nowhere near as ubiquitous as I assumed that it was.

On item three, I have been a TDD practitioner in spurts and starts since 2005, and I was later than I should have been to the game. It has simply been a part of a majority of the code I have dealt with. When folks describe it as new, it is a bit of a shock.


The elephant in the room clearly has to be the same thing everyone is dealing with. The big CV19. Here’s the high points.

  • My kids have been homeschooling since March.
  • My wife was furloughed from the Y, but still teaches the occasional ZOOM yoga course. We are blessed, in that the income from my new job covers what she was bringing in.
  • I do Crossfit at home, either on ZOOM, or simply by myself, and work from home completely. My outings are walking the dog, or going to the grocery store. I have visited a few wineries for club pickups, and recommend everyone go visit a local winery and support small business!

Stay safe!

Sisters

“I believe that children are our future. Teach them well, and let them lead the way…” – Whitney Houston

After an evening of library time, Daddy’s Crossfit, and a Lacrosse pickup, my two girls, Zoe (the oldest) and Lydia were tired and hungry. During a quick evening meal of leftover lasagna, I popped open the laptop to work on a quick kata, when my oldest asked “Whatcha doin’?”

Zoe’s interest in my work is rare, so I happily showed her some of my F# test code. She looked around. Even asked a few questions.

So I popped open the FSI and showed her how stuff works.

Microsoft (R) F# Interactive version 10.2.3 for F# 4.5
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> let zoe = "awesome";;
val zoe : string = "awesome"

> zoe = "not awesome";;
val it : bool = false

>

She was pretty happy that Zoe != “not awesome”.

I gave her her own FSI to try out, and I happily present to you, the very first code in what I’m sure is a very long lucrative functional programming career.

Microsoft (R) F# Interactive version 10.2.3 for F# 4.5
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> let lydia = "annoying";;
val lydia : string = "annoying"

>

I guess sisters will be sisters.

What do you mean it has to be done in a half hour?

Sometimes, you can see problems coming.

A vendor of ours provides security information monthly in a series of text files. Typically, those text files are in the 10s of megabytes large. Those files get viewedย  cleaned up, and imported into our system by portfolio managers monthly.

The system to do the loading is homegrown, not ETL based (someone thought users should ‘upload’ the files) into an ASP.NET MVC app, and then render UIs with the data. :eyeroll:

Naturally, as our data requirements got larger the files got bigger. And bigger.

This last month, the system eventually exploded. A 1 GB text file proved to be too much for our cute little “not-quite-an-ETL” tool. The portfolio managers needed a solution, and in order to quickly get ’em one, I present (with relevant bits redacted) a 30 minute “lets play with F#” script to solve the problem.


#r "FSharp.Data.3.0.0\\lib\\net45\\Fsharp.Data.dll"
open System.IO;
open FSharp.Data
type BigOleFile = CsvProvider<"C:\\working\\sample.txt", "\t">
let writeData filePath stringLines =
try
File.WriteAllLines (filePath , Array.ofList stringLines)
Ok (List.length stringLines)
with
| e -> Error e.Message
let loadFile filePath =
// Shouldn't these be results to?
let folderPath = Path.GetDirectoryName filePath
let fileName = Path.GetFileName filePath
let getNewFileName v =
sprintf "%s%c%s_%s" folderPath Path.DirectorySeparatorChar v fileName
let toUpper c =
(char ((string c).ToUpper () ))
let inRange c1 c2 v =
v >= c1 && v <= c2
let groupByFirstCharacterOfIssueColumnName (row:BigOleFile.Row) =
match toUpper row.ColumnToGroupBy.[0] with
| x when inRange 'A' 'B' x -> "A-B"
| x when inRange 'C' 'D' x -> "C-D"
| x when inRange 'E' 'F' x -> "E-F"
| 'G' -> "G"
| x when inRange 'H' 'K' x -> "H-K"
| 'L' -> "L"
| _ -> "M-Z"
let stringifyLine (line:BigOleFile.Row) =
sprintf "%s" "redacted" /// blah blah redacted
let fileHeader = "" // redacted
BigOleFile.Load(filePath).Rows
|> Seq.groupBy groupByFirstCharacterOfIssueColumnName
|> Seq.iter (fun (groupByKey, groupedRows) ->
let newFileName = getNewFileName groupByKey
let res = writeData newFileName (fileHeader :: ((groupedRows |> Seq.map stringifyLine) |> List.ofSeq))
match res with
| Ok m -> printfn "Wrote file '%s' with '%d' rows" newFileName m
| Error x -> printfn "Error writing file '%s'. Error text: %s" newFileName x
)

Nothing too fancy. The function simply takes a file path, creates a couple of functions to help it along, and then loads up the file and splits it up into distinct new files. Whole thing took 30 minutes to do. Yes, the complexity is O(n-squared), but when you’ve got panicked users, and all of a half hour to hit it, getting it ‘working’ first is the best way to go.

Sleeves

Yesterday afternoon, I had a bit of time on my hands, and a report that someone desperately needed redone. The original author was a support engineer who was trying to learn Ruby and didn’t realize that Ruby hash-maps are case sensitive. In the interest of time, I corrected the issue by downcasing all the keys, and reran the script (which ran successfully, but inelegantly), but I looked at the problem, and once again, found a perfectly lovely use case for the language I love so much, F#.

First off, some of the basics.

open System

// these may not be strictly necessary, but they do help describe
// precisely what data I'm dealing with
module UpperCaseString = 
    type UpperCaseString = UpperCaseString of string
    let private upper (a:string) =
        a.ToUpper()

    let create (a:string) = 
        if String.IsNullOrEmpty(a) then None
        else Some (UpperCaseString (upper a))
    let value (UpperCaseString s) =
        s
open UpperCaseString

module FloatingDecimalBetweenZeroAndOne = 
    type FloatingDecimalBetweenZeroAndOne = FloatingDecimalBetweenZeroAndOne of decimal

    let create (a:decimal) = 
        if a > Decimal.One || a < Decimal.Zero then              None          else              Some (FloatingDecimalBetweenZeroAndOne a)          let value (FloatingDecimalBetweenZeroAndOne f) =               f     let make a = (create a |> Option.get)    
   
open FloatingDecimalBetweenZeroAndOne 

Generally, I’ll do this for most business application coding nowadays. Creating a simple type that encapsulates correctness from the get-go (including basic validation) makes it so I don’t have to try to figure out weird results. I know the sorts of things I’m dealing with, and I deal with them from the beginning. Also, given the fundamental problem of the original script I had (the case sensitive hash-maps), ensuring I had a type that enforced upper-casing seemed worthwhile.

Next, the domain.

type Model = Model of UpperCaseString
    with static member Value (Model (UpperCaseString s)) = s
type Ticker = Ticker of UpperCaseString
    with static member Value (Ticker (UpperCaseString s)) = s

type Holding = { Ticker : Ticker; Allocation : FloatingDecimalBetweenZeroAndOne }
type ModelAllocations = { Model : Model; Holdings : Holding list }
type ModelWeight = { Model : Model; Weight : FloatingDecimalBetweenZeroAndOne }

type SleevedHolding = { Ticker : Ticker; Model : Model; Allocation : FloatingDecimalBetweenZeroAndOne } 
    with override x.ToString () = sprintf "%s,%s,%5M" (Ticker.Value x.Ticker) (Model.Value x.Model) (FloatingDecimalBetweenZeroAndOne.value x.Allocation) 


// This is the simplest description of what we're doing.
// taking a list of holdings, a list of models and their holdings
// a list of weights to apply to the model, and returning a set of 'sleeved' holdings.
type SleeveProcess = Holding list -> ModelAllocations list -> ModelWeight list -> SleevedHolding list

The problem the original script was attempting to solve was to assign an appropriate ‘Model’ to a holding. A Model is, for sake of brevity, simply an identity (representing the “name” of a standard financial benchmark (like the S&P 500), and a Ticker an identity representing a stock ticker.

A Holding represents an individual stock in a of a portfolio, with the Allocation representing the percentage of the portfolio. ModelAllocations represent a model, and the list of holdings it has (what stocks are in the S&P 500, etc.) ModelWeight represents the approximate weight a portfolio is associated to a given model or benchmark.

E.G. S&P 500 -> 50% , and Russell 3000 -> 50%.

let makeModel str =
    UpperCaseString.create str |> Option.get |> Model  

let makeTicker str =
    UpperCaseString.create str |> Option.get |> Ticker

let holding str all =
    { Ticker = makeTicker str; Allocation = make all }

let makeSleeve t m a = { Ticker = t; Model = m; Allocation = a }
let modelWeight str a = { Model = makeModel str; Weight = make a }
let modelAllocation str h = { Model = makeModel str; Holdings = h }

One of the problems of heavy use of custom types is that you’ll typically want ‘shorthand’ functions for creating the types as necessary. It can be a pain, but difficult to explain results after the fact is worse.

Finally, the meat of it.

let sleeveHoldings holdings models weights =
    let findHolding t = 
        models |> 
             List.collect (fun n -> n.Holdings 
                                        |> List.filter (fun n -> n.Ticker = t) 
                                        |> List.map (fun x -> (n.Model, x.Allocation)))
    let sleeve h = 
        let t = h.Ticker
        let m = findHolding t // a list of models that hold the security
        match m with 
        | [] -> 
            weights |> 
                List.map (fun w -> 
                    let weightTimesAllocation = (value w.Weight * value h.Allocation)
                    makeSleeve t w.Model (make weightTimesAllocation))
        | [(x,_)] -> [makeSleeve t x h.Allocation]
        | xs -> 
            let weightsMap = weights |> List.map (fun n -> (n.Model, value n.Weight)) |> Map.ofList
            let total = xs |> List.sumBy (fun (m, w) -> weightsMap.[m] * value (w))
            xs |> List.map (fun (m, w) -> 
                                let weightTimesAllocationOverTotal = (value w * weightsMap.[m]) / total
                                makeSleeve t m (make (weightTimesAllocationOverTotal * value h.Allocation)))
        

    holdings |> List.collect sleeve

First, we define a function to find the models with a given holding, and return that model and the allocation. Then we define a function to sleeve an individual holding. Finally, we call that function over the list of holdings passed in.

A simple set of tests:

let testHoldings = [
        holding  "A" 0.20m;
        holding  "B" 0.20m;
        holding  "C" 0.20m;
        holding  "D" 0.20m; 
        holding  "E" 0.20m; ]
        
let testWeights =  [
        modelWeight "MODEL1" 0.5m; 
        modelWeight "MODEL2" 0.3m;
        modelWeight "MODEL3" 0.2m;
    ]

let testModelAllocations = [
    modelAllocation "MODEL1" [
            holding  "A" 1.0m;
    ];
    modelAllocation "MODEL2" [
            holding  "A" 0.5m;
            holding  "B" 0.3m;
            holding  "D" 0.2m;
    ];
    modelAllocation "MODEL3" [
            holding  "B" 0.5m;
            holding  "C" 0.3m;
            holding  "D" 0.2m;
    ]
]

sleeveHoldings testHoldings testModelAllocations testWeights |> List.map (fun m -> m.ToString ())

val it : string list =
  ["A,MODEL1,0.1538461538461538461538461538";
   "A,MODEL2,0.0461538461538461538461538462";
   "B,MODEL2,0.0947368421052631578947368421";
   "B,MODEL3,0.1052631578947368421052631579"; "C,MODEL3, 0.20";
   "D,MODEL2,0.120"; "D,MODEL3,0.080"; "E,MODEL1,0.100"; "E,MODEL2,0.060";
   "E,MODEL3,0.040"]

At the financial services firm I work for, this concept of attributing holdings is called ‘sleeving.’

A Fishful of Dollars

First of all, allow me to apologize for a solid month of failed updates. The world of coaching my son’s football team and bugbashing caused me to want to spend my time at home, asleep, rather than writing about my various misadventures in F#.

To update on the application rewrite, upcoming tax legislation has my firm in quite a tizzy, and is requiring an all-hands on deck approach for the next few weeks. Assuming the bill passes, we may be changing a lot of software, quite quickly, and absolutely none of that has anything to do with an old poorly-written report. The upcoming business need will be drastic, and preparing for that change is important to do.

That said, let’s play around a bit in F#!

I was watching an old episode of Futurama, when a question popped into my mind. If Fry left a bank account with $0.93 in it, at 2.25% interest, would he actually be sitting on a cool $4.3 billion after being frozen for 1000 years? Time to find out!


type Compounding =
| TimesPerYear of float
| Constant
type InterestCalculationOptions = {
Compounding : Compounding
Rate : float
Principal : float
TermInYears : float
}
let compound options =
let core r e =
options.Principal * (r ** e)
match options.Compounding with
| Constant -> core System.Math.E (options.Rate * options.TermInYears)
| TimesPerYear f -> let rate = options.Rate / f
core (1.0 + rate) (f * options.TermInYears)
let fry = compound { Compounding = TimesPerYear 1.0
Rate = 0.0225
Principal = 0.93
TermInYears = 1000.0 }

view raw

interest.fsx

hosted with ❤ by GitHub

val fry : float = 4283508450.0

Looks like the math works… but man oh man does it take a while to get there. Using FSharp Charting and iterating over each year…

let fryOverTime = [ 1.0 .. 1000.0 ] 
                    |> List.map (fun t -> compound { Compounding = TimesPerYear 1.0
                                                     Rate = 0.0225
                                                     Principal = 0.93
                                                     TermInYears = t }  )

Chart.Line(fryOverTime
            , Name="Balance over Years"
            , XTitle = "Years"
            , YTitle = "Balance in Dollars")
       .WithXAxis(Min=0.0, Max=1000.0)
// because it's almost impossible to see any change until year 650 or so...
Chart.Line(fryOverTime
            , Name="Balance over Years"
            , XTitle = "Years"
            , YTitle = "Balance in Dollars")
       .WithXAxis(Min=0.0, Max=1000.0)
       .WithYAxis(Log = true)
Fry's Balance
Fry's BalanceIt looks like Fry’s bank account doesn’t get interesting until about 300 years or so. Still, compound interest is a wonder, despite whether or not Albert Einstein said so.

Coming Soon – Application Redesign in F#

We have a reporting application at {Redacted}, one I’ve spent more than a few hours maintaining. A colleague of mine and I (the junior developer I spoke of in a prior post) met up with the business owner of the reporting application, to talk about rebuilding the application in more functional way.

This application is not a terribly complicated one. In short, it takes data from multiple data sources, and re-aggregates it into a series of reports. It has however, had multiple developers come and “peek in”, drop code into it, and leave.ย  In the current C# implementation, it’s 5 distinct assemblies. There are 8 test assemblies to go with it.ย  It has a lot of business value riding on it being robust and correct, so the tests do seem warranted.

A word to the wise though… simple things that are more complicated than theyย should beย AND that have a ton of tests are REFACTORING GOLD.ย Nothing feels better than taking something complex and unwinding it to it’s core essentials, and nothing feels better than doing it in F#!

We took a page out of For Fun and Profit’s DDD page and focused for nearly an hour on the objects and processes involved in this report. Naturally, the process seemed extremely solvable in a functional way.

Unfortunately, due to time constraints on the rest of the day we were only able to start with some very high level type definitions, but those type definitions described our problem in such a way that the business owner was able to see and understand.

We did the whole thing with an instance of VS Code and Ionide, and had types describing the objects and functions involved, all with just a simple “domain” setup in the process. Did we implement anything particularly? Well, kinda yeah, this is perfectly compile-able F# code, which as opposed to Gherkin or some other spec-based “code”, does not need a secondary interpreter.


type Name = string
type Percentage = decimal
type Return = decimal
type TargetReturn = decimal
type Model = Name
type Allocation = ( Model * Percentage ) list
type MarketValue = decimal
type AccountReturn = Return
type Account = {
TAllocations : Allocation * MarketValue;
TMinusOneAllocations: Allocation * MarketValue;
Return: AccountReturn
}
type ModelReturn = decimal * Model
type RealizedTrackingError = AccountReturn -> TargetReturn
type SleeveReturn = ModelReturn -> RealizedTrackingError
type SleevedMarketValue = Allocation -> MarketValue
// no tracking error return
type SleeveLevelPerformance = Account -> ModelReturn list -> TargetReturn

That’s the crux of this application and function. I think we may be able to get this down to a few pages of code… as opposed to the novella you’d call it now. #FeelingHopeful

Fractions Cont’d, Factoring a Number


let getFactors (value : bigint) =
let isFactor i pf =
i % pf = 0I
let rec potentialFactors start i = seq {
match start with
| Some v when v > 2I -> for a in v..2I..i do yield a
| Some _ | None ->
yield 2I
yield! potentialFactors (Some 3I) i
}
let rec loop acc lf i =
let findFunc = isFactor i
let seq = potentialFactors lf i
match Seq.tryFind findFunc seq with
| Some (f) -> loop (f::acc) (Some (f)) (i / f)
| None -> acc
if (value < 0I) then loop [-1I] None (value * -1I)
elif (value > 0I) then loop [] None value
else []

As we last spoke about Fractions, we had a simple task after getting a Fraction object. We wanted to reduce them. I was taught to rit educe a fraction by factorizing the numerator and denominator, and then crossing out the common factors. Then, multiplying the numbers together to get the reduced value. E.G.

 60ย  ย ย  ย  ย 2 * 2 * 3 * 5        2 * 2 * 3 * 5       2 
----  =  -----------------  =  ---------------  =  ---
 90        2 * 3 * 3 * 5        2 * 3 * 3 * 5       3

The example above is a simple one.

So the question is, how do we best factor a number?

First, we define the mechanisms we require.ย  We need a way to define that a number is a factor of another.

let isFactor i pf =
    i % pf = 0I

What does this method buy us? First, it gives us a simple True/False flag defining whether or not a number is a factor of another. Namely, if the module of i and pfย is 0Iย ย (specifically, zero, as a System.Numerics.BigInteger), then we know that pf is a factor of i.

Next, we define a recursive function that takes two parameters, start and i.ย The idea of this function is to return a sequence of numbers from startย toย i,ย counting by twos. The start value is an option type, so if not present, it assumes 2 is a valid option, and starts the sequence there, then goes with every odd number. Note: I’m accepting as a given that this will do some checks against numbers it’s unnecessary to do that against.

Finally, I define my internal recursive loop to accumulate the resulting found factors. An interesting thing we’ve got here is some issues of partial application for function calls. The “findFunc” function is a partially applied isFactor call, with the i value already passed in. The reason I did this was to make the Seq.tryFind call easy to use, because the Seq.tryFind call is shaped like this:

('a -> bool) -> seq 'a -> Option 'a

But to get to my “bool”, I needed more than just a single ‘a parameter.ย  That’s why partial application was so valuable here! Instead of changing the function signature to meet my needs, I simply made a quick, easy to reference function by supplying some arguments ahead of time.

The rest of the function is fairly apparent. The loop recurses over the sequence, finding factors and then appending them to as the head of the accumulator (f::acc), until the Seq.tryFind call eventually returns None, and the accumulated list of factors is returned.

The last bits of the function just wrap up return values. In this case, we check for negative numbers, or a 0I being passed in.ย  Negative numbers are fairly easy to factor as they only require normal factorization of the inverse with -1I appended, and 0I simply returns an empty list.

With that, we get our results (mapped to ints, for ease of readability.)

getFactors 64000I |> List.map int;;
val it : int list = [5; 5; 5; 2; 2; 2; 2; 2; 2; 2; 2; 2]

getFactors 6423453000I |> List.map int;;
val it : int list = [8599; 83; 5; 5; 5; 3; 3; 2; 2; 2]

getFactors 9536923853063I |> List.map int64;;
val it : int64 list = [354041L; 178393L; 151L]

getFactors 953692385306332I |> List.map int64;;
// this one's still chugging.

The last thing to do here is to figure out how to avoid unnecessary iterations but that’s a topic for another blog.