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 2)

Now that we're programmers, let's make something

Lesson 1: How To Become a Programmer (In a Few Not-So-Easy Steps)
Lesson 3: Since We've Made Something, Let's Make It Cooler

So two days ago I posted a very rudimentary introduction into "how to become a programmer", which was mostly a rant about what is and isn't important, and about how F# works. (Why F#? Because it lets us be lazy, and it does a whole hell-of-a-lot for you.)

Today we're going to introduce the business domain.

Almost everything is "business logic"

The first thing about the "business domain" is that it's a very real thing. In the business domain everything is what's called "business logic", that is: logic that makes the business work. Now we're not talking "business" as in "for-profit corporate entity", but "business" as in "a person's concern". (Think: "it's none of my business".)

Basically, business logic is any logic that is related to the task at hand. So consider a "cart" system, business logic might be "we need to be able to add an item to the cart, remove an item from the cart, and change quantities of items in the cart." Simple, right? Somewhat straightforward. You may also here this referred to as "BL" or "domain logic", or the collective "domain" (which includes the "business domain" and the "business logic"). I'm going to call it "business logic" because that's what is most applicable to my situation, but feel free to use whatever terms you feel appropriate.

So we saw an example of business logic, what do we do with business logic? Usually we write our software around the business logic, so we may write a function addItemToCart or removeItemFromCart that adjusts the items in our cart as appropriate. These are considered "functions that perform business logic".

Simple, right? This is all largely basic, but it's important to know because we need to understand what is and isn't business logic. For example, when we disucss the implementation itself, we're no longer talking about business logic, but "application logic". The business logic is the broader picture: what is the purpose of our application? It's the "problem" the application is "solving", this can vary all over the place, but the basic idea is it's the higher-overview of the application.

All that long-worded rambling aside, let's define a piece of business logic that we want to build an application to solve:

Given a string, split it into a group of "words" on spaces, where each "word" has no-less-than three characters.

This is a very real problem I had to solve for my job recently, I won't say why, but it was necessary. So, we're going to focus specifically on this problem in this post.

The first step is to define the sub-steps

Every problem is, at it's core, a series of smaller problems. You can always break it down, though sometimes it's unecessary. For our problem let's break it down into subparts:

  1. Split a string on spaces
  2. Analyze the first word, if it has 3 or fewer characters, group it with the next word
  3. Repeat for the next word

So that's more manageable, and we can solve that in "sub-steps". We can solve each step independently. (Granted, we only have 3 steps, but we'll break step 2 down futher when we get to it.) Each step should be testable: so given some input I should have a defined output.

Splitting a string on spaces

So the first step is actually the easiest, split the string on spaces. in F# this is easily done via String.Split, which is available in the entire .NET framework:

let parts = "Some String With A Short Word".Split(' ')

So that's easy, and we can create a function that handles that pretty easily, we'll define a split function that takes a string, and a char (separator) and splits the string on the "char" (separator):

let split (c:char) (s:string) =
    s.Split(c)

That's pretty basic, but why did we define a new function when we could just call s.Split(c) directly, with our own s and c? This is so that we can split strings "idiomatically", that is, matching the general style of F# code. F# isn't about calling methods, it's about composing functions, and you cannot compose String.Split(char) easily like that, so we define a function that lets us do so.

So now we could test our function, which involves simply calling it:

let parts = "Some String With A Short Word" |> split ' '

Well that was easy. This shows that we can pipe a string into the split function, and it does what we expect.

Moving to Step 2: this is going to hurt

So F# makes step 2 pretty easy, and if you have any programming experience with a non-functional language, I want you to forget it right now. What you think you know is not true in F#, and we need to redefine the problem.

We can break step 2 down a little further:

  1. Get a word
  2. Check how many characters the word has
  3. If < 3, it belongs with the next word

So let's start building a groupWords function, we're going to do this the "Elliott Brown" style, which is probably different from what you've usually seen, but this is where functional languages make things pretty awesome.

Instead of looking at the current word, we're going to look at the previous word, since it makes things easier. We're going to use a basic pattern match with a guard clause, List.fold, Array.toList, and Array.ofList.

The easiest way to do this involves converting the string array to a string list, which is done with the Array.toList:

let stringList = parts |> Array.toList

If you're following along in a REPL (the F# interactive window), you should notice the biggest difference between the printed versions of each is that Array has vertical-pipes on the inside of the brackets: [|element1; element2; ...|], and List does not: [element1; element2; ...].

So now we're going to fold over that list: a fold takes an accumulator, and a value. It iterates over each value in the List and applies a function to it, which usually combines the value with the accumulator somehow. Our fold is pretty basic:

let groupStrings (acc:string list) str =
    match acc with
    | prev::tail when prev.Length <= 3 -> (sprintf "%s %s" prev str)::tail
    | _ -> str::acc
let groupedStrings = stringList |> List.fold groupStrings []

We could modify this to take a length instead of 3, but I'll leave that up to you.

Some neat syntax things:

  1. The match is a "pattern match", similar to a C-style language switch, but on sterroids.
  2. The prev::tail is a pattern expression for a list: it means "match the first element in the list to prev, and the remaining elements to tail.
  3. The when ... is a "guard clause": it means this expression is matched first, then the entire pattern is matched if and only if the guard clause returns true.
  4. The -> means "return the right side as the result of the match expression." (Basically)
  5. The (sprintf "%s %s" prev str) just combines the two strings with a space between them.
  6. The ::tail now creates a new list with the sprintf block as the first element, and the remaining elements as the, well, remaining elements.
  7. The _ is a "match anything" pattern-expression.
  8. The [] is an empty list (the type is inferred to be whatever that "folder" expects - a string list in this case).

Now we just need to reverse the list, which in F# is easily achieved by List.rev:

let finalResults = groupedStrings |> List.rev

And lastly, we'll convert them back to an array with Array.ofList:

let result = Array.ofList

That's it, make it reusable

So now we've successfully built the functions to do this work, let's build a function that is reusable.

let splitAndGroupString =
    split ' '
    >> Array.toList
    >> List.fold (fun (acc:string list) str ->
        match acc with
        | prev::tail when prev.Length <= 3 -> (sprintf "%s %s" prev str)::tail
        | _ -> str::acc) []
    >> List.rev
    >> Array.ofList

And we can call it as:

let splitStr = "Some String With A Short Word" |> splitAndGroupString

And that's it!

We went through some basic business logic today, we'll be going through more complex stuff in the coming lessons, but it's good to take it slow (at first), and we'll eventually build up to some pretty complex operations.

I told you I would get more organized, and I did. As we continue I'll figure out how I plan to present things effectively, but for now I'm just going to stick with my gut. I hope you enjoyed it and learned something, but if not it's still helping me learn more and become a better software engineer, so you should probably count on more crap coming from me here soon.

Loading