Learning a new language is pretty straightforward once you have a few under your belt. That said, some languages bring concepts to the table that don’t line up with any of the languages you’ve used in the past. Functional programming (FP) isn’t a new way of programming, but it is very different from how most “traditional” programming languages work. Some languages, like my go-to notebooking language, F#, let you dip your toe in the proverbial FP waters. They give you the procedural flow you expect from languages like C, where instructions are executed one after another, but also let you apply functional-first concepts like currying and partial application. When all else fails, in F# anyway, you can fall back on the comfortable procedural way of doing things, complete with mutable variables and if statements.

Haskell, on the other hand, sits way over in the FP way of doing things. Not only can you not use concepts like if statements to branch control of your application, those sorts of control structures are fundamentally impossible in Haskell. Spurred on by my successes at leveraging F# as my notebook language of choice for almost two years, I decided to go from dipping my toe in the waters of FP to diving in head-first. Haskell was my white whale for many years. It’s why I didn’t think I could grok FP. Now that I’ve got a fairly firm grasp on the basics of FP, I’m ready to try it again.

I’m using a very well-respected book called Learn Haskell by building a blog generator by the wonderful Gil Mizrahi. I am not going to teach you Haskell in this blog post series. Rather, I’m going to try to work my way through the parts that I had trouble understanding, in the hopes that devs with similar backgrounds to me might find it useful.

Structure of the Series

Yeah yeah yeah, I know, I’m starting yet another series, when I don’t think I’ve finished any of my other ones yet. Sorry not sorry. I should just stop doing series, I guess.

Anyway, as I work my way through the book, I’m going to write down the things that I found particularly helpful, complete with code samples.

Part One - Implementing the Basics

So far, so good! A lot of the concepts are familiar from working in F#. I’m on page 36 out of 157 (of the PDF), which is the end of Chapter 3. I’m not really the biggest fan of the . operator, as I’m in love with the pipe (|>) operator from F# and other languages, and it works precisely backwards! This was the part I struggled with the most in this first section of the project.

I really had to think about the order of operations when it came to the escape function. The partial application was throwing me for a loop (so to speak. . .ha!). Once I’d figured out that our input would get “placed” at the end of the line, and then the line would start popping off one function at a time, it clicked. Let’s take a look at the escape function really quick:

escape =
  let
    escapeChar c =
      case c of
        '<'  -> "&lt;"
        '>'  -> "&gt;"
        '&'  -> "&amp;"
        '"'  -> "&quot;"
        '\'' -> "&#39;"
        _ -> [c]
  in
    concat . map escapeChar

The actual function, you’ll notice, does not take any arguments. That’s because it’s wrapping two (well, three) functions. We build out escapeChar in the function. It’s definition, should we care to define it, would be escapeChar :: Char -> String. So, if this function takes a character, but our escape function is supposed to act on the strings we wish to escape, what’s going on here?

The key to understanding this is to carefully read the section in the book regarding Linked Lists and map. Strings in Haskell are, for better or for worse, doubly-linked lists of characters. With that in mind, and knowing generally what a map function is, you can start to piece together how this function is working, even without knowing the let. . .in syntax.1 Since a map function takes a sequence of data and then returns a sequence of new data, calculated on each element of the sequence, you can thus intuit that, in this case, the escapeChar function is actually being performed on some sequence of data. . .which strings are.

That means that escape is (before we take the . operator and concat function into account) just a partial application of the map function, taking a sequence of characters and performing a function on them. My new, short-lived nemesis, the . operator, added an extra layer of complexity to the situation. . .until you remember that it works backwards from how the pipe operator works. With that little insight, the whole things falls into place:

  1. You pass escapeChar to the map function
  2. You pass a string to the resulting partial application of map
  3. Finally, you concatenate the mapped sequence back into a string, and return that data from escape.

A quick bit of cleanup

There exists a function called concatMap, which does exactly what concat . map does. I’m using that in my “final implementation”:

escape =
  let
    escapeChar c =
      case c of
        '<'  -> "&lt;"
        '>'  -> "&gt;"
        '&'  -> "&amp;"
        '"'  -> "&quot;"
        '\'' -> "&#39;"
        _ -> [c]
  in
    concatMap escapeChar

Conclusion

Two things help a lot when learning a new language:

  1. Read the docs carefully. If I’d taken the time to actually think about why the author was mentioning Linked Lists and describing how map and concat worked, I wouldn’t have struggled as much here.2
  2. Don’t be afraid to leverage your existing knowledge when working through a new language, but try to be cognizant of when your preconceptions might lead you astray. The . operator in Haskell is fundamentally the same concept as the pipe operator in other languages, and I know that intellectually, but it’s still hard to break the intuition that things that come later in a line are supposed to happen first. 3

  1. Incidentally, it is this function that actually helped me understand what the let. . .in syntax actually did. 

  2. This is a criticism I have about the book: Often it doesn’t actually tell you “Go write this thing yourself” when you’re supposed to do that, and just as often it does. Similarly, like here, it will explain something, and do that very well, but then not explain why it’s explaining that to you. This is a very minor complaint and, now that I see how the book works, it’s not going to be a problem any more. 

  3. Actually, this might not be entirely true. There exists in Haskell the concept of “fixity”, where certain operators are either “right fixity” or “left fixity”. Still, in general, things are going to resolve from right to left, as far as I can tell.