A REPL session of C++ functional programming, using fplus

A Read-Eval-Print-Loop makes functional programing very proficient. This notebook demonstrates it. If you are new to functional programming, it is recommended to view it as a slideshow.

FunctionalPlus is a Functional Programming Library for C++. Some docs:

First, include fplus, which is a header only library.

In [1]:
#pragma cling add_include_path("../external/FunctionalPlus/include")
#include <fplus/fplus.hpp>

Read some text

Let's try to read some text.

Note: If you do not end your line with a ";", cling will output the result of the last computation

In [2]:
using namespace std;
fplus::read_text_file("data/if.txt") // no ";"
Out[2]:
@0x6ff77f0

Wow, where is our text ? fplus::read_text_file did not return a string!

The reason for this is that fplus is a functional library, so that fplus::read_text_file(filename) does not perform the side effect : instead it returns a function which you need to invoke in order to perform the side effect.

Let's try again, and invoke the function : see the () at the end below

In [3]:
auto poem = fplus::read_text_file("data/if.txt")();
poem
Out[3]:
"If you can keep your head when all about you
 Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
 But make allowance for their doubting too.
If you can wait and not be tired by waiting,
 Or being lied about, don't deal in lies,
Or being hated, don't give way to hating,
 And yet don't look too good, nor talk too wise:
"

Split the text into lines

In [4]:
// Let's try to split some lines
const auto lines = fplus::split_lines(poem, false);
input_line_16:3:20: error: no matching function for call to 'split_lines'
const auto lines = fplus::split_lines(poem, false);
                   ^~~~~~~~~~~~~~~~~~
../external/FunctionalPlus/include/fplus/string_tools.hpp:74:14: note: candidate function not viable: no known conversion from 'std::__cxx11::basic_string<char, std::char_traits<char>,
      std::allocator<char> >' to 'bool' for 1st argument
ContainerOut split_lines(bool allowEmpty, const String& str)
             ^
Interpreter Error: 

Wow, I must have typed something wrong. Let's lookup split_linesin the API (search for "split_lines"), or on sourcegraph

In [5]:
// Ha, the order of the params was wrong. Let's try again
const auto lines = fplus::split_lines(false, poem);
lines
Out[5]:
{ "If you can keep your head when all about you", " Are losing theirs and blaming it on you,", "If you can trust yourself when all men doubt you,", " But make allowance for their doubting too.", "If you can wait and not be tired by waiting,", " Or being lied about, don't deal in lies,", "Or being hated, don't give way to hating,", " And yet don't look too good, nor talk too wise:" }

Much better!

Modify the text word by word

Let's try to modify this poem, by applying a function that changes all letter of each word to lowercase, except the first letter of each word.

In [6]:
// This is the function we want to apply to each word
std::string capitalize_first_letter(const std::string & word) {
    auto result = fplus::to_lower_case(word);
    result[0] = toupper(result[0]);
    return result;
}

capitalize_first_letter("hello")
Out[6]:
"Hello"

Let's start with the first line

First attempt : step by step

With the code below, we can split the first line into words.

In [7]:
const std::string first_line = lines[0];
const auto words = fplus::split<std::string>(' ', false, first_line);
words
Out[7]:
{ "If", "you", "can", "keep", "your", "head", "when", "all", "about", "you" }

How to transform all these words using capitalize_first_letter? We will be using fplus::transform : it applies a given transformation to all the elements of a container. See it's documentation at http://www.editgym.com/fplus-api-search/

In [8]:
auto words_transformed = fplus::transform(capitalize_first_letter, words);
words_transformed
Out[8]:
{ "If", "You", "Can", "Keep", "Your", "Head", "When", "All", "About", "You" }

Then, we need to join our transformed words:

In [9]:
auto first_line_transformed = fplus::join(std::string(" "), words_transformed);
first_line_transformed
Out[9]:
"If You Can Keep Your Head When All About You"

So, the final result for the first line could be written:

In [10]:
fplus::join( std::string(" "), 
             fplus::transform(capitalize_first_letter, 
                              fplus::split<std::string>(' ', false, first_line) 
                             )  
           )
// See how our lines are going towards the right of the screen: this is because
// we are composing three functions calls: efficient, but not very readable
Out[10]:
"If You Can Keep Your Head When All About You"

Second attempt : using higher order functions

apply_by_words is a higher order function that will transform a function f into another function that will apply f word by word.

In [11]:
// Here we are composing three functions in a much more readable way
// We can even transform with any other function (to_upper, etc)! 
auto apply_by_words = [](auto f) {
    return fplus::fwd::compose(
        fplus::fwd::split(' ', false),
        fplus::fwd::transform(f),
        fplus::fwd::join(std::string(" "))
    );
 };;
  • The fplus::fwd namespace contains partially curried version of the functions. This is useful for composition. See doc here
  • the double ";;" after the lambda definition is important. This is a known bug in cling : see Advices And Gotchas
In [12]:
// Now let's instantiate apply_by_words with capitalize_first_letter
// cap_words will be a lambda function of type : string -> string
auto cap_words = apply_by_words(capitalize_first_letter);
In [13]:
// And let's try it
cap_words(lines[0])
Out[13]:
"If You Can Keep Your Head When All About You"

And now, let's transform all the lines

apply_by_lines is another higher order function that will transform a function f into another function that will apply f line by line.

In [14]:
// Let's continue
auto apply_by_lines = [](auto f) {
    return fplus::fwd::compose(
        fplus::fwd::split_lines(false),
        fplus::fwd::transform(f),
        fplus::fwd::join(std::string("\n"))
    );
 };; // the double ;; is voluntary here (bug in cling !)
In [15]:
auto cap_text = apply_by_lines(cap_words);;
In [16]:
// And now let's apply this to the complete poem
cap_text(poem)
Out[16]:
"If You Can Keep Your Head When All About You
Are Losing Theirs And Blaming It On You,
If You Can Trust Yourself When All Men Doubt You,
But Make Allowance For Their Doubting Too.
If You Can Wait And Not Be Tired By Waiting,
Or Being Lied About, Don't Deal In Lies,
Or Being Hated, Don't Give Way To Hating,
And Yet Don't Look Too Good, Nor Talk Too Wise:"

Let's make it a program : use fplus::interact

fplus::interactis a higher order function that transforms a function of type string -> string into a program that read it's output from stdin and writes it output to stdout.

In [17]:
auto prog = fplus::interact(cap_text);

Let's examine prog:

In [18]:
prog
Out[18]:
@0x7f81a7ce9168

So, prog is itself a lambda. In order to construct a program, we need to call it inside main, like so:

In [19]:
int main() {
    prog();
}

So, a full program that would apply our transformation to stdin and write to stdout could be written as below. (beware, this code can not be used inside this page, you need to copy / paste it into a cpp file).

See: capitalize_interact.cpp

#include <fplus/fplus.hpp>

std::string capitalize_first_letter(const std::string & word) {
    auto result = fplus::to_lower_case(word);
    result[0] = toupper(result[0]);
    return result;
}

int main()
{
  auto apply_by_words = [](auto f) {
    return fplus::fwd::compose(
        fplus::fwd::split(' ', false),
        fplus::fwd::transform(f),
        fplus::fwd::join(std::string(" "))
    );
   };
  auto apply_by_lines = [](auto f) {
      return fplus::fwd::compose(
          fplus::fwd::split_lines(false),
          fplus::fwd::transform(f),
          fplus::fwd::join(std::string("\n"))
      );
  };

  auto cap_words = apply_by_words(capitalize_first_letter);
  auto cap_text = apply_by_lines(cap_words);
  auto prog = fplus::interact(cap_text);

  prog();
}