Teachers Of Functional Programming: Stop Driving Me Crazy With Math Problems!
“Actually, I’m starting not to like functional programming…if I see one more Fibonacci or factorial coding example in an F# tutorial or textbook, I’m going to stab the next nerd I find”
I feel this pain. When I learned F#, I’d start reading books and the first thing I saw was stuff that looked like this:
let rec fact x = if x < 1 then 1 else x * fact (x - 1)
Hey look! Now I can do factorials!
Who the heck wants to do factorials?
Like most OO programmers, I struggled a lot learning F# and functional programming. I kept at it, slogging through a bunch of books with a lot of math problems disguised as coding. I had been programming for a long time, and I’ve never coded a math problem. People rarely come to business programmers and ask them to do math.
And then I would talk to some super-smart people, like the guys at google. I’d ask them what their code does, and I’d get weirdest answer.
Well really it just does some matrix math and assembles the results.
Now this code may be running on 100,000 servers worldwide, it may have fault-protection and do all sorts of wondrous things, but to them it’s all about some kind of math problem.
What was I missing? It didn’t make sense to me. Sure, I got the math coding part. I understood how to do it. But when was I going to start form validation? Business logic coding? Important stuff?
So I jumped into my first project. It was a common, plain windows application. I had written something like this in C, C++, VB, C#, maybe a couple of other languages. Using F#, I coded up some windows.
I decided to add in some mailbox stuff, which is kinda cool. And then I went functional-reactive, which sort of fit naturally. Then I wanted to do some other stuff…..
It was a mess. I ended up with code that looked like this. (I may have actually copied this from somewhere. Beats me. It’s been a while)
///<remarks>Worker bee class for doing long-running threaded work</remarks> type AsyncWorker<'T>(jobs: seq<Async<'T>>) = /// <summary>Capture the synchronization context to allow us to /// raise events back on the GUI thread</summary> let syncContext = let x = System.Threading.SynchronizationContext.Current if x = null then new System.Threading.SynchronizationContext() else x let cancellationCapability = new CancellationTokenSource() // Each of these lines declares an F# event that we can raise let allCompleted = new Event<'T>() let error = new Event<System.Exception>()
I had a disk utilities class, a class for writing html, a class for handling gating. I had a class for everything that I thought was important.
It’s not that I didn’t get it working — I did. The problem was that I was spending all of my time being an OO programmer in a functional world. My thinking was not in alignment with the tool I was using. I was interested in class libraries that could do cool stuff, which class libraries I would need to create, which code went in which class. In my mind I was assembling a large structure with a lot of little building blocks. Some of them I made on my own. Some I got elsewhere.
I did that a few times. Heck, I’d probably still be doing that if I hadn’t decided that I wanted to learn startups more than I wanted to learn functional programming. To learn startups, I started writing various web pages — no programming if I could get away with it. In fact, many times I would compete with myself to see if I could take a weekend and write something up on the web that would make money. Anything. The programming part didn’t matter. Just make something useful.
I did all kinds of cool stuff. Eventually I landed on serverless web applications a few years ahead of everybody else. Fun times.
But with those serverless applications, eventually I wanted to add functionality. How would I do that as directly as possible? Pick up a framework?
That’d be tough to do in a weekend, and I was all about making stuff people want and not losing focus on that. So why not just write an F# app that runs on the server as a plain-old CGI app? It’d just be an F# console app for linux. It’d read the header info of the page POSTing or GETing the data, then write html directly back out to the client. This is about as simple as I can possibly make it.
I wrote that up and it worked fine. It was a very small program! But it did what I wanted and I never had to maintain it. It just worked.
I did that a few times with various apps. I started to notice a pattern emerging. First I would “clean up” the incoming data: make sure it was valid, ignore bad data, take care of any option types, make it as correct as possible. Then I would process it. And the processing would always end up being some kind of simple sorting, matching, or recursing through various data structures.
In short, a math problem.
The light was beginning to come on. As I moved to microservices, I saw the pattern again: clean the data, take care of the housekeeping. By the time you finish cleaning things up the problem is trivial. My microservices never got more than around 100 lines of code — and they did very useful things! With very little maintenance!
I found that I needed the strongest types on the “outside” of the program, where it touched the rest of the world. But those types were all about I/O and data movement. As I moved inward, I wrote more and more generic code. This code I captured and put in a utilities library. I also refused to make a class until I was absolutely sure it was doing something useful, that is, that I had persisted state and methods that belonged together and could be used across several applications.
I went for a long time before I had good reason to make a class.
I finally found some code that belonged in a class! It was a class to handle a problem I would have over and over again: taking care of the argument list passed in from the command-line. I made a nice generic type that took care of what I wanted where the programmer using the type could make it all work with just a few dozen lines of code.
That’s a bit more setup than I like, but it’s all fairly straightforward. You should be able to read the comments and figure out what it does. It’s easy-to-use, and really? It’s not part of the app. It’s a thing that handles getting args from the command line. It’s become a standard part of my toolkit. I don’t count it as part of the code I write. I instantiate the type with a few things…and it just works.
This way of working outside-in is called an “Onion Architecture”. People have been doing it for years. Done correctly, it prevents the program from failing. No matter what, the program runs — which is exactly what you want in a true Unix-Philosophy microservices approach. (There’s a long discussion to be had about the Unix Philosophy that we’ll save for another day)
And I finally figured out why everybody kept trying to give me math problems when I was learning! Functional programming to me is focused on output, not structure. What are you doing for me? Everything revolves around that, and it should be the driver of any coding or architectural decision you make. By the time the type system kept out bad data and controlled program flow, the only major thing that was left was understanding simple semantics and how first-class functions were an entirely different animal from the methods in C# that I had been used to. And math is probably the easiest way of understanding that.
They weren’t trying to teach me math. They were trying to teach me function construction and composition. FP is based on math, and it was the easiest thing they had handy to use as an illustration. Otherwise, to do the “real” stuff I was asking about, the authors would have had to guide me through the entire onion process. That’s too much overhead for talking about something simple like recursion or pattern-matching.
The reason those guys at google said their code just added some matrices is that the rest of it doesn’t matter. That outside-of-the-onion stuff is just grunt work, most of which you can reuse (as opposed to the “reuse” promised to us in OO, which never panned out.) In the diagram above, it’s level 2 and 3 where all the “interesting” stuff happens. In fact, it’s mostly level 3 — and it’s mostly not that much stuff. And just like in OO, once you start thinking the right way, with the grain of the language, the problem kind of “falls apart” and becomes trivial. It’s a matrix problem. The only difference is that it falls apart in an entirely different way in FP than OO.
July 8, 2018