1<!-- Copyright 2018 Paul Fultz II
2     Distributed under the Boost Software License, Version 1.0.
3     (http://www.boost.org/LICENSE_1_0.txt)
4-->
5
6Print function
7==============
8
9Say, for example, we would like to write a print function. We could start by writing the function that prints using `std::cout`, like this:
10
11    BOOST_HOF_STATIC_LAMBDA_FUNCTION(print) = [](const auto& x)
12    {
13        std::cout << x << std::endl;
14    };
15
16However, there is lot of things that don't print directly to `std::cout` such as `std::vector` or `std::tuple`. Instead, we want to iterate over these data structures and print each element in them.
17
18Overloading
19-----------
20
21Boost.HigherOrderFunctions provides several ways to do overloading. One of the ways is with the [`first_of`](/include/boost/hof/conditional) adaptor which will pick the first function that is callable. This allows ordering the functions based on which one is more important. So then the first function will print to `std::cout` if possible otherwise we will add an overload to print a range:
22
23
24    BOOST_HOF_STATIC_LAMBDA_FUNCTION(print) = first_of(
25        [](const auto& x) -> decltype(std::cout << x, void())
26        {
27            std::cout << x << std::endl;
28        },
29        [](const auto& range)
30        {
31            for(const auto& x:range) std::cout << x << std::endl;
32        }
33    );
34
35The `-> decltype(std::cout << x, void())` is added to the function to constrain it on whether `std::cout << x` is a valid expression. Then the `void()` is used to return `void` from the function. So, now the function can be called with a vector:
36
37    std::vector<int> v = { 1, 2, 3, 4 };
38    print(v);
39
40This will print each element in the vector.
41
42We can also constrain the second overload as well, which will be important to add more overloads. So a `for` range loop calls `begin` and `end` to iterated over the range, but we will need some helper function in order to call `std::begin` using ADL lookup:
43
44    namespace adl {
45
46    using std::begin;
47
48    template<class R>
49    auto adl_begin(R&& r) BOOST_HOF_RETURNS(begin(r));
50    }
51
52Now we can add `-> decltype(std::cout << *adl::adl_begin(range), void())` to the second function to constrain it to ranges:
53
54    BOOST_HOF_STATIC_LAMBDA_FUNCTION(print) = first_of(
55        [](const auto& x) -> decltype(std::cout << x, void())
56        {
57            std::cout << x << std::endl;
58        },
59        [](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
60        {
61            for(const auto& x:range) std::cout << x << std::endl;
62        }
63    );
64
65So now calling this will work:
66
67    std::vector<int> v = { 1, 2, 3, 4 };
68    print(v);
69
70And print out:
71
72    1
73    2
74    3
75    4
76
77Tuples
78------
79
80We could extend this to printing tuples as well. We will need to combine a couple of functions to make a `for_each_tuple`, which lets us call a function for each element. First, the [`proj`](/include/boost/hof/by) adaptor will let us apply a function to each argument passed in, and the [`unpack`](/include/boost/hof/unpack) adaptor will unpack the elements of a tuple and apply them to the function:
81
82    BOOST_HOF_STATIC_LAMBDA_FUNCTION(for_each_tuple) = [](const auto& sequence, auto f)
83    {
84        return unpack(proj(f))(sequence);
85    };
86
87So now if we call:
88
89    for_each_tuple(std::make_tuple(1, 2, 3), [](auto i)
90    {
91        std::cout << i << std::endl;
92    });
93
94This will print out:
95
96    1
97    2
98    3
99
100We can integrate this into our `print` function by adding an additional overload:
101
102    BOOST_HOF_STATIC_LAMBDA_FUNCTION(print) = first_of(
103        [](const auto& x) -> decltype(std::cout << x, void())
104        {
105            std::cout << x << std::endl;
106        },
107        [](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
108        {
109            for(const auto& x:range) std::cout << x << std::endl;
110        },
111        [](const auto& tuple)
112        {
113            for_each_tuple(tuple, [](const auto& x)
114            {
115                std::cout << x << std::endl;
116            });
117        }
118    );
119
120So now we can call `print` with a tuple:
121
122    print(std::make_tuple(1, 2, 3));
123
124And it will print out:
125
126    1
127    2
128    3
129
130Recursive
131---------
132
133Even though this will print for ranges and tuples, if we were to nest a range into a tuple this would not work. What we need to do is make the function call itself recursively. Even though we are using lambdas, we can easily make this recursive using the [`fix`](/include/boost/hof/fix) adaptor. This implements a fix point combinator, which passes the function(i.e. itself) in as the first argument.
134
135So now we add an additional arguments called `self` which is the `print` function itself. This extra argument is called by the [`fix`](/include/boost/hof/fix) adaptor, and so the user would still call this function with a single argument:
136
137    BOOST_HOF_STATIC_LAMBDA_FUNCTION(print) = fix(first_of(
138        [](auto, const auto& x) -> decltype(std::cout << x, void())
139        {
140            std::cout << x << std::endl;
141        },
142        [](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
143        {
144            for(const auto& x:range) self(x);
145        },
146        [](auto self, const auto& tuple)
147        {
148            return for_each_tuple(tuple, self);
149        }
150    ));
151
152This will let us print nested structures:
153
154    std::vector<int> v = { 1, 2, 3, 4 };
155    auto t = std::make_tuple(1, 2, 3, 4);
156    auto m = std::make_tuple(3, v, t);
157    print(m);
158
159Which outputs this:
160
161    3
162    1
163    2
164    3
165    4
166    1
167    2
168    3
169    4
170
171Variadic
172--------
173
174We can also make this `print` function variadic, so it prints every argument passed into it. We can use the [`proj`](/include/boost/hof/by) adaptor, which already calls the function on every argument passed in. First, we just rename our original `print` function to `simple_print`:
175
176    BOOST_HOF_STATIC_LAMBDA_FUNCTION(simple_print) = fix(first_of(
177        [](auto, const auto& x) -> decltype(std::cout << x, void())
178        {
179            std::cout << x << std::endl;
180        },
181        [](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
182        {
183            for(const auto& x:range) self(x);
184        },
185        [](auto self, const auto& tuple)
186        {
187            return for_each_tuple(tuple, self);
188        }
189    ));
190
191And then apply the [`proj`](/include/boost/hof/by) adaptor to `simple_print`:
192
193    BOOST_HOF_STATIC_LAMBDA_FUNCTION(print) = proj(simple_print);
194
195Now we can call `print` with several arguments:
196
197    print(5, "Hello world");
198
199Which outputs:
200
201    5
202    Hello world
203