Getting Lazy with C++
bartoszmilewski.com
bartoszmilewski.com
But I have come across Milewski before since I started on C++, he has a clever way of explaining programming concepts while tending to avoid the usual "is a, has a".
A couple of months ago I gave Haskell a decent go, and I sort of got my head round the basics of being a lazy coder.
Take a never ending list:
let t=[1..]
Okay, fairly compact notation. I think of this code as declaring blueprints of the list in order to create it when someone demands it.That's alright, as long as those blueprints take up substantially less space in memory than the list itself. In the example above, the entire list can be generated by a starting value and a function to append the previous value incremented by unity to the end of the list; the data exhibits the ability to be compressed infinitely. The same can be applied to many other simple lists, like the set of all square numbers. This is an efficient use of memory.
When the function required to create a certain list becomes comparable in size to the resultant list, this advantage is lost. Luckily, those lists are fairly rare - white noise would be a pathological example.
I wasn't aware of that, cheers for the heads up.
In a lot of simpler, lower-performance cases you _can_ get away with just evaluating the same function with the same inputs repetitively, which doesn't require these tools, but having the "next step" available makes it possible to apply that strategy in more places.
My solution completely avoids dynamic memory allocation--even the stack allocation is just a handful of bytes per generated triple.
You're right that lazy evaluation is useful for performance, but TFA acknowledges that its own implementation is not efficient because it dynamically allocates memory repeatedly. This is not something we can hand-wave away: it's at the core (no pun intended) of high performance in C++ and many other languages.
Many of Bartosz' discussions focus on concurrency. It is one of the big and interesting problems we have at the moment. The criticism repeated here that "No one will ever be able to debug it" and the like is exactly what Bartosz sees in our future if we continue to program concurrent systems the way we do. He favours adoption of functional programming concepts to manage the complexity of concurrent systems. And I agree.
If code similar to the code in his blog is not acceptable in production code, a code review should make sure it never reaches production. An informed discussion about the pros and cons of such code could ensue during the code review, new insights are gained and everyone walks away a better, more knowledgeable programmer. I suspect this is one of Bartosz main goals.
Seriously. I like experiments, but just keep this away from production code.
An article like that should start with: "unless you really really know what you are doing and are an adult (20+ years of coding) you should probably avoid using techniques like that in the production code."
Experts can use them for disastrous consequences as well. This is C++ we're talking about here!
Although having over 7 years experience as a c++ programmer, since I'm not using it so much now I haven't managed to keep up so much with the changes in c++11 and feel a bit noviced overnight.
Still, move constructors are pretty handy so that's one change I use whenever I can.
The example of the article was simple of course. But solving the problem of printing Pythagorean triples was not the aim of the article. The aim was to show what makes lazy streams tick, to show that they can be implemented in C++ and that an uniform interface can be provided with functor, monoid and monad. Finally, the simple problem was solved with the new tool to show the inversion of control.
PS. std::vector is a stateful functor.
But, the mail point of the post was, that if you write an article like that, it should start from a warning to novices. This point of view is based on the experience of having to deal with fuckups from novice developers, who were trying to use and abuse the language to the full extends of their abilities. Including fuckups of my own.
PS. std::vector is a container.
I've seen people saying 10 years of experience when they're closer to have 10 times one year of experience since they continuously do the same thing over and over. I've also seen people with barely a year or two of experience already coding circles around people with 10+ years.
Isn't that just C++ in general?
https://github.com/BartoszMilewski/Okasaki/blob/master/Tripl...
A coroutine is created once and it yields control to its "caller" each time it produces an item. To implement a coroutine, you only need to save its stack (local variables, instruction pointer).
He created a lazy stream: instead of yielding control for each item produced, it makes a linked list of items where each node is created on demand.
Thus, coroutines only return the "next" item, while streams compute and store the history of the whole computation.
Have you ever used STL? It is full of template classes! It is a LOT of code in there.
The author implemented a stream processing framework based on lazy evaluation and an interface taken straight out of functional programming (Haskell) approach.
Tidy it up, make it more robust and why wouldn't it be industry strength and ready for production code?
#include <lazy_streams>
//3 lines of code to implement the original problem
What would be wrong with that?I think C++ more than (m)any other language(s) sufferes from not-invented-here syndrome. If something is not in core language or STL then it might as well not exist -- people will always do their own thing from scratch. Or you grab some behemoth library like Boost which should cater to all your needs.
In contrast in Haskell/OCaml/Clojure (probably others too but those I'm most familiar with) one can download a tiny library that allows to radically change the control flow of the program.
What's demonstrated here is one such thing. It is based on sound principles (monads, functors...) and lazy streams which is a very well researched topic.
Sure, this is C++ we are talking about and there sure will be dragons somewhere. But if Bartosz ever dedicates his time to make a full fledged and tested library I will be damn sure to use it!
You could, but the control is still driven by printNTriples. What if you had to break early, before generating all N triples? In this simple case, you could use a return value from the callback to decide whether to continue or not.
In a more general case, you would you would encapsulate the state into a class (for example, the x,y,z,i locals from the original function would become instance memebers.)
I’ve been working in C++ for about ten years, since a couple years after I started programming. It’s certainly gotten better over time—particularly in terms of containers and ownership semantics—but it’s still not good exactly. For instance, it’s possible for mortals to write exception-safe code now, but still not memory-safe code.
I spend an inordinate of time language-lawyering for my coworkers to avoid future debugging hell, and though in my opinion I don’t even know the standard all that well, I’m certainly among maybe a few dozen people at my company who come close to knowing it in full.
It’s just an absurd source of artificial expertise that needs to die.
It seems to me that you barely have to think about memory ownership anymore ever since unique_ptr and shared_ptr came along.
Also, I think Rust has kind of nailed ownership semantics - makes my life a lot easier when the compiler assists me with those things.
But seriously, while this is toy example, it demonstrates what perhaps will be viable for standard library or for the future language features. Have you tried to read STL implementation code? - it's pretty incomprehensible, but it's still extremely useful library.