Skip to content

Commit

Permalink
Move print example to the examples section
Browse files Browse the repository at this point in the history
  • Loading branch information
pfultz2 committed Apr 24, 2016
1 parent 2ad4009 commit 00d18cf
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 135 deletions.
212 changes: 212 additions & 0 deletions doc/src/example_print.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
Print function
==============

Say, for example, we would like to write a print function. We could start by writing the function that prints using `std::cout`, like this:

FIT_STATIC_LAMBDA_FUNCTION(print) = [](const auto& x)
{
std::cout << x << std::endl;
};

However, 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.

Overloading
-----------

Now, Fit provides two ways of doing overloading. The [`match`](match.md) adaptor will call a function based on C++ overload resolution, which tries to find the best match, like this:

FIT_STATIC_LAMBDA_FUNCTION(print) = match(
[](int x)
{
std::cout << "Integer: " << x << std::endl;
},
[](const std::string& x)
{
std::cout << "String: " << x << std::endl;
}
);

However, when trying to do overloading involving something more generic, it can lead to ambiguities.

The Fit library provides several ways to do overloading. One of the ways is with the [`conditional`](conditional.md) 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:


FIT_STATIC_LAMBDA_FUNCTION(print) = conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range)
{
for(const auto& x:range) std::cout << x << std::endl;
}
);

The `-> 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 called with a vector:

std::vector<int> v = { 1, 2, 3, 4 };
print(v);

This will print each element in the vector.

We 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:

namespace adl {

using std::begin;

template<class R>
auto adl_begin(R&& r) FIT_RETURNS(begin(r));
}

Now we can add `-> decltype(std::cout << *adl::adl_begin(range), void())` to the second function to constrain it to ranges:

FIT_STATIC_LAMBDA_FUNCTION(print) = conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
{
for(const auto& x:range) std::cout << x << std::endl;
}
);

So now calling this will work:

std::vector<int> v = { 1, 2, 3, 4 };
print(v);

And print out:

1
2
3
4

Tuples
------

We 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 [`by`](by.md) adaptor will let us apply a function to each argument passed in, and the [`unpack`](unpack.md) adaptor will unpack the elements of a tuple and apply them to the function:

FIT_STATIC_LAMBDA_FUNCTION(for_each_tuple) = [](const auto& sequence, auto f)
{
return unpack(by(f))(sequence);
};

So now if we call:

for_each_tuple(std::make_tuple(1, 2, 3), [](auto i)
{
std::cout << i << std::endl;
});

This will print out:

1
2
3

We can integrate this into our `print` function by adding an additional overload:

FIT_STATIC_LAMBDA_FUNCTION(print) = conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
{
for(const auto& x:range) std::cout << x << std::endl;
},
[](const auto& tuple)
{
for_each_tuple(tuple, [](const auto& x)
{
std::cout << x << std::endl;
});
}
);

So now we can call `print` with a tuple:

print(std::make_tuple(1, 2, 3));

And it will print out:

1
2
3

Recursive
---------

Even 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`](fix.md) adaptor. This implements a fix point combinator, which passes the function(i.e. itself) in as the first argument.

So now we add an additional arguments called `self` which is the `print` function itself. This extra argument is called by the [`fix`](fix.md) adaptor, and so the user would still call this function with a single argument:

FIT_STATIC_LAMBDA_FUNCTION(print) = fix(conditional(
[](auto, const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
{
for(const auto& x:range) self(x);
},
[](auto self, const auto& tuple)
{
return for_each_tuple(tuple, self);
}
));

This will let us print nested structures:

std::vector<int> v = { 1, 2, 3, 4 };
auto t = std::make_tuple(1, 2, 3, 4);
auto m = std::make_tuple(3, v, t);
print(m);

Which outputs this:

3
1
2
3
4
1
2
3
4

Variadic
--------

We can also make this `print` function variadic, so it prints every argument passed into it. We can use the [`by`](by.md) adaptor, which already calls the function on every argument passed in. First, we just rename our original `print` function to `simple_print`:

FIT_STATIC_LAMBDA_FUNCTION(simple_print) = fix(conditional(
[](auto, const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
{
for(const auto& x:range) self(x);
},
[](auto self, const auto& tuple)
{
return for_each_tuple(tuple, self);
}
));

And then apply the [`by`](by.md) adaptor to `simple_print`:

FIT_STATIC_LAMBDA_FUNCTION(print) = by(simple_print);

Now we can call `print` with several arguments:

print(5, "Hello world");

Which outputs:

5
Hello world
4 changes: 2 additions & 2 deletions doc/src/point_free.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ Of course, this puts all the output together, but we can further extend this to

FIT_STATIC_FUNCTION(print_lines) = by(flow(simple_print, _ << std::integral_constant<char, '\n'>{}));

The [flow](flow.md) adaptor does function composition but the functions are called from left-to-right. That is `flow(f, g)(x)` is equivalent to `g(f(x))`. So in this case, it will call `simple_print` on the argument which returns `std::cout` and then pass that to the next function which call the stream with the newline character. In the above, we write `std::integral_constant<char, '\n'>{}` instead of just `'\n'` because the function is statically defined, so all values must be defined statically.
The [flow](flow.md) adaptor does function composition but the functions are called from left-to-right. That is `flow(f, g)(x)` is equivalent to `g(f(x))`. So in this case, it will call `simple_print` on the argument which returns `std::cout` and then pass that to the next function which calls the stream with the newline character. In the above, we write `std::integral_constant<char, '\n'>{}` instead of just `'\n'` because the function is statically defined, so all values must be defined statically.

So now calling `print_lines`:

print_lines("Hello", "World");

Will print out:
It will print out:

Hello
World
Expand Down
125 changes: 0 additions & 125 deletions doc/src/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,128 +127,3 @@ Additionally, adaptors can be used, so the pipable version of `sum` can be writt
return x + y;
});

Overloading
-----------

Now, Fit provides two ways of doing overloading. The [`match`](match.md) adaptor will call a function based on C++ overload resolution, which tries to find the best match, like this:

FIT_STATIC_LAMBDA_FUNCTION(print) = match(
[](int x)
{
std::cout << "Integer: " << x << std::endl;
},
[](const std::string& x)
{
std::cout << "String: " << x << std::endl;
}
);

However, when trying to do overloading involving something more generic, it can lead to ambiguities. So the [`conditional`](conditional.md) adaptor will pick the first function that is callable. This allows ordering the functions based on which one is more important. Say we would like to write a `print` function that can print not only using `cout` but can also print the values in ranges. We could write something like this:


FIT_STATIC_LAMBDA_FUNCTION(print) = conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range)
{
for(const auto& x:range) std::cout << x << std::endl;
}
);

So the `-> decltype(std::cout << x, void())` will only make the function callable if `std::cout << x` is callable. Then the `void()` is used to return `void` from the function. We can constrain the second overload as well, but we will need some helper function in order to call `std::begin` using ADL lookup:

namespace adl {

using std::begin;

template<class R>
auto adl_begin(R&& r) FIT_RETURNS(begin(r));
}

FIT_STATIC_LAMBDA_FUNCTION(print) = conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
{
for(const auto& x:range) std::cout << x << std::endl;
}
);

Tuples
------

We 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 [`by`](by.md) adaptor will let us apply a function to each argument passed in, and the [`unpack`](unpack.md) adaptor will unpack the elements of a tuple and apply them to the argument:

FIT_STATIC_LAMBDA_FUNCTION(for_each_tuple) = [](const auto& sequence, auto f)
{
return unpack(by(f))(sequence);
};

So now we can add an overload for tuples:

FIT_STATIC_LAMBDA_FUNCTION(print) = conditional(
[](const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](const auto& range) -> decltype(std::cout << *adl::adl_begin(range), void())
{
for(const auto& x:range) std::cout << x << std::endl;
},
[](const auto& tuple)
{
for_each_tuple(tuple, [](const auto& x)
{
std::cout << x << std::endl;
});
}
);

Recursive
---------

Even though we are using lambdas, we can easily make this recursive using the [`fix`](fix.md) adaptor. This implements a fix point combinator, which passes the function(i.e. itself) in as the first argument, so we could write this:

FIT_STATIC_LAMBDA_FUNCTION(print) = fix(conditional(
[](auto, const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
{
for(const auto& x:range) self(x);
},
[](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void())
{
return for_each_tuple(tuple, self);
}
));

Variadic
--------

We can also make this `print` function variadic, so it prints every argument passed into it. We just rename our original `print` function to `simple_print`:

FIT_STATIC_LAMBDA_FUNCTION(simple_print) = fix(conditional(
[](auto, const auto& x) -> decltype(std::cout << x, void())
{
std::cout << x << std::endl;
},
[](auto self, const auto& range) -> decltype(self(*adl::adl_begin(range)), void())
{
for(const auto& x:range) self(x);
},
[](auto self, const auto& tuple) -> decltype(for_each_tuple(tuple, self), void())
{
return for_each_tuple(tuple, self);
}
));

And then apply the [`by`](by.md) adaptor to `simple_print`:

FIT_STATIC_LAMBDA_FUNCTION(print) = by(simple_print);

4 changes: 2 additions & 2 deletions example/print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,6 @@ int main()
auto t = std::make_tuple(1, 2, 3, 4);
print(t);

// auto m = std::make_tuple(3, v, t);
// print(m);
auto m = std::make_tuple(3, v, t);
print(m);
}
14 changes: 8 additions & 6 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ extra_javascript: [js/configure.js]
theme: readthedocs
pages:
- Home: 'index.md'
- About:
- Introduction:
- 'Quick Start': 'quickstart.md'
- 'Point-free style' : 'point_free.md'
- 'Definitions' : 'definitions.md'
- 'Basic Concepts' : 'concepts.md'
- Examples:
- 'Print': 'example_print.md'
- 'Conditional overloading': 'example_overloading.md'
- 'Polymorphic constructors': 'example_polymorphic_constructors.md'
- 'More examples': 'more_examples.md'
- About:
- 'Configurations' : 'configurations.md'
- 'Point-free style' : 'point_free.md'
- 'FAQ': 'faq.md'
- 'Building': 'building.md'
- 'Acknowledgements': 'acknowledgements.md'
- 'License': 'license.md'
- Examples:
- 'Conditional overloading': 'example_overloading.md'
- 'Polymorphic constructors': 'example_polymorphic_constructors.md'
- 'More examples': 'more_examples.md'
- Adaptors:
- 'by': 'by.md'
- 'compose': 'compose.md'
Expand Down

0 comments on commit 00d18cf

Please sign in to comment.