using Programming;

A Blog about some of the intrinsics related to programming and how one can get the best out of various languages.

Getting started with programming and getting absolutely nowhere (Part 12)

How do we become experts?

Lesson 11: Why do we compose?

As I've written this series, I've realized I haven't covered one very important aspect of becoming a programmer: how do we become experts? How do we get better?

Programming well is not easy

Any seasoned software developer can tell you that becoming a good programmer is not an easy task, but I'll let you in on a little secret: becoming good at anything requires a lot of practice, and many failures. If you ask any athlete, you'll get the same response. Programming shares this facet.

So with all this said, I want it to be painfully clear: programming takes practice. It takes a lot of practice, refining your skills, honing your talents, and focusing on what's important: solving problems. As a software engineer you are expected to solve problems, in whatever manner you must. Because programming takes practice, I want to continue solving problems with you, and guide you towards how we should think about them.

If you've ever wondered how the 'pros' write complex, structured code, it's actually just a great deal of practice and studying (much like any other activity). In fact, we often write simplistic code at the beginning, and begin reducing it to more complex, but concise code. We're going to explore some examples of that today.

Example one: eliminating lambda's

One of the most common things I do in F# is a lambda. That is, an anonymous function of the form fun <parameters> -> <expression>, you'll find that you end up using these a lot because they're easy to express: define the activity you need to do and do it. We'll take the following example and eliminate each lambda.

Initially you might write some code that looks like the following (I purposefully violated a couple aspects of F# here, bear with me we'll fix them):

let rows =
    [parameters.InitialOffset..mainSheet.LastRowNum]
    |> List.map (fun num -> num |> mainSheet.GetRow)
    |> List.map (fun row ->
        match row with
        | null -> None
        | _ -> Some row)
    |> List.filter (fun rowOpt -> rowOpt.IsSome)
    |> List.map (fun x -> x |> Option.get)

Which is clear, tells us exactly what is happening, but can be made shorter and more idiomatic.

The first thing to note is the List.map lambda. If you ever see a lambda of the form fun x -> x |> someFunctions, you should understand that you can completely eliminate the lambda here. We can rewrite fun x -> x |> someThing as someThing, no need for the fun x -> x |>. This also applies if you're writing functions of the form someThing(x), or someThing x. If the lambda has one parameter, and we're using that parameter only for a single call, then we can remove the entire lambda and replace it with the function chain.

|> List.map mainSheet.GetRow

Boom, done. Next, we can eliminate that last List.filter and List.map with a single List.choose:

|> List.choose (fun x -> x)

Which now introduces an interesting idea: how do we reduce fun x -> x to a single function call?

Well, F# has a handy, built-in function called the 'identity function', or id for short. This function is essentially the same as our lambda, so we can replace fun x -> x with id:

|> List.choose id

Next, we should realize that List.choose can work in place of our second List.map, so we'll replace that:

let rows =
    [parameters.InitialOffset..mainSheet.LastRowNum]
    |> List.map mainSheet.GetRow
    |> List.choose (fun row ->
        match row with
        | null -> None
        | _ -> Some row)

And now we get to another interesting function that requires some framework knowledge: we can actually replace that entire List.choose lambda with a single function: Option.ofObj. This function takes any 'object' type and returns an Option: None if the object was null, and Some object if it was not.

let rows =
    [parameters.InitialOffset..mainSheet.LastRowNum]
    |> List.map mainSheet.GetRow
    |> List.choose Option.ofObj

There is also an Option.ofNullable, which works on nullable value-types (like a nullable 'int').

Example two: identifying functionality that can be abstracted

In one of my work projects, I have the following function, which should definitely be reduced:

let compare (m1 : SpecPerson) (m2 : SpecPerson) =
    let result = 
        m1.FirstName = m2.FirstName
        && m1.LastName = m2.LastName
        && m1.State = m2.State
    let result =
        result &&
        match m1.MagicId, m2.MagicId with
        | Some a, Some b -> a = b
        | _ -> true
    let result =
        result &&
        match m1.MiddleName, m2.MiddleName with
        | Some a, Some b -> a = b
        | _ -> true
    let result =
        result &&
        match m1.City, m2.City with
        | Some a, Some b -> a = b
        | _ -> true
    let result =
        result &&
        match m1.Zip, m2.Zip with
        | Some a, Some b -> a = b
        | _ -> true
    let result =
        result &&
        match m1.OtherThing, m2.OtherThing with
        | Some a, Some b -> a = b
        | _ -> true
    result

If it's not painfully obvious here, I'm comparing several values, and in the case of optionals, if not both of the things are Some, then considering them true. We can really abstract that to a higher-order function:

let compare (m1 : SpecPerson) (m2 : SpecPerson) =
    let optionalComparison a b =
        match a, b with
        | Some a, Some b -> a = b
        | _ -> true
    m1.FirstName = m2.FirstName
    && m1.LastName = m2.LastName
    && m1.State = m2.State
    && optionalComparison m1.MagicId m2.MagicId
    && optionalComparison m1.MiddleName m2.MiddleName
    && optionalComparison m1.City m2.City
    && optionalComparison m1.Zip m2.Zip
    && optionalComparison m1.OtherThing m2.OtherThing

Now doesn't that seem nicer? We should always try to identify what's common and pull it up a level, so we can reuse it. This is a really tough thing to be able to do in the beginning, so I don't expect you to get it right away. As you gain experience you'll learn what can be lifted out more effectively, and you'll gather a stronger understanding of what is important to lift out.

Example three: think critically about what you're doing

I recently had the following match in a project I was working on:

match p with
| One OtherSpecific
| Multiple (AnyNumber OtherSpecific)
| Multiple (Two OtherSpecific)
| Multiple (Zero OtherSpecific)
| Multiple (Many OtherSpecific) -> doSpecificThing
| One OtherNonspecific
| Multiple (AnyNumber OtherNonspecific)
| Multiple (Two OtherNonspecific)
| Multiple (Zero OtherNonspecific)
| Multiple (Many OtherNonspecific)  -> doNonspecificThing

Of course, we can really improve this, what terrible dev wro—....oh yeah, me. We should realize that we're doing something that can easily be pushed away:

let getSpecificity q =
    match q with
    | One s
    | Multiple (AnyNumber s)
    | Multiple (Two s)
    | Multiple (Zero s)
    | Multiple (Many s) -> s

match p |> getSpecificity with
| OtherSpecific -> doSpecificThing
| OtherNonspecific -> doNonspecificThing

I'm supposed to be a professional and I let that ugly mess live in a project for quite some time, so if you ever begin to doubt yourself: even the good programmers do it.

You may also notice that this is no shorter than the original version (both are 11 lines, though the second version has a debatable line-break), but interestingly, this version is reusable, if we ever have to getSpecificity elsewhere, we can. This should be incredibly important to you, and you absolutely must consider it: if something is generic, build it to reuse it.

You may not be perfect, but no one is

I don't think I've stated it in this series yet, so here we go: you may not be perfect, but no one is. Programming is a way of thinking, it's an entire mindset. If you stick with it, you'll eventually become so familiar with the entire process that it'll become second nature, you'll begin looking at things more clearly. There will be several "aha" moments — one day you'll be sitting on the porch thinking about something completely unrelated, and then an epiphany will hit, and things become immedately clear. This happens to all of us, and it will most probably happen to you.


Every time I write one of these I reflect on what the previous lessons include, and continually disappoint myself. I really, really expected to do better for you all, and I promise it'll come. If anyone wants a specific topic or 'thing' (where 'thing' can be a feature, concept, problem) covered, please let me know on Twitter. I want you to want to read this, and for that to happen I need to know what you want to see. You can also comment on this post, I read all comments and (unfortunately) delete most as spam, but it's a safe medium to reach out.

Loading