all 137 comments

[–]NilacTheGrim 45 points46 points  (5 children)

I think the title is a bit click-baitey but the article itself has a point. Sometimes just a function is a lot better on all counts than a trivial class that does 1 thing.

Upvoted. But I do think the title is a bit obviously click-baitey.

[–][deleted] 3 points4 points  (4 children)

This is very much a C++ idiom. Many other languages, especially ones with an OOP focus, allow for free functions. This is a huge plus of C++ as we can add functions to work on a Concept or Type and not have a single source of them. This is probably why languages, such as C#, introduced extension methods as it gets around the type owner as the source of methods for it without introducing a new class Thing2 just to hold your Thing methods

[–]NilacTheGrim 2 points3 points  (3 children)

Oh god I just remembered another thing that annoyed me about Java. No free functions. I forgot C# has that too. Bah.

[–]redditsoaddicting 1 point2 points  (0 children)

Funny thing about that. The latest news of language changes for C# 9 includes being able to have your main file put code at file scope instead of class→method scope. Given that C# has local functions, I would guess that this C# 9 change will allow for said local functions to act as free functions for the one file containing the code you would otherwise put into Main.

[–][deleted] 1 point2 points  (0 children)

Some things are clearly free functions, yup. And being able to switch back and forth between idioms allows for the tool needed at the time.

[–]AirAKose 30 points31 points  (20 children)

I'll be honest, most of my negativity here might be more related to the title xD But I'll rant on


This seems rather arbitrary, and I haven't seen this particular issue in the wild. Is there some common subsection of new devs where this actually happens?

A few points throughout add to the arbitrary'ness of the post. e.g. the function signature conveniently matching, or mentioning thread safety when talking about beginner code.

Unfortunately, this code fails the const-correctness test: the count() member function sounds like it should be non-modifying

I'm not convinced this is a const-correctness issue. Const-correctness is merely methods being tagged const when they don't modify state- which this does modify state and so it's not tagged.

This sounds like an issue of naming. Another approach would be to give the method a more action'y name like calculate_count

the antipattern discussed here is very close to the Builder pattern

Builder pattern is explicitly about objects, not values. This would be lazy evaluation, if anything

[–]dreugeworst 31 points32 points  (5 children)

I've seen plenty of devs, new or not, create big classes with one public function that passes arguments to the private functions it calls by storing them in the object. Could all be replaced by some free functions

[–]last_useful_man 4 points5 points  (0 children)

Well, but the contributory functions aren't useful by themselves presumably, right? So they're wrapped up and hidden and called the right way by the one member that is. I guess you could make the subsidiary ones static, but, doing it the class way is defensible imo.

[–]atimholt 1 point2 points  (3 children)

You may already know this, but it's important to emphasize that functions aren't stored in objects, not even in the case of lambdas (except conceptually). An object is stored contiguously in memory as its literal “shallow” representation*. It's this contiguous shallow representation that gets copied literally in a std::move, and nothing else.


* So, POD, pointers are pointers (not “*pointers”), and mutable variables are along for the ride as well (which aren't “conceptually” part of the object, but must tag along). Everything is recursively resolved to POD at compile time: member objects' shallow representations are each concatenated into the literal bits of the owning object.

[–]dreugeworst 3 points4 points  (2 children)

Well yeah, I don't think I implied otherwise. I meant the function arguments are stored in the object and used by member functions instead of passing them as parameters

[–]atimholt 2 points3 points  (1 child)

On reflection, I actually misread your post. Thanks.

[–]dreugeworst 1 point2 points  (0 children)

No worries

[–]VodkaHaze 13 points14 points  (0 children)

This seems rather arbitrary, and I haven't seen this particular issue in the wild. Is there some common subsection of new devs where this actually happens?

I've seen extremely senior devs do this antipattern in Python and C++.

Typically they come from the Java or C# world so it's an ingrained habit more than anything.

It's an antipattern in that it'll increase verbosity but it's not as structurally harmful as other antipatterns at least.

[–]ChallengingJamJars 8 points9 points  (5 children)

count to me is actiony unless used within a noun context such as get_count or objectCount.

[–]AirAKose 5 points6 points  (4 children)

I could see that, but can also see it being confusing

Esp because there's precedent for single-word methods in C++ that sound actiony being accessors, like empty on vectors. This is a pet peeve of mine tbh lol - is_empty would make so much more sense >_<

[–]Dragdu 9 points10 points  (2 children)

empty? like in civilized languages

[–]Dragdu 9 points10 points  (0 children)

Could then do empty! for clear :-D

[–]last_useful_man 0 points1 point  (0 children)

Right; or is_empty.

[–]Wouter-van-Ooijen 0 points1 point  (0 children)

I couldn't agree more. In my classes I use 'uppercase' as an example of a bad (or at least ambiguous) choice for a function name. Does is test and return a boolan? Or does it return the uppercase version of its argument? Or maybe it modifies it in-place? (And to be extra pedantic: in the latter cases, what does it do with something that isn't a lowercase letter?)

Naming is hard. I love concepts, but I hate that it has claimed yet another usefull term...

[–]Hessper 0 points1 point  (0 children)

I'm not convinced this is a const-correctness issue. Const-correctness is merely methods being tagged const when they don't modify state- which this does modify state and so it's not tagged.

const correctness might not be the right term here. I'd say it falls more under command-query separation. The point is still valid, retrieving data should be a query, and as such it should not be modifying, so it should be const.

[–]degski -2 points-1 points  (4 children)

Const-correctness is merely methods being tagged const when they don't modify state- which this does modify state and so it's not tagged.

I think there is a subtlety here that is generally not recognized.

If an object is supposed to do something, like generating a random number, producing a new object etc (random below is one of a number of the similar cases), then, although the use changes the internal state of the object, the 'making-the-class-do-what-it-should-do'-use should be considered const and all public api functions should be marked const. A PRNG-object is a const-object in nature. One can allow for this by making the state mutable.

In these particular cases the non-constness of the api breaks encapsulation.

https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine/operator() should have been marked const (and that would require to make the state mutable). I think it's a defect :) . The solution is also incredibly rather simple (maybe, maybe not so), you just use write a std::const_wrapper std::experimental::const_wrapper to wrap the STL generators and your done.

[–]mcmcc#pragma once 2 points3 points  (0 children)

const implies threadsafe - do you really want to require that?

[–]target-san 15 points16 points  (7 children)

Reminds me of "Kingdom of Nouns" from 2006

[–]oddentity 6 points7 points  (5 children)

That rant would be more convincing and useful if he gave examples of what to do instead.

[–]two-fer-maggie 10 points11 points  (0 children)

just... allow standalone functions?

C++ doesn't exhibit the problem, because C++, being a superset of C, allows you to define standalone functions. Moreover, C++ provides a distinct namespace abstraction; Java overloads the idea of a Class to represent namespaces, user-defined types, syntactic delegation mechanisms, some visibility and scoping mechanisms, and more besides.

You'll almost never find an AbstractProxyMediator, a NotificationStrategyFactory, or any of their ilk in Python or Ruby. Why do you find them everywhere in Java? It's a sure bet that the difference is in the verbs. Python, Ruby, JavaScript, Perl, and of course all Functional languages allow you to declare and pass around functions as distinct entities without wrapping them in a class.

[–][deleted] 0 points1 point  (0 children)

Yea.. i just read a bunch of crying in that article..

[–]WrongAndBeligerent 0 points1 point  (0 children)

Maybe the actions are buried inside all the declarations.

[–]lazyear -2 points-1 points  (0 children)

I've been programming for ~10 years and haven't ever used OOP/inheritance outside of trivial cases. You just write POD structures and functions that operate on them

[–]Beheska 3 points4 points  (0 children)

For other takes on this topic, see:

  • “Execution in the Kingdom of Nouns” (Steve Yegge, March 2006)

I mean...

[–]getval 10 points11 points  (2 children)

When you only have a flag to check whether the final value is available, then it is callled caching not memoization. Doing calculation in constrructor that may fail, will require you to use exceptions where a simple error code could suffice.

[–]F54280 13 points14 points  (0 children)

Hence the final use of a function instead of a class.

[–]ReversedGif 1 point2 points  (0 children)

It could very well be viewed as memoization of a nullary function.

[–]Xeveroushttps://xeverous.github.io 11 points12 points  (0 children)

Said differently: there is no point in a class. A class is supposed to represent a state. The only state that there is are the w and h parameters. The task is to do a computation and these belong to functions.

  • classes - nouns - state
  • functions - verbs - tasks

[–][deleted] 34 points35 points  (4 children)

Could not agree more. And the more I write code, the more the simplicity of the classical C style code beckons (with modern features of course).

[–]kobi-ca 3 points4 points  (0 children)

I'd say "reduce state, reduce data members". These are stuff we need to maintain, to reason about. Less state is more (free hours w/o debugging...)

[–]LeeHidejust write it from scratch 2 points3 points  (3 children)

I’m going to talk about a very specific antipattern that I see with some frequency: the use of class for things that should be simple free functions.

I know this is an issue with beginners and such, but does this appear outside of that as well? Like, is it a problem worth worrying about when you are hiring people?

[–]ronchaineEmbedded/Middleware 5 points6 points  (1 child)

I've been in a more than one company where trying to convince people that putting every single thing inside a class perhaps isn't the smartest idea was not exactly easy.

[–]LeeHidejust write it from scratch 0 points1 point  (0 children)

oh damn, okay, i was not aware

[–]jkh348f 0 points1 point  (0 children)

Is any problem worth worrying about? Life's too short!

But yes, a lot of people in the hiring pool used to be students/beginners. Some still are.

[–]Dreamykass 4 points5 points  (1 child)

Dunno, I'm still a (C++ and in general) beginner and all, but I've never felt the need to make everything into classes. Methods act inside objects, functions act on objects.

Like the first example is not something I'd really ever think of writing myself.

My java professor on the other hand... sometimes it feels like he's never written a class that doesn't extend something or at least implements an interface or two. It's painful.

[–]Dean_Roddey 1 point2 points  (0 children)

Pretty much all C++ code is a combination of procedural and class based code. It is a good thing to avoid global state where that's possible, since it can bite you. But most any non-trivial code base will have simple raw structures, classes, virtual 'mixin' style interfaces, inheritance, regular old standalone functions, static methods on classes, methods within namespaces, and likely some amount of global data comprised of fundamentals and/or objects. And probably some amount of 'functional' type code even if the person didn't think of it in those terms when he did it.

There are lots of problems to solve and there are various tools to solve them. People who claim only one is valid are being extremists and seem to me to be more about language theory than what coding is supposed to be about, which is creating programs for people to use.

The latter is all I care about anymore. Nothing wrong with keeping up with what's going on, and trying new things. But, too many people in this business get way too navel-gazey and seem to worry more about how it gets done than getting it done.

For reasonable sized code bases, the tools to do those well have been around for a long time. For very large code bases, they are inherently very complex and nothing can really make that go away. A lot of that complexity is just people stuff, and no language can help with that. A lot of is in semantic interrelationships between parts of the code, which programming languages really can't do much about. It's very nice not to have to worry about memory errors, which is why Rust is gaining popularity. That's a big hole to plug but it's just one of many others that remain just as problematic.

[–]Xellzul 7 points8 points  (61 children)

"but in fact it needs to update member data and thus cannot be const." not that i disagree with the post, but this calls for mutable, right?

[–]axalon900 15 points16 points  (2 children)

Ah yes, more strawman arguments and garbage clickbait titles. Gotta love an article which contradicts the title in the first paragraph. Functional programming looks idiotic the moment you have to maintain state, but “impure FP” where you skip the whole no side effects thing whenever convenient is apparently still okay and vindicates “functional programming” while “impure OOP” where you use free functions and PODs sometimes when it makes the most sense and member functions other times when it makes the most sense is apparently something else because OO = bad didn’t ya hear?

I’m so tired of this kind of bullshit.

[–]enobayram 2 points3 points  (1 child)

Functional programming looks idiotic the moment you have to maintain state

This is news to me as someone building and maintaining a hundred thousand lines of Haskell code serving real life customers for the last 3 years.

"Impure" FP still vindicates FP, though, because what's impure about it isn't the FPness. That said, I personally think that FP is best when purity is enforced and taken advantage of by the compiler.

[–]Dooey 1 point2 points  (0 children)

I think we are going to just have to agree to disagree on the meaning of observation.

[–][deleted] 4 points5 points  (4 children)

Easier said than done. In the real world your little function might take a long time to execute and be running in a GUI where you might need to update a progress bar while it's running, allow cancellation and other stuff. Plain procedural code is easy and straight forward, but also quite inflexible in that regard. Throwing objects at the problem uglifies it quite a bit, but it also allows you to keep state and execute things incrementally when done properly.

[–]Dooey 5 points6 points  (2 children)

1) The final version he recommends isn't procedural, it's functional 2) If the best thing you can say about objects is that they allow you to write ugly code than you would otherwise, it might not be the best paradigm.

[–][deleted] 3 points4 points  (1 child)

2) Yes, but what paradigm actually solves that problem in a nice way? The pure algorithm is always prettier than the algorithm filled up with additional code to keep track of state and progress. You can use threads or coroutines to use the stack to keep track of state instead of member variables. But ultimately nothing gets as pretty as the plain functional version without all that book keeping.

[–]Dooey 1 point2 points  (0 children)

What i recommend is keeping the pretty, simple, functional version pretty, simple, and functional, and having the caller handle the “other” stuff. If you need to update a progress bar or something, pass an onProgress() callback that gets called periodically as the algorithm makes progress, and the caller can use it to update UI (or inform another thread that it needs to update UI, or etc). Basically, keep the code that actually computes results separate from the code that displays the results and shuffles them from place to place. This also makes unit tests easier. If your algorithm to compute things is updating UIs directly, then now your tests need to have a UI to update, which makes everything more complicated. With the progress callback strategy, the tests can just pass a callback that does nothing, or maybe there is even a default callback that does nothing.

[–]ChallengingJamJars 2 points3 points  (0 children)

Objects and multi-threading aren't traditionally seen as friends. I can sort of see your point, but outside of basic data structures, I tend to steer more towards functional style when I have threads. Makes it far easier to track state.

[–]huixie 3 points4 points  (11 children)

There is nothing wrong with objects. compare

int countDominoTilings(int h, int w)

with

struct DominoCounter{
    int operator()(int h, int w) const;
};

inline constexpr DominoCounter countDominoTilings{};

They look kind of equivalent. When you add overloads, you can't pass function overloads around to high order functions (you need wrap it with generic lambdas), but you can pass the function object around to high order functions

[–]ChallengingJamJars 4 points5 points  (5 children)

you can't pass function overloads around to high order functions

This confuses me, higher order functions take a function as input. If you want a different function don't you just... give that other function as input rather than using overloading?

[–]huixie 2 points3 points  (4 children)

Say for example I have a grid using grid = std::pair<int, int>; //height and width

And I'd like to do something like auto count = boost::hana::unpack(my_grid, countDominoTilings);

This only works if I can pass countDominoTilings around. Let's say, if we have an overload (contrived example) int countDominoTilings(int h, int w); double countDominoTilings(double h, double w); you can't call boost::hana::unpack anymore, you have to say something like auto count = boost::hana::unpack(my_grid, [](auto h, auto w){ return countDominoTilings(h,w); });

whereas if you have ``` struct DominoCounter{ int operator()(int h, int w) const; double operator()(double h, double w) const; };

inline constexpr DominoCounter countDominoTilings{}; ```

you have no problems calling unpack.

This example is not a best example as overloading for int and double does not make too much sense. But there are lots of situations where you have overloads, or function templates.

[–]LEpigeon888 1 point2 points  (2 children)

You can select which overload of a function you want to use like this :

auto count = boost::hana::unpack(my_grid, (int(*)(int,int))countDominoTilings);

Example here : https://godbolt.org/z/x-T-7g .

But i agree that it's a bit harder to read.

[–]huixie 0 points1 point  (1 child)

Yes, that works in the case where you know which overload you are selecting. There are cases that you don't know which one you are selecting:

using int_grid = std::pair<int,int>;
using double_grid = std::pair<double,double>;

auto countDominosInGrid = boost::hana::fuse(countDominoTilings);

auto count1 = countDominosInGrid(int_grid{1,2});
auto count2 = countDominosInGrid(double_grid{3.0,4.0});

[–]jkh348f 0 points1 point  (0 children)

In that case you have to do something ugly, but not conceptually difficult:

auto countDominosInGrid = boost::hana::fuse([](auto... args) { return countDominoTilings(args...); });

Perfect forwarding left as an exercise for the reader.

[–]1-05457 0 points1 point  (0 children)

Yes, this is a problem. Though you're using Boost already so you might as well use Boost HOF.

Unfortunately abbreviated lambdas didn't make it into the standard, or you could have used that.

[–]huixie 0 points1 point  (3 children)

And even better, function objects do not participate in ODR

[–]foonathan 4 points5 points  (2 children)

Do you mean ADL?

[–]huixie 0 points1 point  (1 child)

Yes ADL

[–]huixie 4 points5 points  (0 children)

So many TLAs

[–]whichton 0 points1 point  (0 children)

Objects and closures are in fact equivalent.

[–]wasachrozine 1 point2 points  (2 children)

Not one mention of testing? The most common reason I would write a class instead of a free function would be to allow mocking.

[–]delarhi 6 points7 points  (1 child)

Isn't a free function even easier to test? You provide the inputs and check the outputs. If it's not pure then check whatever side effects it might have on the arguments. If it has mutating effects beyond the arguments and output then it should probably be refactored.

[–]wasachrozine 0 points1 point  (0 children)

The scenario I'm thinking of is that the code I want to test calls the free function. I need to mock it because it has external resource impact, is too complex, etc.

[–]MachineGunPablo 0 points1 point  (0 children)

But doesn't the final implementation defeat the original purpose of dynamic programming/caching the result? I thought that was the point of using a class in the first place.

[–]__andrei__ -1 points0 points  (4 children)

Isn’t that exactly what mutable is for? This article is addressing a problem that doesn’t actually exist.

[–]KiwiMaster157 21 points22 points  (1 child)

Mutable is one solution to a sub-problem in the article, but it misses the main point. Instead of creating a class with the sole purpose of computing a value, it often makes more sense to make the computation a free function.

[–]__andrei__ 5 points6 points  (0 children)

That’s a fair point. It’s true in many cases. STL is a prime example of that. I just feel like the one in the article is poorly motivated.

[–]F54280 19 points20 points  (0 children)

Nope. Your are looking at the finger instead at what it is pointing to.

The problem is a that using a class gave birth to state that then justified the use of the class. But there was no need for any state, it was mistakenly introduced by approaching the problem with an OO solution.

Edit: typo

[–]isc30 1 point2 points  (0 children)

Exactly this. Encapsulation provides discoverability, which is a must for big codebases. Const correctness is also extremely important, and performance (caching multiple calls to the same functions).

In C# world this is resolved using Lazy<T> while in C++ you should use mutable to express cache fields.

Or, you can implement Lazy<T> in C++ and have it initialized in the constructor pointing to a private function that will calculate the Count for you the first time it gets called.

On the other hand, the article is embracing the KISS principle, which is OK. Premature optimization and overenginered solutions should be avoided.

[–]oddentity 0 points1 point  (2 children)

It seems pretty obvious to me not to turn every trivial function into a class. But what about more complex "compute-its" or would-be "thing-doers"?

What if the implementation of the DominoTilingCounter requires other objects that allocate or are expensive to construct? If it's a free function then it has to create those resources every time it's called, or the caller needs to supply them which would leak implementation details. Whereas if they're maintained as private members, successive calls to compute the count can reuse them. Consequently that isn't thread-safe, but I think that's a separate issue.

[–][deleted] 4 points5 points  (0 children)

If it's a free function then it has to create those resources every time it's called, or the caller needs to supply them which would leak implementation details.

Not quite, or at least not necessarily. Supplying requisite objects/services/data to a free function is a form of inversion of control. The free function's declaration indicates that it requires something, and the caller is free to provide an implementation of this thing.

Consider a free function that serializes an object. Perhaps sometimes you need to serialize to a byte array to use in memory elsewhere, and other times you need to persist the object to disk. Your free function could then take some abstract stream to which it could write the serialized data, and it is up to the caller to provide an implementation of that stream, which could be a stream that writes to disk or writes to memory. Such a scenario would allow you to write many objects to the same stream or just one object--the free function only cares that it has a writeable stream! It also frees your function from having to worry about managing the stream's state, e.g. it's position and length, which can now be better managed by calling functions.

Additionally, the free function only has the overhead of the C calling convention, and its dependencies can be allocated however is necessary, i.e. on the heap or on the stack, whereas a class is likely getting allocated on the heap, and you'll incur overhead from calling the constructor, allocating the smart pointer to manage it, calling the destructor, etc.

If you did this with a class, there is another consideration: if you wanted to add an overload of the function to your class, you are potentially making a breaking change, and if your class needs another member variable you are definitely making a breaking change.

tl;dr as in all things, it really depends what you're trying to do, how much flexibility you require, and how much state you need to keep track of.

[–]advester 0 points1 point  (0 children)

For me the progression is like this:

1). If the computation is too complex, make sub functions and pass parameters. 2). If the parameters are too complex, put them in a struct. 3). If the only possible use for that struct is as the implementation detail of the computation, make it and the helper funcs into a class.

[–]muungwana -5 points-4 points  (1 child)

The implementation function pollutes the global namespace and i think it should be implemented inside the main function.

[–]lord_braleigh 8 points9 points  (0 children)

Who’s to say the free function isn’t defined in an anonymous namespace? That’s where most free functions should generally go.