niedziela, 29 kwietnia 2012

ACCU 2012 - Random thoughs

I consider this post as a kind of small conclusions after ACCU 2012 conference in Oxford, UK I took part in.


1. Python, range, and for-loop
Those who are familiar with python syntax are not surprised with following listing:
for i in range (10):
    print i
It's python, so nothing interresting already happened. Here's the question: how can we achieve similar syntax in C++?
I believe you agree with me that this is not possible in C (unless you use ninja pre-processor macros). C++2003 doesn't help too much here, even with template stuff. However, in C++11 (with origin library written by Andrew Sutton) it seems to be possible:
for (const auto& i: bounded_range<int>(1, 10))
{
    std::cout << i << std::endl;
}
When I saw presentation about origin library for the first time I though "WAT?!"[1]. After some time I started to realize how much C++11 will change capatibilities of C++ language at all. Now I wonder if following snippets could also be rewritten to C++11 with minimal syntax changes :-).
for i, j in enumerate (alphabet): # alphabet = ['a', 'b', ..., 'z']
    print i, j
for i in zip(range(1, 10), range(10, 20)):
    print i

2. Funny fact about endl
Interresting thing: if we dig enough deep into implementation of C++ STL, we are likely to find something like that:
template<class charT, class traits>
inline basic_ostream<charT, traits>& endl(basic_ostream<charT, traits>& os)
{
    os.put(os.widen('\n'));
    os.flush();
    return os;
}
I'm aware that this isn't hitting performance and makes code less clear, but instead of:
std::cout << "Hello, world!" << std::endl;
you can actually write
std::endl(std::cout << "Hello, world!");
not to say that std::endl is generally slower than simple "\n";

3. Designing APIs: two important rules
Designing user-friendly, error-prone, easy, understandable, and eventually functional API is very hard task. But when you do, you must follow specific rules so final API is not a crap. Besides other rules I find these are also very important, but sometimes not followed:
  • do not mix abstraction level in your API
  • do not provide two functions which essentially perform the same work
And now time comes for rationale. In the first case when you accidentally mix abstraction level, users of your library might end up with something similar to following.
Scene scene;
Object object;
scene.AddObject(object);
Renderer.Render(screen, scene);
Renderer.PutPixel(0, 1, Color(255, 255, 0));
Renderer.SwapFramebuffers();
In above snippet we can easily see that Renderer class should perform high-level tasks related to rendering some sort of scene with objects. However, the API mix abstraction levels, so user is confused and starts using methods that manipulate on single pixels. After that, another method SwapFramebuffers is evaluated, but at this level it should have been hidden from the user. If the user wanted more control about what's displayed on the screen, he would access some lower layer instead of Renderer class, which is supposed to do the work without much effort spent on configuring things.

Not following second rule may be even worse. Consider following example.
Scene scene;
ObjectManager objectManager;
Object object;
// Adding object to scene, case I
scene.AddObject(object);
// Adding object to scene, case II
objectManager.pushObjectToScene(object, scene);
// Rendering scene, case I
Renderer.Render(screen, scene);
// Rendering scene, case II
scene.AssignScreen(screen);
Renderer.Render(scene);
Above snippet is quite confusing, isn't it? Basically, when you create a lot of paths in your API that lead to the same effect you can accidentally confuse library users. In effect instead of giving possibilities you make library less-understandable and less-convenient.


I think that would be all I got for this time. Hope that helps you in any area.