C++ is great and powerful, but lacks some very basic
functions like string.split, vector.group_by, etc.
This library allows you to write them
in C++ like in Ruby. Syntactic sugar is sweeeet!
It's basically a wrapper for std::string and some
containers, extending them with the functionality of
their counterpart in Ruby.
I'm conservative and trying to stay compatible with native C++. For example, elements are not assumed to always be pointers, and there's no universal parent type "Object".
In addition, there are some experimental features
that I find useful but not necessarily supported by
Ruby, such as do_once, Algebraic Effects, etc.
See test.cpp.
If you don't use Algebraic Effects: A compiler that supports C++11
If you use Algebraic Effects: A compiler that supports C++2a (for type deduction)
Drop it anywhere in your PATH.
Include rubify.hpp.
Add rubify.cpp to your source file for compiling.
Rubify::string derives std::string, and is equivalence of
String in Ruby.
Rubify::vector derives std::vector, and is equivalence of
Array in Ruby.
Rubify::map derives std::map, and is equivalence of
Hash in Ruby.
C++ lambda is equivalence of block in Ruby.
If the std container already has a function,
(e.g., push_back), it won't be overridden(e.g. by append).
Otherwise, the function name and
behavior will be the same as in Ruby(if possible).
! character is not allowed in C++ as identifier,
so it's replaced by _ (underscore). ? is not allowed either.
But it incurs no ambiguity. So it's simply removed.
Container elements are copied by value, unlike in Ruby, where everything is an object. If you want things to work the Ruby way, pass around pointers for everything.
Types can't be mixed up in a container, nor can elements'
type be changed(e.g. by map_), unless you define everything
as subtype of an "Object" class like in Ruby.
To break or continue the iteration within a lambda, break
and continue won't work. Use break_ and continue_ instead.
Currently only supported in each. Usage in other situations
will cause an exception.
A macro _S_ is defined for string interpolation.
_S_( exp ) will evaluate exp and turn the result
into a string. exp's result type must either has to_s()
function defined or can be fed to stringstream.
If you are too lazy to type the surrounding +
symbol, use S_ instead.
An example:
a = 1;
b = 2;
cout << "" S_(a) " plus " S_(b) " equals " S_(a+b) "" << endl;
Output:
1 plus 2 equals 3
You can undefine puts if it's useless for you.
I'm actually not a super fan of FP. My knowledge about this mechanism mainly comes from this article. And here is a Chinese translation. I recommend reading the article for a better understanding of it.
There are 2 keywords in my implementation: require_ and provide_.
require_ is like throw. It throws a requirement that can be caught
by provide_ block (similar to catch). The interface require_(Want, name)
throws a requirement with return type of Want. name is literally the name
of the required thing. It can be of any type.
provide_(handler) can catch a requirement and feed it to handler, which
is a function pointer or a lambda. After calling require_(Want, name),
the first handler up in the stack that accepts
name (type must be the same) and returns a value of Want type will catch the
thrown requirement. If the requirement can be handled, the program will jump
back to the place require_ is called and replace the statement with the
return value. Otherwise the handler can call return require_(Want, name);
to throw the requirement again to upper levels. If no handler in the stack
can handle it, name will be thrown as an exception.
Both operations are thread safe. Each thread is provided with its own
context. When a new thread is created, nothing is derived from the parent
thread and the initial stack is empty. If you want to require something from
another thread (e.g. worker from master), use require_from_(thread_id, Want, name)
and the other thread's current context will be used. This may be dangerous,
so be careful with it.
One example can be found in test.cpp where a recursive function dive into
multiple levels and require a value provided by an upper level from the bottom.
Read comments in real_rubify.hpp for more details.
Some tips on using C++ if you've never used C++11 or later:
A lambda is essentially a function pointer, but can
optionally capture local context.
The most brainless way to define a lambda is like this:
[&]( $arguments ) -> $return_type {
$body
}
It captures all local contexts and should work in most cases.
Try to use auto feature in C++. It saves you a lot of
effort figuring out and typing the proper type name.
For example:
auto add = [&](int a, int b) -> int {
return a+b;
};
auto c = add(1, 2);
String interpolation
S__S_
Short circuit
continue_break_
vector:
[](support negative index)++=&*takedropeach(with/without index)group_bysort_bysort_by_map(with/without index)map_(with/without index)selectselect_zipnorm_zip(duplicate the shorter vector's elements to make them equally long, then zip)flattento_sjointo_h
string:
<(comparison)split
map:
eachto_ato_s
Algebraic effects
provide_require_
Helper functions
do_oncedo_a_few_timesdo_at_intervaldo_a_few_times_at_interval
TODO:
- more functions
Enumerator- conversion between types