Let's read: Haskell Programming from First Principles, pt III

What if a string was just a list of characters?
Detail from the Programming Haskell From First Principles book cover, showing just the title. There is an overlay saying 'Chapter 3' across the top right corner.

You still following?

This will be a rather short and focused post. Chapter 3 of the book doesn’t spring any exciting new concepts on us or cover large topics such as general syntax. Instead it chooses to zoom in on the String datatype. We look at how they are represented in Haskell and how we can treat them like lists because, spoiler alert: that’s what they are.

Like in all compiled languages that I’ve used, there’s a difference between the data types Char and String and how you represent the literals. As is the convention, a Char is represented using single quotes ('a'), while a string uses double quotes: "hey, tiger".

However, unlike in a most languages I’m aware of, a String is actually just syntactic sugar for a linked list of characters ([Char]). So ['h', 'e', 'l', 'l', 'o'] and "hello" are just two ways of writing the exact same thing. This has some interesting effects on how we handle strings, in that it means anything we can do with a list, we can do with a string, but it also means that certain operations will be very costly. There are alternatives for when you need performance, but let’s not concern ourselves with that for now.

String/list operations

In the introduction to handling strings (secretly lists), we are introduced to the functions head, tail, take, drop, and the operator !!, so let’s have a quick look at what they do.

The head and tail functions return the first element of the list and the list without the first element respectively; so that if you take the head of a list, the tail of a list, and then put them back together, you’d have the original list back:

Side note: this is the first time we’re seeing that : operator. It’s known as cons and it adds a value to the front of a list.

Be aware that these functions—head and tail—are not total, and will throw exceptions at you if you give them an empty list.

A total function is one that is well defined and gives you a result for every input value—i.e. maps every value from the domain into a value in the image (the subset of the codomain containing possible output values), to use the terminology from chapter one.

Functions that are defined only for a subset of their valid arguments are known as partial functions.

More on this later on in the book.

The take and drop functions, though, are total, and will not throw anything in your face. They operate on lists, take an Int as the first argument and then split the list after as many elements as specified by the Int, giving you either the first portion or the second. They’re pretty much like head and tail, except you can choose how many elements go in each pile, and you’ll always get a list back. If there aren’t enough elements in the list to reach the specified number, take will give you back the whole list, and drop will give you an empty list.

!! is the index operator and returns the element at the specified index in the list. The equivalent of [n] in a lot of other languages. Zero-indexed and unsafe: "hey" !! 2 would return 'y', while "hey" !! 5 would crash.

Definitions

There are only a few interesting definitions in this chapter:

String
a sequence of characters. In Haskell, the String type is just an alias for a linked list of characters, [Char].
Type (or datatype)
“A classification of values or data”, such as Int, Bool, String, etc. Notably does not have any behavior associated with it.
Concatenation
Joining sequences of values together. Often used with lists, it’s the act of putting one before the other and creating a new list. The operator (++) takes care of it in Haskell.

That’s it for this chapter. Next time, we’ll be looking at basic datatypes. This is when things start to get interesting, so strap yourself in and get ready.