3.14

String

In Elm, strings are represented with double quotes:

> "Pretzels"
"Pretzels" : String

Characters are represented with single quotes:

> 'p'
'p' : Char

Character literals must contain exactly one character. Therefore, these expressions are not allowed in Elm: 'abc', ''. Strings can have as many characters as we want in them.

Multiline Strings

Multi-line strings are created using """. That’s triple quotes. Here’s how we can insert a multi-line string in repl:

> """ \
|   It became very clear to me sitting out there today \
|   that every decision I've made in my entire life has \
|   been wrong. My life is the complete "opposite" of \
|   everything I want it to be. Every instinct I have, \
|   in every aspect of life, be it something to wear, \
|   something to eat - it's all been wrong. \
|   """

We don’t need to add \ to the end of each line when we create a multi-line string in a file though. Define a constant called revelation right above main in Playground.elm file.

revelation =
    """
    It became very clear to me sitting out there today
    that every decision I've made in my entire life has
    been wrong. My life is the complete "opposite" of
    everything I want it to be. Every instinct I have,
    in every aspect of life, be it something to wear,
    something to eat - it's all been wrong.
    """

Now pass the revelation constant to Html.text in main.

main =
    Html.text revelation

Run elm reactor from the beginning-elm directory in terminal if it’s not running already and refresh the page at http://localhost:8000/src/Playground.elm. You should see the profound revelation George Costanza had when he was looking at the ocean on a dreary day.

We can use both single and double quotes inside a multi-line string. Using a single quote inside a single-line string is fine, but we need to escape the double quotes with \.

> "Michael Scott's Rabies Awareness \"Fun Run\" Race for the Cure"
"Michael Scott's Rabies Awareness \"Fun Run\" Race for the Cure"

\ tells Elm that the double quotes immediately after it aren’t there to end the string. Similarly, a single quote must be escaped inside a character literal.

> '\''
'\''

How do we escape the backslash itself? With another backslash.

> '\\'
'\\'

Calculating Length

To calculate a string’s length, we can use the length function from String module.

> String.length "Creed Bratton"
13

> String.length ""
0

Up until now, we have been creating only custom functions by explicitly defining them. Going forward we will make heavy use of built-in functions like String.length, which is a part of Elm’s standard library.

Standard Library
Most programming languages come with a standard library — a collection of functions and values most likely written in the same language. Elm follows that trend by providing a standard library of its own. All functions and values in Elm’s standard library are grouped into modules based on what type of data they operate on. For example, the String module contains functions for manipulating strings. Similarly, the List module provides functions for modifying lists. There are many more modules in the standard library for working with almost any type of data, for example Dict, Set, and Time to name a few. Various math and function helper operators we saw earlier in this chapter are inside the Basics module.

Modules are further grouped into packages. String, List, and Basics all belong to a package called Core, which comes pre-loaded with the Elm Platform. Only the most essential packages are included in Elm Platform. Others are available for download from the online catalog.

When an elm repl session is started, the String module gets loaded automatically. Otherwise we would have to explicitly import it like this:

> import String

elm repl automatically loads modules that are most frequently used in Elm. When using a function, it’s a good practice to prefix it with a module name for two reasons:

  • It tells us where the function came from.

  • It prevents name clashes between functions from different modules.

If we want to, we can expose the length function so that we don’t have to use the prefix.

> import String exposing (length)

> length "Angela Martin"
13

We used a slightly different syntax to import the String module here. The exposing (length) part tells Elm to expose the length function so that we don’t have to prefix it with String. As it happens, the List module also provides a function called length. What will happen if we expose that function too? Let’s find out.

Note: In Elm, a list is a data structure for storing multiple values. We can create one with square brackets like this: [ "Titan", "Encaladus" ]. We will cover lists in detail later in this chapter.

> import List exposing (length)

> length [ 1, 2, 3 ]

----------------- TYPE MISMATCH ------------------
The 1st argument to `length` is not what I expect:

6|   length [ 1, 2, 3 ]
            ^^^^^^^^^^^
This argument is a list of type:

    List number

But `length` needs the 1st argument to be:

    String.String

Even though we exposed the length function from List, Elm still thinks we are trying to use String.length. To avoid this confusion, we need to be specific by prefixing the module name.

> String.length "Angela Martin"
13

> List.length [ 1, 2, 3 ]
3

Checking Emptiness

The String module provides a function called isEmpty that tells us whether a given string is empty or not.

 > String.isEmpty ""
True

> String.isEmpty "Kevin Malone"
False

This is more readable than using the length function, where we have to explicitly compare the length to 0.

> String.length "Kevin Malone" == 0
False

Combining Strings

++ operator is used to concatenate two strings.

> "These pretzels are " ++ "makin' me thirsty!"
"These pretzels are makin' me thirsty!"

Note: We don’t need to prefix operators with a module name.

We can’t use ++ to combine characters or numbers though. These will throw errors:

> 'p' ++ 'r' -- Error
> 42 ++ 10 -- Error

One way to combine characters is by converting them to strings first. We can use the fromChar function to do that.

> (String.fromChar 'p') ++ (String.fromChar 'r')
"pr"

We can also combine two strings using the append function.

> String.append "These pretzels are " "makin' me thirsty!"
"These pretzels are makin' me thirsty!"

append is a special case of ++ operator, which puts two appendable things such as strings and lists together.

Concatenating multiple strings

We can also combine more than two strings using the ++ operator.

> "Bears. " ++ "Beats. " ++ "Battlestar Gallactica."
"Bears. Beats. Battlestar Gallactica."

Technically speaking, the ++ operator combines only two strings. In our example above, Elm first applies ++ to "Bears. " and "Beats. " resulting in "Bears. Beats. ". It then applies ++ with "Battlestar Gallactica." as a second argument to produce the final string. We can also use append to combine more than two strings. Although it tends to get a bit verbose.

> String.append (String.append "Butter shave, " "Voice, ") "Serenity now"
"Butter shave, Voice, Serenity now"

Another way of concatenating multiple strings is by using the concat function which requires us to put individual strings into a list.

> String.concat [ "Bears. ", "Beats. ", "Battlestar Gallactica." ]
"Bears. Beats. Battlestar Gallactica."

Notice how we included the space between words as part of the individual strings themselves. That looks awkward. If we use the join function instead, we don’t have to do that.

> String.join " " [ "Bears.", "Beats.", "Battlestar Gallactica." ]
"Bears. Beats. Battlestar Gallactica."

join takes two parameters:

  • A separator that separates individual strings when they all get combined.

  • A list of strings.

The separator can be anything we want.

> String.join "/" [ "Bears.", "Beats.", "Battlestar Gallactica." ]
"Bears./Beats./Battlestar Gallactica."

> String.join " thatswhatshesaid " [ "Bears.", "Beats.", "Battlestar Gallactica." ]
"Bears. thatswhatshesaid Beats. thatswhatshesaid Battlestar Gallactica."

Splitting a String

We can use the split function to break a string into multiple parts. It’s the exact opposite of join.

> String.split " " "Bears. Beats. Battlestar Gallactica."
["Bears.","Beats.","Battlestar","Gallactica."]

> String.split "/" "Bears./Beats./Battlestar Gallactica."
["Bears.","Beats.","Battlestar Gallactica."]

> String.split " thatswhatshesaid " "Bears. thatswhatshesaid Beats. thatswhatshesaid Battlestar Gallactica."
["Bears.","Beats.","Battlestar Gallactica."]

Did you notice that in the first example above, when we split on " ", we end up with four strings, while in the other examples the result is three strings? That’s because there is a space between the words “Battlestar” and “Gallactica.” If we wanted to get a result of three strings, we’d have to be more specific on what to split on like the following example:

> String.split ". " "Bears. Beats. Battlestar Gallactica."
["Bears","Beats","Battlestar Gallactica."]

Reversing a String

Let’s write a function that tells us whether or not a word is a palindrome. Palindrome is a word or groups of words that read the same forward as backward.

> palindrome word = word == String.reverse word
<function>

We reverse the word using the reverse function and compare it with the original. If they both are same, we return True. Now let’s have some fun with this function.

> palindrome "tacocat"
True

> palindrome "hannah"
True

> palindrome "palindrome"
False

> palindrome "As I pee, sir, I see Pisa"
False

Unfortunately, our function is too simple to recognize the last example as a palindrome. See if you can improve it to return True for that phrase too. Also, why is the word "palindrome" not a palindrome?

Filtering a String

Let’s say we need to do a background check on every candidate that wants to join the Enceladus Program for exploring one of Saturn’s moons. So we have asked the candidates to give us their social security number. Someone miscommunicated and asked them to enter it like this: 222-11-5555 when in fact our system doesn’t accept dashes. We can use the filter function to get rid of them, but we can’t just apply it like this:

> String.filter '-' "222-11-5555"

----------------- TYPE MISMATCH -------------------
The 1st argument to `filter` is not what I expect:

7|   String.filter '-' "222-11-5555"
                   ^^^
This argument is a character of type:

    Char

But `filter` needs the 1st argument to be:

    Char -> Bool

It expects a function that takes a character and returns a boolean. Let’s create a function which does exactly that.

> isValid char = char /= '-'
<function>

> isValid '-'
False

> isValid '2'
True

Note: In mathematics, a function that takes a value and returns a boolean is called a predicate function. You will often hear functions like isValid being called a predicate.

If the character is - we return False, otherwise True. Let’s give the isValid function to filter and see what happens.

> String.filter isValid "222-11-5555"
"222115555"

Aha! The dashes are gone. The following diagram illustrates how filter works.

filter fed each character from the string we provided into isValid. If it returned True, the character was kept, otherwise it was discarded. filter is a higher-order function because it takes another function as an argument. The String module provides many more higher-order functions like this such as map. We won’t cover them all here, but you should check them out from the official documentation.

Anonymous Function

Sometimes its desirable to inline the function that determines whether or not a value should be filtered instead of defining it separately as shown below.

> String.filter (\char -> char /= '-') "222-11-5555"
"222115555"

This time we gave filter a function that doesn’t have a name. It’s called an anonymous function. The following diagram explains various components of an anonymous function.

Anonymous functions are quite useful for writing quick inline functions. We will see more examples later. We can actually re-write the isValid function we saw earlier like this:

> isValid = \char -> char /= '-'
<function>

> isValid '-'
False

As it turns out a function name is just a constant pointing to an anonymous function definition.

Formatting a String

We often need to convert a string to all upper or lower case. The String module provides just the functions we need.

> String.toUpper "I declare bankruptcy!"
"I DECLARE BANKRUPTCY!"

> String.toLower "Shhh. Be quiet."
"shhh. be quiet."

We can also trim unnecessary whitespaces from a string.

> String.trim "    A band of backwoods mail-hating survivalists   \n"
"A band of backwoods mail-hating survivalists"

> String.trimLeft "    A band of backwoods mail-hating survivalists   \n"
"A band of backwoods mail-hating survivalists   \n"

> String.trimRight "   A band of backwoods mail-hating survivalists   \n"
"   A band of backwoods mail-hating survivalists"

If we want to stuff more characters into a string, the String module has functions for that too.

> String.pad 10 ' ' "5"
"     5    "

> String.pad 10 '*' "5"
"*****5****"

The pad function pads both sides of a string with a character of our choice until it reaches the given length: 10. We can also pad only one side if we want.

> String.padLeft 10 ' ' "5"
"         5"

> String.padRight 10 ' ' "5"
"5         "

> String.padLeft 10 '*' "5"
"*********5"

> String.padRight 10 '*' "5"
"5*********"

Substrings

Finding Substrings

We can check for substrings using the contains function.

> String.contains "believe" "It’s not a lie if you believe it."
True

> String.contains "George" "Fires people like it's a bodily function!"
False

We can also find out if a string starts or ends with a certain substring.

> String.startsWith "Kruger" "Kruger Industrial Smoothing"
True

> String.endsWith "LeBaron" "1989 LeBaron"
True

We can even pinpoint where exactly the substring lies with the indexes function.

> String.indexes "on" "They just write it off."
[]

> String.indexes "write" "They just write it off."
[10]

> String.indexes "write" "How is it a write-off? They just write it off."
[12,33]

indexes returns an empty list when it can’t find a substring. Otherwise it returns an index where the substring starts for each occurrence. And remember when we count indexes in Elm, we have to start from 0. We can also use the indexes function on multi-line strings.

> String.indexes "quitter" """ \
|   I'm a great quitter. It's one of the few things \
|   I do well. I come from a long line of quitters. \
|   My father was a quitter, my grandfather was a \
|   quitter... I was raised to give up. \
|   """
[18,97,128,163]

It’s important to note that most functions in the String module for locating substrings are case-sensitive.

> String.contains "Believe" "It’s not a lie if you believe it."
False

> String.endsWith "Lebaron" "1989 LeBaron"
False

> String.indexes "Write" "They just write it off."
[]

Extracting Substrings

Now that we know how to look for substrings, let’s go ahead and extract them using the slice function.

> String.slice 0 5 "Bears. Beets. Battlestar Galactica."
"Bears"

> String.slice 7 12 "Bears. Beets. Battlestar Galactica."
"Beets"

> String.length "Bears. Beets. Battlestar Galactica."
35

> String.slice 14 35 "Bears. Beets. Battlestar Galactica."
"Battlestar Galactica."

slice takes start and end indices of the substring we are interested in. But there is a catch. It stops at the character right before the end index. When we extracted “Bears”, we had to give 5 as the end index even though the last character on that word (s) is located at index 4. We can also count backwards like this:

> String.slice -21 -1 "Bears. Beets. Battlestar Galactica."
"Battlestar Galactica" : String

> String.slice -28 -23 "Bears. Beets. Battlestar Galactica."
"Beets"

> String.slice -35 -30 "Bears. Beets. Battlestar Galactica."
"Bears"

Counting backwards can be confusing. Because of how the end index works, we can’t extract the last dot (.) if we count from the back. -1 is the index of the last dot, but slice only extracts characters up until the index before it. If we try to use 0 as the end, we get an empty string. Let’s just stick with the positive indices.

> String.slice -21 0 "Bears. Beets. Battlestar Galactica."
""

Replacing Substrings

The String module doesn’t provide an easy way to replace substrings yet. We will have to use regular expressions for that. The next section is all about them.

We have only covered some of the most commonly used functions for manipulating strings. The String module provides plenty more functions. You can learn all about them here.

Back to top
Close