Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
The use of `class` for things that should be simple free functions (quuxplusone.github.io)
408 points by ingve on May 28, 2020 | hide | past | favorite | 399 comments


Counter-point: while in many situations this isn't the right approach, it's worth recognizing when this is the right approach, because they can look awfully similar. An example of this is graph searching, e.g. BFS or Dijkstra's algorithm. The typical implementation is a function. But if you make Dijkstra a class, with (say) a function to iterate through nodes, it lets you do several things that would be difficult with a function: (a) you can now re-use the same object for computing distances to multiple vertices, which allows incremental search, (b) you can now fork/copy the object and continue searching on different extensions of the original graph, (c) you can now save & restore the searcher state to pause the search & continue it later, (d) you can do multiple searches across one or more graphs in lockstep. IMHO these are very much not obvious, and to someone who doesn't recognize what's going on, a class for something like BFS will look exactly like an "OO antipattern", when in reality it's actually providing significant additional functionality for more complicated situations.


None of these situations require an object to do. You can always have a function that takes in an extra ‘state’ argument. You can then do everything you said by passing in the corresponding state values.


Sure, you can build your own object and pass the 'this' pointer around manually. You could use C for everything, and tediously do everything again that objects do for free.

But why?

There's a lot of object-hating going around. Its silly. Use objects to encapsulate functionality, they are good at that and everybody understands what it means.


You don’t agree with an idea so it’s silly? Passing around state via variables is no more tedious than the extra syntax for class definitions, constructors, member variable access, extra semantics related to objects, extra keywords related to visibility, etc.

You can encapsulate functionality without objects. There is nothing that you can do with an object that you can’t do with a closure, and the closure version will be 1/10th the size and have 1/10th the semantic programming language elements. That is why you see ‘object-hating’ all over the industry. It hasn’t lived up to its promise.

Objects aren’t simpler, inherently. It’s just what you know, today, and you’re unwilling to acknowledge your biases.


the closure version will be 1/10th the size and have 1/10th the semantic programming language elements

This seems pretty central to your point, but a factor of 10 is also a very strong claim. I'd like to see sources.

Passing around state via variables is no more tedious than the extra syntax for class definitions, constructors, member variable access, extra semantics related to objects, extra keywords related to visibility, etc.

So instead of doing objects, you're doing closures that act like objects?


> So instead of doing objects, you're doing closures that act like objects?

No, I'm using using as-plain-as-possible data structures for my state, that I pass to functions that operate on it and return new state. Simple, composable, easy to test, easy to pass the state data elsewhere, serialize it or whatever else I might want to do. I'm not arguing against using classes or objects, but often its unnecessary and hiding mutable state in an implicit "this" variable is, in my opinion, something that often gets in the way of simplicity, as it often tends to imply mutable objects when in my opinion mutable data should be a careful decision rather than the default. Not necessarily of course, but even when you're operating on a constant "this", I feel that hiding the data that its acting on internally is still often not the right choice, especially if the object itself isn't constant/immutable: then its a coeffect instead of an effect, and if you're using truly immutable objects, then its really just a minor syntax difference to write obj.foo(bar) instead of obj(foo, bar). Object systems still carry around a whole bunch of other functionality that you may not want or need, but by using objects, a reader can't know if you are or not without reading the code or documentation. If its data and functions, it isolates the places things can happen somewhat. Classes and objects have their place, but I feel they shouldn't always be the default choice, just because.

That's my take at least, and a big reason I switched from Python and Java to Clojure, but to each their own :)


Fair enough. I agree with you that: "Classes and objects have their place, but I feel they shouldn't always be the default choice, just because."

Often the best things to do in an OOP language is to remember that you don't have to encapsulate everything, and sometimes it's just about bundling data/state.

I may have highlighted the wrong point that I was responding to. I was making a point about the behavioral equivalence of objects and closures in response to this:

There is nothing that you can do with an object that you can’t do with a closure, and the closure version will be 1/10th the size and have 1/10th the semantic programming language elements. That is why you see ‘object-hating’ all over the industry. It hasn’t lived up to its promise.

If you're using a closure where it would have made sense to use an object, it's likely that the closure has the same problems to solve with encapsulation, reasoning about hidden state, etc. that make many languages' object related syntax complex, but instead of dedicated object syntax sugar you're solving it with dedicated closure syntax sugar.


I agree with you. The only point in favour of using a closure that I want to point out (you already pointed out a big point in favour of objects: the dedicated and streamlined syntax) is that its a more a la carte way of doing objects: you choose how deep you want to go.

Of course in reality its not that bad because you can also use classes as much or little as you want and can use them simply for single dispatch, or namespacing, if you wish.

I do find that in practice syntax and features matter because they push you to think in a certain way and it can be hard to break away from that. For example, I write a lot of Clojure and really like functional languages, but when I use Java, Python or even C++, its all too easy to slip into the OOP mindset even when I set out to just "do functional" in those languages. Maybe that's my failing and its certainly not OOP's fault, but I think in practice (at least from observation) it seems most people are like this.

But yeah, I don't actually disagree with you on what you said.


You're saying what? Doing objects that act just like closures? :)


I didn't spell it out, but the gist of it is that their behavioral equivalence means that they also share the same problems, and therefore closures aren't an obviously superior choice (regardless of the syntactic sugar aspect of the debate) the way the parent comment portrays it.


OO - data with functions

Closures - functions with data


There's nothing you can do in Python that you can't also do in assembler. It just takes a bit longer to code, but runs a lot faster.

But no one is going to write a big industrial project in assembler today, because it's the wrong abstraction for the job.

Likewise with objects. They make it easy to switch conceptual levels in a domain in a way that projects don't. They take a bit longer to code, but with careful coding you can reuse them ad lib.

The OP is making the point that OOP is the wrong abstraction if there's no reuse. And that's perfectly true.

But there are situations where you want to say "Make a thousand of these items which respond dynamically and somewhat independently to their environment but which still support some kind of top-down management..."[1]

You're going to have a bad time trying to do that with exclusively with closures. Of course you can probably make it work - but that doesn't mean you should.

[1] E.g. particle and/or crowd simulation in games/CGI.


Seems like kind of a meaningless gesture to pass in your own “this” just because you don’t like OOP. If you need a class, why not write it the idiomatic way?


I didn't down-vote you, but in my opinion it's not meaningless because it makes it inherently more testable. Try testing an opaque class without any accessibility into its state. It is much more difficult. If it takes in the state, performs an operation, and returns a new version of that state (ideally an immutable copy) then it becomes much easier to test and validate.

The reason information hiding is/was advocated was for reduced coupling. But in reality I find that this coupling becomes implicit which is arguably worse. But this is just my opinion.


I see your point, but I 80% disagree with your conclusion.

When you test opaque objects, you're generally not trying to test individual state transitions, you're trying to test that the class's interface adheres to the external promises it makes.

That said, many OOP languages have solutions specifically for when you actually do need to test those internal state transitions. C# for example has the "internal" keyword and allows you to declare friend assemblies, so you mostly get your cake and eat it too, at the cost of not hiding the code from yourself as the module implementer.


I interpreted the top-level comment as a list of extra external promises you could add to your class's interface (incremental search, save/restore, etc). If those are easier to test with functions and a state parameter, it might be better to skip the class.


Isn't how you pass things into a function separate from whether you are hiding information? If your state parameter was an opaque pointer then you wouldn't necessarily be able to do anything with it. Also if your class internals were public you would be able to change anything you wanted.


Exactly! You don't need classes to obtain information hiding. In general treating the state as an opaque pointer is a good thing. But the state is part of the public API whether you want it to be or not. Even if you try to "hide" it in the OOP sense, the result of the methods depends on the internal state so it is leaked through the behavior of those methods.

However, when it is hidden in the OOP sense it makes it much harder to reason about the behavior of a function without reading the source code. Because there is this internal "hidden" state that you do not know exists.

But a referentially transparent function is much easier to understand. For a given input, you get an exact output.


You're talking yourself in circles. Your final sentence contradicts the remainder of your point.

Testing is limited when you do not have the ability to fully control state, and understanding can be limited when information is hidden. The impacts of this are determined by the information being hidden, its relationship to the function's behavior, and the documentation of that behavior relative to the calling context.

Limitations on the ability to fully control state are orthogonal to the method of information hiding (opaque function parameters, internal object state, etc).

This is not an OOP/functional discussion, this is a discussion about the tradeoffs inherent in information hiding. And let's be perfectly clear here: These are tradeoffs, not black and white clear wins in either direction.


I think information hiding is the wrong term. It should be hidden for modification, open for inspection. Functional programming naturally promotes this. OO does not. That's my opinion.

It's been a useful discussion though so I appreciate everyone's input.


So are you saying you no longer think OO inherently makes things harder to test?

I feel like it's difficult to talk about this without examples. The library I like thinking about when I think of passing around in a state variable is the lua C api.

https://www.lua.org/pil/24.1.html here is an example.

https://pgl.yoyo.org/luai/i/lua_State here are docs for the state variable's type.

This is pretty object oriented except that it's not using C++'s syntax sugar. You can't really do much with L except pass it into other lua "methods". The discussion up until your comment seems to be about the difference between using the syntax sugar and passing around a state variable yourself.

Information hiding and these other implementation details are kind of seperate. In my example you can't just configure L exactly how you want by messing around with it or inspecting its state directly. You have to go through accessor "methods".

I think if the argument is that OO promotes information hiding I can agree with that. I'm not sure about the point about the internal state becoming a part of the API though. APIs are contracts that can be met with different implementations right? If your implementation details are public then your contract is huge and inflexible.


Not exactly, I'm saying OO still makes it hard to test because it is an opaque blob of state. It would be really hard to use/test the LUA API for example without knowing it is using a stack underneath the hood. That detail leaks through the API whether you want it to or not. If it was switched to a FIFO queue that would completely change the behavior.

Since using this API makes an implicit constraint that it is using a stack underneath the hood, why not just expose the stack for inspection? What advantage does keeping it an opaque blob have? I agree you don't want code manipulating this stack (although if it is immutable as in FP this isn't an issue), but since the external code already knows it is a stack and relies on that fact then it is part of the public API already.


The term information hiding used in this thread is very confusing (to me, at least).

I believe David Parnas introduced it in 1971 to mean that a program's design was sliced along shared units of concerns (things that vary together) rather than "steps in a flowchart".

https://prl.ccs.neu.edu/img/p-tr-1971.pdf

I believe what you are trying to convey is called "data abstraction", as for example used by Reynolds, 1975;

mentioned here: https://www.cs.utexas.edu/~wcook/papers/OOPvsADT/CookOOPvsAD...

explained here: https://link.springer.com/chapter/10.1007%2F978-1-4612-6315-...


The class will have to provide you with an option to get your answer back after a computation, so that's what you test. I don't see a reason to test how a class computes a result as long as the result is correct.


Well either you have internal state or you don't. If you don't have internal state (and it just returns the answer), then there is no difference between it and a function.

If it does have internal state, then the answer it returns depends on the value of that internal state which means it's hard to verify that the answer returned is correct since you have to know the internal state of the object.


The whole idea of internal state is that it is not accessible (so not relevant) to the outside world.

You test a class as a user by calling its methods in the desired order (per requirements and documentation) and you check that the results you get are correct. If the results are correct, the class fits your use case. If they are not, then you're either calling it wrong or it has a bug. Either way, no need to know the internal state.


That's a great ideal, and I understand the arguments in favor, but the reality is that sometimes in order to test all of the internal code paths, you have to go to extremes when only interacting via the public interface. If I'd have to write 50 lines of extra testing code (or worse, extend various classes to add fake hooks into external dependencies, etc., which is where testing tends to really get messy) to validate that some edge case is handled appropriately, it's sometimes worth skipping that and fiddling some internal state to jump straight to the edge case.


"Calling its methods in the desired order"

This is my point. The order of calling the methods matters, because it manipulates opaque internal state. Since this ordering matters, the caller is implicitly dependent on this internal state as it must know the order to call the methods in to get the desired result.

So it's very relevant to the outside world in my opinion and nothing is truly hidden. It becomes part of the public API whether you hide it or not.


It still depends.

Think of a List class. Let's say it has methods to add, remove and retrieve elements, and it has a method to retrieve the size.

Let's say the internal implementation is an array with a fill pointer.

Obviously, before you can usefully call list.get(7), you must have called list.add(T) at least 7 times (assuming no deletes).

Do you need to know the the state of the backing array and the value of the fill pointer? Do you need to know whether there even is a fill pointer, or a linked list underneath (obviously, the performance would be different so you would need to know at some point)?


You do not want to test private implementation. You should be testing the public contract only. Reaching in and making private things public crystallizes the implementation and reduces your ability to refactor.


If you’re asserting non-public internals of a class, your testing is wrong in the first place. Assert outcomes, not the process.


That's the whole point. You can't test outcomes if they are dependent on internal hidden state as that affects the outcome.

Whether you make it private or not hidden state leaks into the output since the output is dependent on it.


It sounds like you’re confusing side effect free code with OOP. You can use OOP while having side effect free stuff that never requires you to assert internals.


    Tests.h
    #define private public
    #define protected public


Maybe I'm mis-reading it, but it seems that your comment is just a re-statement of what your parent comment is replying to. It’s like, “Why A” “Because B” “But why A?”


Because it's like using a while loop to print out each character in a string when you can just do print to print the entire string.

I don't want to worry about external methods modifying state, I don't want to deal with constructors, I don't want to deal with instantiating state or modifying state before I call my function.

My function just takes a state and returns a new state. Simple.


In what way do you 'need' a class? It uses none of the features that a class provides over normal structs.


The idea is, if you need the functionality that classes give you (passing the implicit state between multiple calls), why not use the special syntax for that use case?


Because sometimes you don't need a class, or don't need the full-blown machinery that an object system entails.


Objects with a single public method are more tedious than equivalent closures. Objects with multiple public methods are much more friendly than closures with multiple public entry points (they can be done, but they look and feel hacky).

Also, pieces of data that hide their internal structure and just expose an API to modify that data (Objects) are a very useful construct, and used in all languages with first-class mutation. Some languages have special syntax for this case (e.g. C++, Python, Go, OCaml), some don't (e.g. C). For example, no language that I know of exposes a mutable List type with a public count of elements. Neither does any language I know of expose a mutable List type as a closure.


> Passing around state via variables is no more tedious

But it is - the programming language is now repeatedly quizzing you on something you've already told it.


Well I mean would you rather have this:

    method(state){
        return doSomething(state)
    }
Or this:

   class A:
       otherThingThatMutatesState1()
       otherThingThatMutatesState2()
       otherThingThatMutatesState3()

       method(){
          this.state += 1 // or some other mutation
       }


That depends on the language. Haskell provides some nice syntax here with monads.


> There is nothing that you can do with an object that you can’t do with a closure

How do you prevent memory “leaks” due to the banana referencing a forest problem? With JavaScript libraries returning a function pointer, the returned function pointer would often have a closure that contained a lot of irrelevant state. There is no easy way to reason about the leak, there is virtually no way to work out the cause at runtime (debuggers can help, but a problem with increasing memory usage certainly is not easy to diagnose why). Even when really conscientious about the problem, it is seriously hard to avoid the problem or inadvertently create a closure e.g. many programmers are unaware that arguments may not get GCed if you add an event handler within a function (closing over arguments and maybe other variables).

Objects have their problems, but the memory leak problem with JavaScript closures is insidious and very very hard to fix.


Though i agree with with your general statement, i think the 1/10th the size figure is a bit disingenuous. At least the cases where i've gone from the unnecessary OO style to a simpler function/procedure based solution (in JS, which supports both styles just nice), the code size difference hasn't been anything that great. At most it's been half the size. Which is a lot i think! But not an order of magnitude less.

I'm noting this because i think that number kinda detracts from your main argument, with which i agree completely. I think even if the code would remain roughly the same size, the removal of OO would still be worth it, since, as you mention, is much less jargon and conceptual baggage. And if i can achieve the same result with a much simpler conceptual model and language, then all the better :)


I will definitely admit that those numbers are made up, more of a rhetorical technique. Numbers are important, I do agree.


For JS, it's best to avoid class as possible, due to this and bind / apply shenanigans.

Closure and function return function can be used as substitute, and except for inheritance (which should be avoided too), I haven't found any use case for using class class. Example:

    function MyList {
      let data = [];
      let add = (item) => data.push(item);
      return { add };
    }


> Closure and function return function can be used as substitute

So for every list you have an additional allocation for the add function. It obviously continuously to get much worse as more "methods" are inevitably added. That's extremely inefficient for something that's used often and it's not something JavaScript engines can optimize away. The benefit is also extremely negligible since the closed over values are still implicit in the call to add.


Exactly. So you aren't proposing any improvements if by your own admission, it's no more tedious.

Except that it's actually more tedious implementing your own bootleg object. Then managing the scope, namespace and pointers. When they could all be contained inside a language construct guaranteed to follow the rules.


Less complexity to deal with. No need to worry about external scope or external methods mutating properties on objects. No need to think about constructors or anything else. No need to worry about inherited properties. No need to instantiate the object before executing the method.

All you need to do is define a function and call it.

Literally, again I urge you to take a look at the following example, it's waay more simple than OOP:

https://news.ycombinator.com/item?id=23338700


But you _are_ passing `this` around with OO code too. It just ends up being before the dot (or arrow), instead of being an argument. All you did is loose some flexibility.

OO couples behavior with state. Sometimes that's exactly what you want (e.g. containers). And sometimes you just need data, functions and namespaces and all 3 are available in C++ outside classes.


But I don't want that flexibility!

If my state has some invariant, I want to be sure that I can trust it. I don't want to worry that someone will mess with it, breaking the invariant, or accidentally pass in stale state.

If that's all handled automagically by the object, it's out of sight, out of mind. Add in a get_state() and set_state() method if you need it as an escape hatch or for testing.


Ok mostly true. It can also be part of a function pointer.


You lost me at “there’s a lot of object-hating going around.” Conversely there’s a return to being mindful of how we use the computer’s resources. Lately I’ve been reading about Ruby internals, and rather than be impressed I’m terrified by how staples such as classes are implemented.


Objects do not necessarily mean polymorphism and they certainly don't necessarily mean polymorphism via dynamic dispatch. For example, it's entirely possible to just use objects all within the stack. It's entirely possible to implement monomorphism via generics.

So yes, object-hate characterises the popular but tiresome anti OOP sentiments going around.


What you call “for free”, I call implicit. Passing it as function arguments makes it explicit. You don't need to “hate” “objects” to prefer the explicit over the implicit.


I don't find all that much of a difference between notations like set.map{...} and map(set){...}, so at that point I suppose it really is just aesthetic preference.


Classes usually have a more complex state than the state passed to utility functions, which is a reason I'd be incline to use e.g. a struct as a state vs a C++ class. Makes it harder to write bad code.


So, let's say what you actually mean here: overly complex state is a problem, regardless of the syntactic sugar around passing it.

I've seen monstrous state variables passed around to "pure functions" at least as much as I've seen monstrous god objects; the problem is around complexity and data flow design and has very little to do with function vs object.


Yes that's my thinking. Everything can be done both ways, but in my experience structs have a better track record. Also different languages implement objects differently which adds mental overhead. Copying structs or marking them read-only is also usually easier vs an object which manipulates its own state.


Upvote for the comment, but the reality is OOP (at least in C++) encourages people to write code this way. When you have a sufficiently large code base and an unforeseen requirement comes in, one that wasn't planned for in those architectural committees, you have a problem. And that problem is solved by either a man-century of refactoring, or by taking some shortcuts that destroy the purity of the code and eventually lead to these monstrous god objects that you mentioned. This happens over and over, to the point where you can't just say "oh it was an accident, happened just one time".


Sounds like you would enjoy Uniform Function Call Syntax!

https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax


I think you could apply your argument to any use of objects, unless there are virtual methods involved. So you probably would have to be pretty generally against the use of objects to agree.


Classes for use with stack-allocated instances in C++ are also very handy. Makes it easy to control the extent (i.e. lifetime) of the side-effects they produce.


Except for the title of the OP, which seems argumentative.

The context pointer for an encapsulated set of functionality, need not be passed around. It adds little or nothing. In a language full of encapsulated functionality, it stands out like Chekov's Gun. I'd argue, there better be a damn good reason to deviate for normal practice. And not just 'I prefer the explicit'.


There isn't object hating it's just writing the simplest program that does what you need, having n layers of indirection requires the programmer to keep context of n layers. If you need the indirection create it when you need it.


> Use objects to encapsulate functionality

Sadly objects are also used in an attempt to hide data (because direct access to data is considered dirty in OOP).

Now, consider a non-trivial OOP class hierarchy. You have an object that needs to mutate another object's data (an object that is completely unrelated to your initial object). In order to solve this you have options:

- completely rearchitect your code so that you take this use case into account

- find the shortest path between these two classes and add setters to each class

- hack the code and call it "technical debt"

First one is not feasible, second one is unmanageable (complexity increases dramatically), third one is usually chosen.

Speaking from experience, OOP is most of the time the worst tool for the job.


OR, don't use a 'setter', use a carefully named API that describes the purpose instead. Or design the two classes to share the data with locks. Or whatever your solution requires.

Yes breaking encapsulation willy-nilly is a bad thing. So don't.

I don't doubt there are bad programmers out there. Blaming the hammer for a bad carpenter is foolishness.

Btw objects are used for like 5 different things. Choose what works for your problem space.


> don't use a 'setter', use a carefully named API that describes the purpose instead.

Call it whatever you want, the requirement is for one class to produce a side effect in a completely unrelated part of the code (happens all the time). This doesn't fit with the current architecture, and there was no way of predicting it beforehand. Now what? Rewrite everything? What happens if you're in a department of 30 developers? Tell them "hold on while I go back to the drawing board to create a clean design"?

OOP isn't a hammer, it's duct tape, and carpenters are taught from school that duct tape is the main way of doing carpentry. It's duct tape because it ties data to functions in a way that makes it very difficult to extract or reuse that data somewhere else.

When OOP is your religion you don't blame the bad carpenters, you blame the master priest.


I would argue that it is actually waaay more awkward to use objects for this. See my example code here:

https://news.ycombinator.com/item?id=23338700

You will see that it is far more concise and intuitive to do it with a function.


But if you're passing a state object around, you might as well use a class, no? Admittedly simplifying a bit, an instance method is a function that implicitly takes "this" as the first argument.


There's one big difference in terms of convenience in many common implementations of classes and methods. There are essentially three different pieces involved:

In OO notation, we have A.B(C)

In almost all languages, you can store C in a variable and supply it "later":

    z = C
    A.B(z)
You can also store A in a variable and supply it "later":

    x = A
    x.B(C)
However, it is much more rare to be able to store B in a variable and supply it "later". Often, you must store A.B:

    xy = A.B
    xy(C)
This means that at the time you select which method to call, you must also have at your disposal the instance on which you want to call it!

With functions, things get easier. If you have B(A, C), you can generally just store B in a variable on its own:

    y = B
    y(A, C)
Some OO languages allow you to store plain methods that aren't yet bound to a particular instance (JavaScript, modern Java) but far from all do, without resorting to inconveniences like

    y = lambda o: o.B
Please note that what this workaround accomplishes is effectively to turn the method into a function!

I suspect the main argument here is that it just doesn't make sense notationally to treat the first argument to a function specially. It only introduces wierd edge cases that have to be worked around. Plain old function notation has stood the test of time for good reason: it's flexible and handles most of the common cases we want.


>There's one big difference in terms of convenience in many common implementations of classes and methods.

Which ones?

>However, it is much more rare to be able to store B in a variable and supply it "later". Often, you must store A.B:

How is it rare? Smalltalk doesn't require you to specify the receiver when storing a symbol. Neither does Objective-C (selector). Neither does Ruby. Nor Java.

Which commonly used languages require one to have the receiver tied to the method when memoized? C++ is the only one I'm aware of. So it might be rare to do it in C++, but not in OOP generally.


Even C++ lets you store the address of the member function and lets you invoke it with an instance later.


> Even C++ lets you store the address of the member function and lets you invoke it with an instance later.

Not for virtual member functions (not that I've been able to find). C++ is geared towards generating optimal executables. Since the type of the object is known at compile-time, the particular function used for that type is known, and the compiler will generate code that applies for it.

This is why C++ uses mangled names: to discriminate between identically named functions of different types and signatures.


Java before invokedynamic, I guess.


I like the approach Wouter van Oortmerssen uses in Lobster:

    class Animal:
        alive = true

    class Cat : Animal
        def hello(): print "meow"

    class Dog : Animal
        barked = 0

    def hello(d::Dog):
        print "bark!"
        barked++

    let d = Dog {}
    d.hello()

    let a:Animal = d
    a.hello()
In other words, A.B(C) is just syntactic sugar for B(A, C), and class definitions are just syntactic on top of that. The relation between OO and regular functions and stateful objects is completely transparent (the only real gotcha I can see is that a.hello() still works here because it was assigned a subclass where this method is defined). This makes it easy to store B for later usage, like you wanted to do in your example, and reason about its behavior.

[0] http://strlen.com/lobster/


This is also the approach taken by Nim and D, and it is known as the UFCS (Uniform Function Call Syntax)[0], and I agree - it's the best of all worlds.

[0] https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax


Ah, I wondered where it originated. Thank you for that bit of info!


I think Swift does this too?


> This means that at the time you select which method to call, you must also have at your disposal the instance on which you want to call it!

That is also its advantage, since the method to call can depend on the instance. Not just for dynamic dispatch either. A statically dispatched call will look up B on classof(A).


I'd just like to add that you can still have the `A.B(C)` syntax, if you just define it as syntactic sugar for `(class of A).B(A, C)`


> This means that at the time you select which method to call, you must also have at your disposal the instance on which you want to call it!

The time you "select which method to call" is when you write the code, right? You select it once. Like, "printf(arg);". I selected printf (in my mind), I called it. I don't need to do "x = print; x(arg)".

> With functions, things get easier. If you have B(A, C), you can generally just store B in a variable on its own:

> y = B

> y(A, C)

>Some OO languages allow you to store plain methods that aren't yet bound to a particular instance (JavaScript, modern Java) but far from all do...

Sorry, I lost the plot here. If you have B(A, C) then you don't need "y = B". B is an invariant of sorts. Just like you don't usually store the address of a class method; you just call that method on an object. Nobody writes:

> x = &objectInstance::method;

> x(args);

People just write

> "objectInstance.method(args);"

Therefore in your case, if using functions, you just do B(A, C). No assignment needed, works in every language.

What am I missing?


>But if you're passing a state object around, you might as well use a class, no?

A lot of your functions aren't going to need access to the whole state, only to some of it. If we're not using a class, we can just make these functions take only the needed arguments, rather than the whole state. This makes them easier to reason about, as we can know from the function signature that it only looks at the params we pass in, not the whole state. It also makes them easier to reuse elsewhere in the code, where we might not have the full state but we do have the arguments to the function.

For any function that doesn't need to access every member variable of a class, making it a class member function essentially creates an unnecessary coupling between that function and the class members variables it doesn't use. This makes it unecessarily harder to reuse elsewhere, as anyone who wants to use it needs to create an instance of the whole class, including any variables that aren't needed by that function.


This sounds like exposing internal implementation details. You're pushing some complexity onto the caller, and breaking up the uniformity of the interface. Sometimes the caller will want to manage that complexity for the reasons that you describe, but it's not always good to force the caller to wrangle that complexity.

Design trade-offs, as always. I get why you would do it your way, and it makes sense in some applications. But for some applications the fact that you're hiding which specific bits of state each function depends on, is actually the point of using a class.


Let's use C and C++ as an example. What does the code look like?

    myobj.cool_func(1,2,3); // C++

    cool_func(myobj, 1, 2, 3); // C

Is this better?

If you use a vanilla C++ style class then the class definition is in the header. All changes to the class require a recompilation and break the ABI of the class. This means you basically cannot release patch versions of your library because consumers cannot link against it if they have the old headers.

Contrast with C where you would forward declare a struct in the header and pass that around while the definition is hidden in a .c file. This means changes to the size of the structure don't matter because all calling code ever sees is a pointer. (True data hiding!) It's called an opaque pointer and many many libraries use this pattern. e.g. libcairo.

There is then a pattern to do this in C++ called pimpl:

https://en.cppreference.com/w/cpp/language/pimpl

This solves it for C++ but the alleged convenience is gone because now you're coding C++ with the supposed convenience but still your class implementation has to trampoline all the calls to the pimpl which is an extra function call (if you're writing C or C++ this might matter).

But RAII, though. Not having [standardized] RAII sucks.


> If you use a vanilla C++ style class then the class definition is in the header. All changes to the class require a recompilation and break the ABI of the class.

definitely not all, only things that change the layout. adding a non-virtual method does not break ABI at all for instance.

> Contrast with C where you would forward declare a struct in the header and pass that around while the definition is hidden in a .c file. This means changes to the size of the structure don't matter because all calling code ever sees is a pointer. (True data hiding!) It's called an opaque pointer and many many libraries use this pattern. e.g. libcairo.

Does this have a point in 2020 ? Good modern practice (c.f. Rust, etc) is to ship LTO'ed mostly-static binaries. Such hiding is only relevant for legacy things such as what Linux distros obstinates themselves in doing for their userspace - hopefully in a few years everyone will switch to snap / flatpack / appimage to finally reach a sane application distribution model on Linux.

I'd definitely say PIMPL is 100% an anti-pattern in every possible case. It forces more memory allocations, and makes it much harder to hack your way out of things when it's 3AM and everything is crashing and you have 5 minutes to fix things before heads start to roll.


> only things that change the layout. adding a non-virtual method does not break ABI at all for instance.

Yes, you are correct. The point was that C code can keep the ABI intact through (some) data layout changes but vanilla C++ classes cannot I misstated it.

>Good modern practice (c.f. Rust, etc) is to ship LTO'ed mostly-static binaries.

That is the state of play now because Rust does not have a stable ABI. I thought one might be coming but maybe no one's working on it. It's unfortunate because it would be really nice to have binary crates with signing.

>hopefully in a few years everyone will switch to snap / flatpack / appimage to finally reach a sane application distribution model on Linux.

Snap has a while to go before it can be adopted fully. Images are enormous, they take a long time to load, and they don't respect (or don't understand) hidpi settings. I tried to use it for spotify and telegram-desktop and other applications but they just don't work that well.

>it much harder to hack your way out of things when it's 3AM and everything is crashing and you have 5 minutes to fix things before heads start to roll.

Absolutely but it comes from enormous systems like Windows where changing a library and breaking ABI means recompiling Everything. And these compilation jobs are often 'overnights'.


> adding a non-virtual method does not break ABI at all for instance.

Ctors/dtors can break ABI as well: https://gcc.godbolt.org/z/XAx00v


> Does this have a point in 2020 ? Good modern practice (c.f. Rust, etc) is to ship LTO'ed mostly-static binaries.

It sure sounds to me like modern practice as you've defined it is that when a component has a security vulnerability you're not going to patch it. It's going to be statically linked and running in a container image maybe created by somebody else and you'll have no idea it's there.


Dynamic linking is no panacea here.

Yes, it theoretically lets you ship a patch version bump containing security fixes for the few languages with a stable ABI. But even ignoring the deluge of unpatched IOT garbage running seriously out of date kernels - nevermind userspaces - how do you ship that version bump of your library to your Windows users? Your OS X users? iOS? FreeBSD? RedHat? Android? Mint? Debian? PS4? Redox? Xbox One? Solaris? Switch? I've dynamically linked into PuTTY before - a good 'ole C codebase - and I still had to rebuild and redeploy myself when a security patch landed.

Meanwhile, an updated package on crates.io can change deps.rs badges, or trigger dependabot - optionally auto-merging the pull request if CI passes, or altering you to the build failure and need for manual action if that "patch" version broke things. Rustsec and cargo audit/geiger/crev are all hideously platform independent ways to catch problems. My patches to an unsound Rust crate can automatically land on your WASM-enabled website if you have things so configured.


I'd like to see the NixOS approach merged with flatpak/snap/whatever. Basically secure/deterministic, fine grained dependencies. Yet easily shared (since they are just content-addressable), and easily updated (since you don't actually have to depend on the content-hash, and distro maintainers can still coalesce the versions of many-many packages' common dependencies).


> Good modern practice (c.f. Rust, etc) is to ship LTO'ed mostly-static binaries.

What Go and Rust do (by default) is only useful for internal software that you have full control of (both in source and in updates), but it is definitely not "good practice" for general software distribution.

Going fully static (Go) or mostly static (Rust) means your users/clients cannot update dependencies easily. Users will suffer when the vendor is not replying as quickly as they would hope for (which is not a pleasant experience at all, specially if you run server software) or when they simply discontinue the software (cf thousands of games). Then there is the usual duplicated code pages with less effective cache utilization etc. (which is a minor issue nowadays but if everything is a 100 Mo Electron copy it starts being painful).


Users/clients could expect to be able to upgrade minor OS/utility versions, but not direct dependencies. There's no reason to expect rogue upgrades of dependencies to work out of the box, unless explicitly guaranteed by subvendors ie. within minor versions. The modern approach is to encapsulate the complexity within a static binary, or even containers with more files. The deliverables are provided and guaranteed by the vendor. If there are security issues, 99% of that should rather be solved by safe language usage and security perimeter or encrypted channels, while refraining from unsafe library dependencies and direct connections to core business processes.

Linux packaging is interesting, since the delivery model of gratis distributions often have no such guarantees. Thus one may indeed experience difficulties at any moment after a recent upgrade due to that model. In that case, the complexity lies within upstream and distro, a shared responsibility that the distro should reconcile but may fail to do in minute detail.

Modern methods involve a pipeline, and no rogue upgrades that haven't passed multiple stages of tests and security checks.


> There's no reason to expect rogue upgrades of dependencies to work out of the box

If they don’t work, they are broken. Of course it can happen, but the opposite approach means never being able to do it.

> If there are security issues, 99% of that should rather be solved by safe language usage and security perimeter or encrypted channels

There is no mainstream language or operating system out there that solves "99% of security issues" unless you are talking about formally proven systems etc.

> The deliverables are provided and guaranteed by the vendor.

As I explained in the GP, this only happens for systems with support contracts.

For most software out there, this isn’t the case. Mainstream software vendors don’t guarantee you anything at all, for good reasons.

> Modern methods involve a pipeline, and no rogue upgrades that haven't passed multiple stages of tests and security checks.

That is not "modern". That is how it has always been done since the 80’s. Again, for software properly supported.

I am not sure why you talk about "rogue" updates, since nobody has mentioned such.


> Users will suffer when the vendor is not replying as quickly as they would hope for (which is not a pleasant experience at all, specially if you run server software) or when they simply discontinue the software (cf thousands of games).

My experience as a Linux user is that I suffer even more from some minor update in /usr/lib/libwhatever.so suddenly breaking some feature in software that I use to do my job / have fun, which wouldn't happen at all if that software was self-contained.


Being self-contained does not imply statically linked, though.

The benefit of dynamically linking is that you (as a user) or upstream (most likely) can fix those issues you point out, including future ones.

Being self-contained simply means updates aren’t forced into it, which is a good thing and I agree with it.

I didn’t downvote you, by the way, since the point you make is valid.


> This means you basically cannot release patch versions of your library because consumers cannot link against it if they have the old headers.

This is pretty much only a problem in C/C++ though. Everyone else is using static linking (which you can of course also do in C++), or a VM of some form. IMO relying on a library having stable ABI is a bit of anti-pattern. It's like relying on the implementation details of a function/class rather than it's API: sometimes it's necessary, but it should be avoided if possible.


Java has the possibility of maintaining ABIs as well. And I imagine that's what .Net assembles are about too.

I think you're heavily over stating reliance on libraries having stable ABIs as an anti-pattern. If a mistake is made in the ABI promise, can it get hairy? Absolutely. If you work on an application and are somewhere in the middle of it and need to recompile it to test a fix, do you want to wait for the compilation of the entire application to test it? Obviously not.


In C you would do

    my_lib_cool_func(myobj, 1, 2, 3);
so you don't pollute the global namespace excessively. Having it accessed through the object avoids that.


> Is this better?

Very few professional developers code in vim or notepad. Vast majority of us are using IDEs. IDE knows types of things, type `myobj.` and you'll get a suggestion list with methods of that class callable from the current context.

> There is then a pattern to do this in C++ called pimpl

There's another useful pattern in C++:

    struct iObj
    {
        virtual ~iObj() { }
        virtual void cool_func( int a, int b, int c ) = 0;
        static std::unique_ptr<iObj> factory();
    };


> Very few professional developers code in vim or notepad

Notepad++ and vim are the 3rd and 5th most popular development environment according to stack overflows 2019 report [0] and I and many others I know use Vim at least.

> IDE knows types of things, type `myobj.` and you'll get a suggestion list with methods of that class callable from the current context.

I've never used notepad but Vim absolutely supports omni completion [1].

[0]: https://insights.stackoverflow.com/survey/2019

[1]: https://vim.fandom.com/wiki/Omni_completion


> Notepad++ and vim are the 3rd and 5th most popular development environment according to stack overflows 2019 report [0] and I and many others I know use Vim at least.

in C++ the ratio of ppl I know using an IDE with semantic code completion ability (that is, not rtags but actually using e.g. libclang or something that does understand C++ to some extent for IDE completion) vs "glorified text editors" must be 95/100 using an IDE though.


Vim and VS Code have access to semantic code completion through the language server protocol.

But your assertion is weird. Either you work on Windows where everyone is on Visual Studio, or you have 95/100 people around you using CLion, XCode, and Qt Creator, two of which don't even have an entry on GP's survey link. One of which is probably only used to sign apps for ios. Could you give more info?


I use emacs with clangd on a fairly large (~2M lines of code), but between clangd getting stuck, crashing or being too slow, I don't really get much benefits from it. When it works is great though.

TBH, in my limited experience, I have never seen code completion working well on large C++ code bases. I remember years ago VS literally becoming unresponsive when intellisense was enabled.


https://www.cairographics.org/manual/cairo-cairo-t.html

All operations on `cairo_t` have prefix `cairo_`


That's doesn't give context for IDE. You can type `cairo_` and get suggestions what may follow, but you can't type `x.` and get suggestions on what you can do with x.


While PIMPL has additional indirection for accessing member variables, this pattern has indirection for accesing methods (need to go through vtable).


Why? The main advantage of passing a state as an argument is letting the user control the state. If you need an array as state, the user might want to put the array on the stack, or the heap, or in static memory, or protected behind a mutex somewhere...

It doesn't really matter that much how things are actually implemented: most languages are flexible enough that you can do this with a class or a function or even implement the algorithm as a generic interface. But if you want the algorithm implementation to be reusable, you have to remove as many assumptions about the state as possible because otherwise some users won't be able to use your algorithm.

If you pick up a class, and the class owns the state, now you need to make the class generic to allow the user to customize the state, you need to allow the user to break the state invariants, to be able to unsafely read and write from it, because otherwise you are forcing the user to read into a separate buffer, and then make a copy to your class, etc.

Somebody that knows how to avoid all these issues when writing their algorithm as a class, probably also knows that by just using a function most of these issues just cannot happen anymore (or are much harder to introduce).

You can also write the algorithm as a function, that takes some generic state, and provide a "class wrapper" for convenience, so that those who don't want to customize anything don't have to. But then your class doesn't implement the algorithm anymore, it just wraps it.


If we’re talking about an algorithm as a class, the state managed by the class is algorithm state, not the data the algorithm acts on. The data the algorithm acts on should probably be passed as a parameter regardless of whether the algorithm is implemented as a class or top-level function.


> If we’re talking about an algorithm as a class, the state managed by the class is algorithm state, not the data the algorithm acts on.

I was only talking about algorithm state here. Check, for example, how the C++ string searching algorithms work to get a feeling of how these APIs work in the wild. For an example of this gone wrong, look no further than C++ stable_search, which does not let the user reuse the merge sort buffer across searches.


Hm, I guess this is moving in the direction of a debate on semantics and API design... which is independent of the original topic.

I don’t know what stable_search is, but it would be a design decision if/how it supports different memory management options for buffers, and isn’t really affected by the decision to use a class or top-level function for implementation. Either way, buffer allocation could be hidden in the implementation and out of the user’s control or exposed to some degree and under the user’s control.


Why? You would never suggest this if OO wasn’t the predominant paradigm. What is the benefit of implicitly passing the parameter? I prefer to see it passed explicitly, so the implicit passing is a downside to me, not a benefit.


In many languages, it is an explicit parameter though.

For instance, in Python, one would define a function with `def fun(state)` or a class method with `def fun(self)`. In the function body, one would use respectively `state.var` or `self.var` to refer to a variable `var` in the state object. Running it is then done using `fun(state)` or `state.fun()`.

There are some minor syntactic differences between the two, but I find both these examples to be just as explicit...


If it's already explicitly part of an object, do you need to be explicit about the same thing twice?


So you would not use type-classes in Haskell as they rely on implicitly passed parameters to functions?


I can't speak for the GP, but what makes typeclasses in Haskell work for me is that there's a very strong cultural force ensuring that they are truly about consistency boundaries and maintaining invariants/laws.

Haskell developers don't just create a typeclass to bunch related functionality together. Doing so is a common beginner's anti-pattern. Typeclasses exist to wrap up general operations that satisfy some laws.

When objects in OOP are used in the same way, I don't mind it as much. It's when they're not that I don't find the syntactic inconvenience worth it.


In Haskell typeclasses, it's the type of the parameter that is implicitly passed, not the parameter itself.


i don't think that's correct, afaik typeclasses roughly desugar to passing a record of functions (vtable/"dictionary") at the call site


And that vtable depends on the type, not the value.


i guess there's really two implicit arguments - the value's type, `a` (quantified via forall - the System F "Λ") and its vtable `Show a`... though i guess the vtable is uniquely determined by the type bc in Haskell instances are globally unique


The canonical answer is encapsulation. But in my view it's rarely a good idea to couple state related to an algorithm's invocation (e.g a cache) with the data structure itself.


It's almost as if a hard and fast rule doesn't work, ie., the point of the article.


The benefit is encapsulation.


Don't need a dedicated OOP lang for that benefit.

It's an age-old discussion. As old school software engineering became sufficiently advanced, some people bundled their favorite "state of the art" concepts, and called it OOP. (Also: Multiple people, multiple different concepts of what "OOP" actually precisely means.) It became a horrible hype, and from then on out everyone was taught to see all these concepts as "OOP concepts" that elevate OOP over other programming paradigms.

But they're not and they don't.

The only real "advantage" is, OOP languages are opinionated and will try to force their interpretation of these concepts on the coder. Which is nice, if you happen to like it that way. Just not everybody does.


Encapsulation's a weird one. It's a cornerstone of OO but most/all OO languages have rubbish support for ABIs so changing your object in one library means you need to recompile your code using the library. This is the opposite situation that encapsulation seems to promise (in my view).


Is encapsulation different in either case? Nobody should be operating directly on the state, any more than they should be reflecting on private fields in a class.


If you're at the point where you'd rather pass a state argument around rather than use an object you've thrown out OO categorically and there's no point to even discuss anything. The article in question talks about when it makes sense to use functions within the OO paradigm instead of classes.

Generally using classes when modelling persistent state is not an anti-pattern, because that's what classes are for.


Nothing made this as clear for me as using the Nim language, where functions defined as

   proc doThing(x: MyObject; value: string)
can be called both with

   doThing(x, y)
and with

   x.doThing(y)
where x by itself is 'just' a data object, but is treated transparently by the language as having associated functions, setters, getters, etc as if it were a class (even though they can all also be directly called separately).

It's an approach that I find really elegant and that I wish more languages used in some way.


That's a thing you can also do in Python:

    class MyObject:
        def doThing(self, value: str) -> None:
            # whatever
            pass

    obj = MyObject()
    obj.doThing("value!")
    MyObject.doThing(obj, "a different value!")
The only difference is that doThing is namespaced to the MyObject class.


You certainly can do that. You can also refer to John Smith as "Mr. Smith", even when he's your dad. Whether it's more natural or more jarring or confusing is another question though...


Can't you close over the state, so the library doesn't need to see and pass around the extra parameters?


Isn’t that just axiomatic? Of course anything you can do with a class you can do with a function + state. The prose is to make that pattern more natural for humans to program and use.


But after a couple of arguments, fields inside an object become more appealing -- this is about taste.

But even if you have free functions, as they become more complex, you tend to group them together, so at that point you might just slap the "class" keyword somewhere at the beginning of the file and just call it a class.


>But after a couple of arguments, fields inside an object become more appealing -- this is about taste.

I don't understand this. The difference between passing a structure implicitly or explicitly will always be a single argument.

>But even if you have free functions, as they become more complex, you tend to group them together, so at that point you might just slap the "class" keyword somewhere at the beginning of the file and just call it a class.

Sure, but at that point you're not really using any class features. If you're just using classes as modules, why not use modules directly?


> ...a function that takes in an extra ‘state’ argument.

That's exactly what a 'class' is.


You're talking about s.f(x) vs f(s,x)?


Precisely. And that's the way it's done in functional languages.


No, it's done inverted in functional languages!

In a FPL your graph will be an ADT. The advantage is clients can pick it apart, but there is no easy way to extend it. If you wanted to "use the same object for computing distances to multiple vertices", you're screwed: the ADT is laid bare, and there is no data hiding.

In an OO language, your graph will be an opaque object, and you can write things like CachingDijkstra or ParallelDijkstra and make these work transparently for your clients. The price is that you are bound to your APIs: a client cannot pick apart your data structure, because it's opaque.


I think using existential types will give you data-hiding like this. (Disclaimer: I'm still learning about them, and making up this syntax.) Say my abstract data type is

    DijkstraLib =
    there exists GraphState with {
        construct: List(Edge) -> GraphState
        getDist: GraphState -> Vertex -> Vertex -> Num
    }
Now we can write two different implementations of this type that use a different data structure for graph. The data structure will be opaque to the client (even though the client obtains and passes around graph objects!). Like so:

    -- Library
    type CachingDijkstraLib : DijkstraLib
    with GraphState = (HashMap((Vertex, Vertex), Num), List(Edge))
    with {
        construct(a_list) = (HashMap.empty, a_list)
        ...
    }

    -- Client
    DL = CachingDijkstraLib : DijkstraLib
    some_obj = DL.construct(edge_list)
    DL.getDist(some_obj, u, v)
    DL2 = SomeOtherDijkstraLib : DijkstraLib
    some_other_obj = DL2.construct(edge_list)
    DL2.getDist(some_other_obj, u, v)
The types of some_obj and some_other_obj could be completely different, and neither would be accessible to the client. For example, the client couldn't assume some_obj is a pair and try to get its first element. It would also be an error to call e.g. DL2.getDist(some_obj, ...).


The A in ADT can stand for both abstract and algebraic, so it's an unhelpful abbreviation in contexts where both algebraic and abstract datatypes are relevant.

Functional programming languages tend to have good support for algebraic types (which I think you mean) but they can often do data-hiding too! In Haskell, for example, you do this by not exporting the data constructors for your type, so they can only be used within the module that defines the type.


We do have data hiding in functional programming, even if we would not have modules, which we do, we do have type classes, and for example in OCaml you can hide constructors if that's your jam.

It's not like when I use a sorting function that the implementors couldn't swap it out for a caching one or a parallel one.

One of the amazing things I remember is changing an int to a float in a very well used "struct" that was touching thousands of functions but because the functions in Haskell usually so extremely general I didn't have to change a single other line (Number type is heavily used and doesn't incur an extra performance cost, which it very much does in Java).

In fact we can be even more precise or loose in what assumptions need to be made via dependent types in functional programming languages like Agda.

I encourage you to take a look at any popular Haskell-framework to see how it's done in practice.


Cool idea! Actually, you could probably call this extra argument "this".


  void do(withThis, that) {
    that(withThis)
  }
Why be implicit with anything, when you can provide nice headscratchers for the next clown! ;-)

Seriosuly though, most OO-languages brings not much to the table other than being "OO-centric" (methods+data vs procedures|functions). OO without encapsulation of data and implementation though, may even be worse abuse than no OO. So one can by convention even do OO without direct support for it in the language itself, by using any form of indirection that encapsulates data and implementation. REST typically fails this, because it often exposes direct access to internal resources (CRUD).


If you do use a class for this, of course you can provide a wrapper function that constructs the object, calls the "run" method on it, and returns the result. This can be in addition to the processing class, or it can be the entirety of the public API while the processing class is defined privately in an implementation file.

I've done this before for Munkres, also called The Hungarian Algorithm, which assigns jobs to workers. Logically the interface is a simple function, but internally it's very useful to have a class with multiple private methods.


Yeah, I almost suggested a static method for the common case too. It's nice to have a convenient interface for the simpler use cases.


Isn’t this all just different approaches to currying? It’s often convenient to have some function that you can call without passing the same subset of parameters to each invocation. Whether to curry depends on how much repetition is involved and how “expensive” it is for the programmer to curry. In the case of “curry by creating a whole named class”, the expense is relatively large, while a functional currying approach is much cheaper.


I'm not that familiar with functional programming but I think the answer is no. In the situation I'm describing, most of the arguments to the private methods are completely different except for the implicit this/self argument that contains the progress of the algorithm. Currying is about turning a function of multiple arguments into a function of one argument which itself is a function (which in turn takes a function as an argument, and so on). I don't see the connection. Maybe you could clarify?


If you have a function `foo(a, b, c)` and you find yourself passing the same a and b all the time, you can curry it into a function that only takes c: `f = curry(foo, a, b)` such that you can just call `f(c)`. The OO alternative would be to create a class:

    class Foo:
        def __init__(self, a, b):
            self.a = a
            self.b = b

        def f(c):
            return foo(self.a, self.b, c)
And then you create an instance `obj = Foo(a, b)` and when you need to call `foo`, you call it via `obj.f(c)`. This is a long winded way of saying an object is a poor man's closure.


Ah I see, I believe you've mixed up currying with partial function application, that's what confused me (but, again, I'm not an expert on functional languages so it could be me that's mixed up). I believe that currying doesn't take any arguments except the function itself:

    # Signature of f is (a, b, c) -> r
    g = curry(f)
    # Signature of g is a -> (b -> (c -> r))
    #    i.e. g is a -> blah, 
    #    where blah itself is a function taking b, etc.
Admittedly you can get partial function application out of currying (but only with parameters at the start of the parameter list):

    g = curry(f)
    h = g(a)(b)
    # Signature of h is c -> r
But, although they're related, they're different procedures overall: sometimes you'll really want the curried function without immediately applying some arguments to it.

Anyway, replacing "currying" with "partial function application" in your earlier comment: I suppose you're right to some extent, but the "this" parameter usually isn't that hidden and in Python you even pass the "self" parameter explicitly. I suppose the real major thing is you've put a bunch of parameters into a single class/tuple/struct rather than spelling them out individually; if you had a weird language that didn't support structs etc. then some partial function application would certainly be a very useful alternative to writing the same argument list out many times. But once you've got the struct, making your utility functions be methods is just minor syntactic sugar on top of that.


Yeah, you’re probably right. I don’t understand all the tedious, annoying functional jargon; I just know there is a function in Racket called “curry” which does what I described above. Sorry for my error.

> I suppose the real major thing is you've put a bunch of parameters into a single class/tuple/struct rather than spelling them out individually

Yes, this is the mechanic, ultimately. Partial application vs structs/objects/tuples are just two different solutions for the same problem. Arguably they may even reduce down to the same underlying solution (to the extent that closures are objects behind the scenes).


I get how this works and the reason for it. It just doesn't feel DRY to me. Like we're adapting to satisfy a pattern vs the pattern serving us and our dev goals. Aesthetically unpleasing, imo.


We suffer so many bad abstractions at the church of DRY.


There's nothing repeating here... you define a procedure to achieve a higher-level task, which is a combination of other tasks. Kind of like File.ReadAllBytes() vs. FileStream. Later you use it when you need to perform that high-level task.


(a), (c), and (d) seems to be about exposing the algorithm's state and a function that performs a single step of the algorithm, rather than about having a class.

(b) seems like quite a bit of trouble, since the clone function must either copy the underlying graph by default or provide a mechanism for swapping out the underlying graph that fixes up the internal map of (node->distance) and the internal priority queue to refer to things in the new graph.


I believe you describing at least one of two cases: 1. There are multiple methods on the object (not counting constructor) 2. There are arguments passed to the methods.

Either of these cases are different from the provided example: The example will only ever compute a single value per object, always returning that value from a single method with no arguments (other than self).


Right. I think the article is talking about one very specific case, which is apparently common amongst novice C++ programmers, where there is a single computation, whose only inputs come from the constructor, and which gets done in a method.

There are plenty of cases where it makes sense to wrap a function in an object, but the point of the article is that this isn't one of them!


Not necessarily. What I said can apply to even a class with with 1 method that takes no arguments... though you might often recognize it as a more sophisticated kind of iterator:

  class BFS(object):
    def __init__(self, origin): self.origin = origin; ...
    def next(self): ...


IMO it’s even simpler to use a monad-type extension to functions to suspend state. (This can be coroutines/generators, explicit Haskell monads, some macros encapsulating longjmp or whatever.)

    queue <- { root }
    while queue is not empty
      node <- dequeue one node

      # One of the following:
      # for a simple function approach…
      if predicate(node)
        return node
      # or for a monadic approach, can be overloaded generically…
      yield! node

      for child of node
        enqueue child
The monad-style extensions fits with the other pieces of the language much more naturally IMO. You could easily use a predicate monad to regain the functionality of the naive implementation, or a more complex monad if you want a complex BFS traversal, but the function looks almost identical either way.

Python and Ruby and Lua and JavaScript even do this! C++20 will probably make this somewhat more common too.

(The word “monad” may set off sirens here, but it really just refers to a feature similar to but somewhat more generic than “yield” in a coroutine/generator.)


You're describing a classic use of iterators, which is itself a classic use of Objects-as-contracts.


You still hit the same issue and you can still solve it the same way by converting that class with saved state into just a function.

Below are two short examples of two different ways to do this:

   type Node = Null | {value: int, left: Node, right: Node} 
   //a Node is NULL or a value that is an int with left and right nodes

   //procedural with mutation, saved state is placed in parameter: found
   def find_n_and_print_all_DFS (node: Node, search_value: int, &found: Node) -> Void:
      if node == Null:
         return
      else:
         print(node.value)
         *found = node.value == search_value ? node : Null
         find_n_and_print_all_DFS(node.left)
         find_n_and_print_all_DFS(node.right)


   //functional, no mutation, saved state is returned
   def find_n_and_print_all_DFS (node: Node, search_value: int, found: Node = Null) -> Node:
      if node == Null:
         return found
      else:
         print(node.value)
         found = node.value == search_value ? node : Null
         left = find_n_and_print_all_DFS(node.left, found)
         right = find_n_and_print_all_DFS(node.right, found)
         return left != Null ? left : right


I would argue that if I used classes the code would be way harder to read and much more verbose.

You can essentially use tail recursion on that DFS and pass in some accumulator as a parameter into your DFS or whatever. That accumulator can be some pointer representing some saved state or anything you want. There is still no need to group everything together into an Object.

It's a bit trickier but in the second example you can see that you don't even need to mutate anything at all. What you described can be achieved without redundant OOP classes and without mutating state at all! Additionally setting found to have a default value of Null negates the need for the extra accumulator parameter during function calls as you can just call it like this:

     saved_node = find_n_and_print_all_DFS(x, 5)
I would still say OOP is an anti pattern because it overall promotes adding these mutating parameters in your code even when you don't need save states or anything of that nature. In OOP, the existence of getters and setters and methods for accessing variables outside of the definition of the method itself heavily promotes this type of coding style regardless of whether or not it is needed.

The procedural style forces this additional "saved" feature to be evident as an extra parameter. If you never use that parameter it becomes obvious that the parameter is redundant while in OOP mutating external state is an intrinsic part of the style and like the OPs example you have to go through several logical leaps to see how redundant it is.


The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."

Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.

On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.


I felt as though I should know the moral of the story, yet I did not.

https://stackoverflow.com/a/11421598 Moral of the story is that closures and objects are ideas that are expressible in terms of each other, and none is more fundamental than the other. That's all there is to the statement under consideration.


Maybe I'm mis-understanding the definition of class or object but isn't a "File" in even Haskell an example of a class. Files have state which is either the read head or write head and in most languages you can change the head using seek.

I don't think the fact that you call `file.seek` vs `seek(file)` the difference between functional and object oriented. functional people say "state = evil, side effects = evil" and yet that version of `seek` has both.

The functional version of files would have to move the head out of the file

newWritePosition = write(file, currrentWritePosition)

And there would be no need for seek since you're holding your own write and/or read position.

If you close over a bunch of state and pass back a function or functions to work with that closed state you've just created an object with state and side effects. That's exactly what FP people say is bad.


I can’t speak for all functional programming advocates, but personally, when I criticize “classes” it is because of the overall design/culture and not because of encapsulating state/side effects.

- Subclassing is notoriously misleading and confusing and almost never truly useful (true subclassing, not abstract subclassing/interfaces).

- Class syntax/semantics are usually divorced from the other semantics of the language; closures are usually a much simpler design to achieve the same power. (You can get class-like ideas with simpler ideas too, but I think most “class” features don’t.)

- Culturally, classes are often viewed as mythic/special in a way that’s kind of out of touch with mapping problems to solutions. Look at “typical” Java code full of classes for crazy tasks like implementing 20 getters and constructing a factory to create callbacks. All of these things are related to the problem, but there’s a lot of friction/mismatch because people are taught to “use classes.”


I don't know what your point is here. When you do IO, even in Haskell, you will be mutating state in the outside world. Also know that many functional languages, especially the lisps, will allow you to mutate state in a closure.


It's code and state. Nothing more. Nothing less.


My first computer book was: “Data Structures + Algorithms = Programs”. Thinking in this way has simplified my code tremendously. I think OO is taught because it’s more tactile. Literally it takes more keystrokes, and so much is pattern repetition that it facilitates learning and feels like progress. Functional is like learning Latin by studying Ovid one word at a time. When books (or mips) were expensive, this was how it was done. Even OO is old-school now though; today’s introduction to programming is mostly configuring CSS and webpack.


OO is just the most intuitive way to compartmentalize state to many people, me included.


It often is, unless you solve a problem where it isn't (and at some point you will).

Nowadays I try to be very pragmatic about these things, but before I had a certain realization I just wanted things to fit neatly in classes and saw some beauty in it.

That was until I started using rust where this wish initially lead to a lot of frustration. When I finally gave in and did it the Rust "compositional" way, everything worked incredibly smoothly


I feel the same way.


How do you model changes in state over time? (i.e. effects and processes?)


Is this a trick question? You mutate the state, of course.


True. But given that the difference in implementation appears to be arbitrary, the real differene between oop and fp must be evaluated in the domain of empirical research regarding their ergonomics. If one matches your natural thoughtpatterns more closely (be that abstract or concrete modelling) or educative efforts (it might be that one is easier to understand), or ease of implementation, that would be an argument for or against it.

Although Java might be conceptually weak, empirically speaking it's a big winner, because developers appear to value ease of education, connectedness with others, availability of jobs and job security more than feeling stupid for not understanding lambda calculus. (that doesn't render formalist approaches invalid)


You absolutely do not need to understand lambda calculus to leverage FP. (And similarly, you don’t need to understand Turing machines to leverage imperative styles.)

Tools like ReactJS and regular expressions would indicate that the market for FP is quite large in spite of the popularity of Java as well, IMO


How much great learning the student could have done if he had not been insulted and physically abused by an impatient "educator".


This gave me a good chuckle. Who or what is Qc Na?


> I'll take some koanic license and combine Norman Adams (alleged source of "objects are a poor man's closures") and Christian Queinnec ("closures are a poor man's objects") into a single great Zen language master named Qc Na.

(From https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/...)


Also known in python as "if your class has only two methods, one of which is init, it's a function" in the "stop writing classes" https://www.youtube.com/watch?v=o9pEzgHorH0

EDIT: typo, changed link


I have a few little classes that are clients for network services. They have a constructor which sets up an HTTP client or socket or something, and maybe prepares some metadata, and then a method to make a call to the service.

I could write these clients as lambdas or nested functions which close over the stuff which init creates. But why? An object makes it much clearer that there is state.


I agree. I think of this as a functor pattern: object that supports some params being bound at constructor time, and other params set when the function is called later.

In languages that let you operator overload function call syntax, you end up with an object that, once constructed, supports being called like with same syntax as a function call. This works easily in python (define __init__ and __call__ ), and you don't have to fight the typesystem to structure code that will accept both a callable object or a function.

Another perspective of the whole thing is that you have a function with many arguments, then you curry to bind some arguments, then pass the resulting function with remaining free arguments to be called.

I prefer structuring code as functor objects as it lets you access the bound parameters as attributes (if you want to expose them) which can also sometimes be useful in code or in test


> I agree. I think of this as a functor pattern: object that supports some params being bound at constructor time, and other params set when the function is called later.

> Another perspective of the whole thing is that you have a function with many arguments, then you curry to bind some arguments, then pass the resulting function with remaining free arguments to be called.

It's not the same, though, because a socket etc is being constructed in the constructor. Here's an abridged (and possibly wrong!) version of a monitoring client:

    class Monitor:
        def __init__(self, monitoring_url, app_name):
            self.monitoring_url = monitoring_url
            self.session = aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=1))
            self.details = ujson.dumps({'app_name': app_name, 'pid': os.getpid()})

        async def send(self):
            async with self.session.post(monitoring_url, data=self.details, raise_for_status=True) as resp:
                pass
If you treat that as currying, you will create a new ClientSession every time you call ping(). A ClientSession contains a connection pool, so that means you will create a new socket instead of reusing one.

> In languages that let you operator overload function call syntax, you end up with an object that, once constructed, supports being called like with same syntax as a function call. This works easily in python (define __init__ and __call__ ), and you don't have to fight the typesystem to structure code that will accept both a callable object or a function.

In Python and Java, you can easily refer to bound methods to produce callables from objects, so this seems like unnecessary work.


One of the great tools to use here is `functools.partial`

After that, it's a question of taste. Agreed, callable objects can be useful but if you don't modify your "self" during function calls, partial funciton aplication might be clearer.


If there is state then it makes sense for it to be a class

But the parent comment applies


Using init/a constructor implies state, does it not?


I think that depends on what we understand by 'state'. Do we count immutable data? Consider faking a 'bind' with something like:

    var quadrupler = new ConstantMultiplier(4);
    var output = quadrupler.applyTo(42);
The 'quadrupler' instance is stateless in that it's immutable, but it presumably has a (constant) member to store the value 4.


After ES6, I jumped at the opportunity to write classes in javascript, but lately I've realised I barely write classes anymore, but I write a lot of functions that return functions. And this example is perfect for that.

For example, I recently wrote this:

  const labelizeElement = ((element) => element.type.replace(/\s+/g, ''));
  const labelizeRelation = ((relation) => relation.type.toUpperCase().replace(/\s+/g, '_'));

  const groupByLabels = (labelizer) => (set, element) => (
    set[labelizer(element)]
      ? { ...set, [labelizer(element)]: [element, ...set[labelizer(element)]] }
      : { ...set, [labelizer(element)]: [element] }
  );
  const groupByElement = groupByLabels(labelizeElement);
  const groupByRelation = groupByLabels(labelizeRelation);
I needed two functions that did almost the same thing, but not quite. I don't want to write a ton of nearly-duplicate code, so instead I extract the bits that are different, and write a function that I actually need.

So yes, I'm inclined to say that only mutable state needs a class. And the more you get used to working with constants and immutables, the less mutable state you're going to find.

I used to be a big fan of OO, but somehow experience seems to have landed me in the functional programming camp.


Personally I'd avoid currying and go with a function with three params, like:

    const labelizeElement = ...
    const labelizeRelation = ...
    
    const groupByLabels = (labelizer, set, element) => ....
    
    const groupByElement = (set, element) => groupByLabels(labelizeElement, set, element);
    const groupByRelation = (set, element) => groupByLabels(labelizeRelation, set, element);
This makes it slightly simpler to create a binding function around groupByLabels which fixes, say, the middle argument. I also think that avoiding the chained use of the => operator makes the code more readable to the average JavaScript dev.

C++ has (strange looking) support for the bind pattern in its standard-library now [0], and Python3 has functools.partial but it only supports left-to-right binding [1] (not that there's any particular reason to avoid lambdas).

[0] https://en.cppreference.com/w/cpp/utility/functional/bind

[1] https://docs.python.org/3/library/functools.html


Yes. If nothing else, the calculation of that immutable data may be costly enough to motivate not calculating it multiple times. Hence you want to treat it as state. But that doesn't mean you want to expose it to the user of the class which may have a nice simple interface of cheap operations after the init is done.


    def __init__(self):
        pass

Not necessarily. Or the constructor is just setting some values that are later used by the calculation but are not modified and used later


> But why?

This is my key question, and I ask with minimal assumption that either one is better.

What are the criteria we are even using to judge? Clarity that there is state is one. Elsewhere there are arguments about ABI that I fail to have practical knowledge of. There was some discussion of the data structure describing your program that completely lost me. Explicitness is lost when discussing closures.

What makes one better than the other in ways that arent entirely subjective?


I/O in constructor ? I thought that is an avoid


And if you have many methods, but only one 'public' method, use a closure.


I think before you do that, you should be careful to ask why the methods are private. Frequently, code like that exists because the private methods are reusable code that is not related to the title of this class. So the solution is first to make the private methods into public methods on separate classes.

And then the resulting classes probably meet some other rule that says "reduce trivial classes to pure functions or closures".

Generally speaking, before a person writes a private function, they should remember that it means "if you misuse it you'll break my otherwise unprotected invariants." Whenever that's not true, you shouldn't write "private". In particular, it doesn't mean "I was too lazy to structure my code correctly so I tried to hide it from the public implementation".


If the code could be extracted into generic top-level pure functions, I generally don’t unless its used in more than once in the codebase. Otherwise I find a number of contextless generic functions/classes without any idea of their use exhausting to look at eventually.

I have some functions that live in closures that are fairly generic. I’m always contemplating extracting them into a higher level. But they’re currently only used in one place, and they currently reside close to that one place, and this seems sensible.


Classes with one public function can be converted into a closure. Classes with two or more public functions can be converted to a closure that return those functions through an object. Then classes and closures become closer.

Classes, of course, offer inheritance. Yet most prefer composition over inheritance. Classes, too, offer polymorphism through typing. But dynamic languages use duck typing. In most dynamic languages where duck typing and composition are used, I find little need for traditional object orientated programming.

I frankly prefer closures, composition and duck-typing over the classes, inheritance and polymorphism through typing. The only thing missing is type checking and Golang's interfaces offer a way through that without the overhead of traditional object orientated design.


I often run into that situation because I'm trying to make the main method a lot simpler by breaking it up into smaller methods with good names. Why should I avoid it?


Your link goes by means of Google; here's a direct link: https://www.youtube.com/watch?v=o9pEzgHorH0


Or it's a named stateful or immutable data container with validation? You don't always want to use primitive types. But I am not a Python dev.


Or anything that needs validation before you try to run the main part. e.g. the init() method may give you a shopping list of data you're going to need to pass to run() but which couldn't have been deduced until init() validated its parameters.

Sure, you could handle this the functional way by passing a getData() function to run() so it can do so itself but I'm not sure that's any more readable.


Then build a function from validated parameters (or partially apply some parameters).


Yes, this is the point of `dataclasses` and `attrs`.


Python doesn't have private members, so that doesn't help you as much.


Members in Python are never private. Variables that are supposed to be used internally only are marked with and underscore, but that just conveys intent and isn't enforced by the interpreter/runtime. But you can emulate private data like this:

    >>> def a(u, v):
    ...     def b():
    ...         return u + v
    ...     return b
    ...
    >>> b = a(1,2)
    >>> b()
    3
Like this, there is no way to access u & v from b.


Looks like classes can't be closures, though:

  >>> def f():
  ...     x = 5
  ...     class Blub:
  ...             def incx(self):
  ...                     x += 1
  ...             def getx(self):
  ...                     return x
  ...     return Blub()
  ... 
  >>> j = f()
  >>> j.incx()
  Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 5, in incx
  UnboundLocalError: local variable 'x' referenced before assignment


When using it like this, the interpreter opens a new namespace where the variables are bound. The x is declared in the outer scope, so it can't find it. However, you can ask python to keep searching vor the name `x` in the closest outer scope, that is what the `nonlocal` statement is for:

    def f():
        x = 5
        class Blub:
            def incx(self):
                nonlocal x
                x += 1

            def getx(self):
                return x

        return Blub()

    j = f()
    j.incx()
    print(j.getx())
This will print 6, as expected.


Python has private members through double underscore methods


That’s not their purpose and they’re not really private. Leading double underscore methods cause name mangling (the class name gets added to the method name) and they’re used for solving specific problems with inheritance. Don’t use them just to make something private. You’re screwing up your inheritance if you don’t realize what you’re doing.

Leading single underscore is private by convention. Still should generally be avoided, but it’s the proper use.


They're only private in a sense that you're not supposed to access them, there's no enforcement by the language in any way beyond renaming them `_<class>__<attr>`. They're still accessible and they still show up in `dir`, `__dict__`, etc.


At the same time, if your function has an expensive initialization step that only needs to be called once (and doesn't change between calls), then the pattern of using `__init__()` + `__call__()` can be very nice.


I would consider this pattern:

    def x():
        expensive precompute
        def y():
           cheap stuff
        return y
It's a matter of taste I guess.


For comparisons sake:

  class Thing:
    def __init__(self, arg):
      # expensive precompute
      self.result = ...
    
    def __call__(self, arg):
      # cheap stuff that uses self.result for something
      ...
And then usage is:

  f = Foo("some val")
  bar = f("another val")
So yeah, both definitely do the same job. And your pattern is probably faster. But I generally like using Python's dunder methods as I think they allow for more consistent/recognizable code patterns.


Do python linters catch that? It is not a code error and sometimes not a design error either, but a warning would be useful, and linters may have the tools necessary for that.


It's a warning in pylint by default: "Too few public methods (1/2)"


Here is how to disable it:

# pylint: disable=too-few-public-methods


what if it has just values (like an enum)?


I'm always curious when someone recommends reading The Elements of Programming Style. I had seen so many recommendations that one day I decided to dive into it and read through it all myself. The book is a fine read and an interesting snapshot of what coding in 1974 was like. Seeing Kernighan and Plauger analysing and understanding coding issues and formulating early coding best practices is really interesting. However its value as a reference for modern day programmers is heavily overstated - you'll spend most of your time staring at big listings of early Fortran and PL/I code for one thing.

So read it, enjoy it, smile when you see guidance that's still applicable today ... but do not expect to achieve any sort of enlightenment. And be wary if anyone who tells you otherwise.


I’d be wary just from the fact someone tried to piggy back off the authoritative work of Elements of Style.


Elements of Style is not authoritative. Widely cited, widely praised, widely criticized.

Find an author you enjoy, subject their work to an analysis strictly driven by Elements of Style, and you'll find they fail to measure up. Even if the book in question is Charlotte's Web, by the way.

I'm not saying the book has no value, but it's far from authoritative.


I don't know – Robert Bringhurst's Elements of Typographic Style is excellent.


This reminds me of the blog rant "Execution in the Kingdom of Nouns": https://steve-yegge.blogspot.com/2006/03/execution-in-kingdo...


Luckily Java 8 introduced streams and functional interfaces - the latter of which is admittedly very object-oriented way of passing around functions.


For those wondering what I mean:

To support backwards-compatibility, Java could not simply introduce functions as first-class objects. Instead, it works like follows:

Syntax for a function `foo` which itself takes in a function called `intConcat` that takes in two ints and spits out string:

    foo(BiFunction<Integer, Integer, String> intConcat) {
        // ...
    }
Notice that I have to explicitly state "BiFunction". There is also "Function" for single-argument functions, and nothing for more arguments (there are also `Runnable`, `Consumer` and `Producer` for fewer arguments in-or-out). This is because BiFunction isn't actually a function - it's an INTERFACE! Any class that implements 'apply' and 'andThen' functions with the right signatures will satisfy it, and can be passed in. You can make your own class and Java will happily accept it into this method.

Java then just adds some nice syntactic sugar to make stuff look like functions. E.g., if you do want to define an anonymous lambda like

    (x,y) -> "" + x + y;
What happens under-the-hood is that Java defines an anonymous BiFunction class. You can assign it to a variable and do everything you would want to do with an object:

    BiFunction<Integer, Integer, String> bif = (x, y) -> "" + x + y;
I can call bif.toString() and all those other default methods defined on objects in Java. It's really not a function, it's an object holding a function:

    BiFunction<Integer, Integer, String> {

        String apply(Integer x, Integer y) {
            return "" + x + y;
        }

        // ...
    }
and if you were to go and implement your own BiFunction as above (filling in the blanks) - you could pass it around exactly the same places as your "anonymous lambda" and it would work exactly the same way because it IS the same thing.

Like I said, a very object-oriented approach to functionality.


It's referred to in the article.


Believe it or not, that was added after my comment! (In an edit at 28/05/2020 14:30 - https://web.archive.org/web/20200528143032/https://quuxpluso... :)


Reducing OOP to implementation detail features will yield poor results. The flexibility stems from an improved means of analysis in that types and entities can be identified more easily. By using classes and objects you're retaining flexibility because they can be interchanged; It's easy to create an object that pretends to be a function; it's harder to make a function retain state later.

The example is nice; it removes a lot of code but also removes the deferred execution aspect of the solution. Calling `countDominoTilings` will make it run in that instance; encapsulating the whole thing makes it able to pass it around and execute the potentially expensive `.count` method later. If applied properly, this can give you quite a lot of flexibility; for some memory and performance tradeoff; but that's the cost of abstraction.

Instead of looking for how FP and OOP are dissimilar, why not start looking for similarities instead? Objects are just higher order functions. Calling ctors is just partial function application.


(1) Interfaces / typeclasses / traits are good; inheritance is bad. An object is a family of partially applied functions with some shared state; this means tight coupling between them, rather often unwanted.

(2) Not having mutable state is the point. The more state you have, and the more code paths can mutate the state, the harder it is to reason about the program correctly. Examples of various degrees of hilarity / severity abound.

Sometimes shared mutable state is inevitable for performance reasons. It should be well insulated from other parts of the program then. Same applies to other effects, such as I/O. The standard approach is a core of pure functions with an outer shell of effectful / stateful procedures (as opposed to a mix of them).

While I'm on this soapbox, let me remind that Smalltalk was initially envisioned as an actor system, something like Erlang (hence "messages"), but hardware limitations did not allow to implement the original vision.


> inheritance is bad

That depends. Inheritance makes it easy to break encapsulation (which is bad -- agreed). It can be hard to model "Is-A"-Relationships properly, but I wouldn't call it inherently bad. A circle isn't an ellipsis; but a chair is furniture. The quality of code stems from your quality of thought.

> this means tight coupling between them, rather often unwanted.

That depends on your design. Coupling is the whole point, you do objects because you want to couple. Fraction.reduce, Fraction.add, Fraction.subtract, Fraction.multiply. Why not have them as as a cohesive unit, all these functions must understand the details of fraction anyway. Why not couple them?

> Not having mutable state is the point.

I agree. But objects never force you to publish their state; that's bad education. Getters and setters should be avoided. The Fraction above can be made immutable easily, Fraction(1, 2).add(Fraction(1,4)) --> <Fraction: 3/4>, leaving the old ones intact.

But then, it depends on how you communicate state.

I believe `Connection.Open()` should return an Object of Type `OpenConnection`. I believe that state changes should be communicated by changes of identity (either a new instance or a new Type), but ideas like this are most often answered by waving torches and pitchforks in front of my house at night (figuratively).


Inheritance reminds me of https://www.pcgamer.com/heres-whats-happening-inside-fallout...

Your OpenConnection idea might make sense in some abstract way, but one thing I know about connections is that they have a habit of closing. They will close without any notice to your programming runtime, because the operating system will do it. What happens to your OpenConnection object then? Well it becomes invalidated, and now you have a nonsensical object hanging around. So you read from it, get an error, and... now what? Replace it with a ClosedConnection?


> What happens to your OpenConnection object then?

It raises an exception. This interrupts the normal flow of things and asks you to deal with the problem asap. If necessary, the handling code then could try to reconnect or abort. If desired, it could return a closed connection to convey that state-change, so that calling code is made aware that it needs to reconnect first and can't just reused the now closed connection. You could revert it to a normal connection (a "Has never ever been opened to begin with"-Connection). Depends on whether your driver/adapter/underlying connection thing cares about a difference in initial connects or reconnecting. If the handling code can't deal with it, it can bubble the exception up one level.

Swapping a `Connection` for an `OpenConnection` isn't heretic by the way, such structures are described by the state pattern. Objects model state, Exceptions model events (state transitions) but the later isn't explicitly described in the original Gang of Four book that way. I just found that exceptions are very usefull for this, given that you react to them in only a limited scope.

Be aware that this idea is culturally dependent. In Java, Exceptions are often discouraged from being used in such a way (exceptions should never be used for control flow and only convey error cases), in Python it's normal to communicate state transition that way, e.g. for-loops watch for StopIteration exceptions.


> inheritance is bad

Inheritance is a common-sense solution to the problem of creating something similar to an existing object, but different in some key aspects. It's programming by difference. Nothing bad about it until you start using it for type information.

>An object is a family of partially applied functions with some shared state

This is not true because of dynamic dispatch and because in non-sucky languages objects can interpret messages.


Nothing stops you from doing OOP with state that is immutable once initialized though. Likewise, nothing stops language designers for enforcing that in their OOP languages. You are arguing against a strawman.


It's easy to create an object that pretends to be a function; it's harder to make a function retain state later.

I read that as an argument in favour of functions. Implicit state is pain. Making state explicit is a great advantage of fp-style programming IMHO.


I am not sure I understand your differentiation of explicit and implicit state. I believe you refer to Objects that change their internal values over time; in that calling the same function twice yields different results. Of course, such objects can be a pain to use.

Objects are all about making state explicit. Constructors connect different primitive values to a higher order concept, goal or task and checks invariants; The object's class gives the primitives a type which IS a form of state. A Year(2005) could guarante that the integer is > 1970; an int can do that on its own. 'hello@example.com' is not just a string; it should be an Email address and modelling this with an explicit class communicates state: that string is not just any string (all of Shakespeare's work in mandarin in base64) but a specific kind of string (an Email address). The mere existence of a valid object then communicates state, as do all types. I'd argue: Objects are all about making state explicit.

Still, it is really easy to abuse objects and make state implicit as you described above. I would argue that getters and setters are plain wrong to begin with, but that is a nother topic.

For example, if you do `Wallet.pay(amount)`, Ideally, you should get a new instance of a Wallet with the reduced amount, instead of reducing the internal credit variable. For many, this seems to be the wrong kind of modelling though, because when immitating the real world, you don't copy-clone your wallet physically when tipping a waiter.

Objects can and should be created immutable, but then, sometimes working with state is fine. For example an iterator (calling `bookmark = Boomark(book); bookmark.next()` a hundred times gives different page each time).

Imagine OpenGL; you send a lot of data to your graphics card and then tell it what to do with that data. Sending data is quite expensive. During rendering, you tell the pipeline what buffer to bind and what to draw at a single point in time (i.e. a frame). This is easlily represented by objects that keep track of the state. Your object-graph just remembers what buffers are bound. I, too have seen tangled and messed up designs; but I would still argue that thinking in state (my change does not go away) is intuitive for many people. Why not model systems that work like this explicitly? Shoehorning this into stateless pipelines can be quite difficult.

The problem isn't functions or objects (thats just fancy pointer syntax really) but that noone has figured out how to modell processes that work on symbols that change over time properly.

We won't fix this issue here; let's agree that we all like our state as explicit as possible as to avoid surprises and relieve or working memory.


I think your Wallet.pay(amount) example argues for a different conclusion than you arrive at.

I have a Wallet with $500 in it. I call Wallet.pay(100). In your approach, it returns a new Wallet with $400 in it. But I also still have the old, unmutated Wallet with $500 in it, which could be referred to by mistake (or by malice). That's probably not the best argument for immutable objects...


If you limit the scope of usage, the old wallet should be garbage collected as soon as you have the new one. If you don't want to rely on that, or the point in time when gc happens is important or you're dealing with sensitive data, then the environment should allow you to perform appropriate cleanup actions and for you to utilize a different means to control and protect the data.


As josephcsible said in a post parallel to yours, affine types can do this for you. If not, though... if you never mess up and keep a reference to the old one, you're good. I don't care when it's garbage collected; if there's no reference to it, it's not going to be used. But if anybody, ever, keeps a reference...


That's actually an argument in favor of linear or affine types. In your example, you'd still get a new Wallet from that function, but the compiler would keep you from accidentally using the old one afterwards. That way, you can't make that mistake, but still get the benefits of immutability.


I will admit that I don't know what "linear types" or "affine types" are. Your answer makes me feel like I am trying to convey an idea for which you have the perfect formalism / meta vocabulary. It might be that objects, or at least the way that I know how to use them in my favorite languges, are just a (potentially limited) implementation of said formalism.

I feel that objects offer a flexibility benefit though (which is why they often elude formal approaches) for the cost of purity.


Rust is probably the most mainstream language that uses affine types. Once you call a function that takes ownership of a value (e.g., `drop`), the compiler won't let you use it anymore after the function call.


Is that true though? You can pass the function itself. If your interface is a function that takes no parameters and returns an int, just wrap this function with another function that takes no arguments and moves the arguments into a closure.

Doing it in a class is forcing these assumptions on your user - that they need this added complexity in all of their uses, and will make for unwieldy code when they don't, or even more unwieldy code when they use a facility that doesn't satisfy the interface you decided on. Let your user be responsible for deciding that and wrapping your code for whatever use they want to do with it - if you write library code (library and not framework), it should be as simple as possible to allow flexibility for the user.


This circles back to my original argument. Here:

    def adder(x):
        def add(y):
            return x + y
        return add

    add_five = adder(5)
    add_five(10)
How is that different from:

    class adder:
        def __init__(self, x):
            self._x = x
    
        def __call__(self, y):
            return self._x + y
     
    add_five = adder(5)
    add_five(10)
Did you have something like that in mind? Do I understand your idea correctly?

They both allow you to achive the same thing. The syntax doesn't matter, they both produce the same result (in Python, that is, which is a bit unfair, because there, functions are objects to start with -- but even if this was not the case, then the object would require namespacing which is negligible). It depends on whether you want to `think` with a closure or not.

I propose that differences in OOP and FP on this level are irrelevant, but that the magic lies in the naming chosen here. An "adder" is something that has continuity and identity, and the calculation is performed later. I don't care about how it is constructed. There is nothing wrong with just calling add(1, 2) -> 3, but objects give you this deferred stuff for free (sorry, not free, for the price of some memory).


The difference I was aiming at is that you (a library author, whether public or internal) shouldn't implement adder, you should implement add. And let your caller implement adder using add, if they require it. If you only expose adder, you limit possible uses for your library.


If I'm writing a library for public consumption that performs a non-trivial operation (the only time I would write a library for public consumption), I absolutely do assume I know better than the calling code.

I feel like most of these threads boil down creating stawmen of different situations in which FP or OOP fall down, and then declare that case the most essential problem in programming.

Programming is hard. Full stop. The most essential problem is programming is that most of the people engaging in it are not adept at it enough to avoid painting themselves into one of many, very hairy corners, regardless of what programming paradigm they are using. Excel users, coding boot camp graduates, PhD candidate scientists, mechanical engineers, computer scientists who never learned how much Dijkstra liked to troll people and took him way too seriously.

You write the code that does the job. That's not "does the job" in the sense that people who say things like "good results can be made in any programming language, including PHP" mean. That's "does the job" in the sense that it also doesn't blow up when you drive two motorcycles over it after having only tested it with trucks. Bad results can be written in any programming language, even Rust.

And the only operative difference between those situations is there experience and study of the programmer. You don't get great programs by choosing particular languages or programming paradigms. You get them by hiring great programmers. Who then have specific languages and paradigms that they use for different situations.


If you know better than the calling code, you're probably writing a framework and not a library :) A library should not be limiting, and should be easy to adapt for use with other code. Frameworks are highly opinionated so it's ok for them to only work in specific use cases (in exchange, they offer increased ease of use for those cases).

For example, the Golang Echo HTTP framework supports generating Let's Encrypt certificates using AutoTLS mode. It's not very configurable (and for my use case, that was a limiting factor), but because it was built on top of the autocert library, I was able to use the library directly and make it work with Echo in my use case. If the library was too opinionated, I would've had to fork it to use it in my use case, which was not anticipated by Echo's author.


> it's harder to make a function retain state later.

I do not agree with this kind of argument. If you need to transform the function into an object later, you can just do it later. Even in a huge codebase it does not take much time to do it.

The `what if [...] someday` argument only leads to unnecessarily complex and expensive code, and most of the time you will never actually need it.


It's not so much about "I need that later", but to start thinking about a program differently to begin with. If you think about the things that "are" rather than what should happen in what order, you get the chance to rearrange everything and do some optimizations that an eager processing might prevent you to do. Most of the OOP power comes from uniformity, which many systems break unfortunatelly. When you work with a system in which everything is an object, you can start to relax a lot, although everythin is conceptually slower. Most software doesn't even have to be that fast. If it has to be, feel free not to use Ruby.

The tradeoff is most often performance versus comprehensibility. I'd argue in favor for the latter. Of course, ergonomics and ease of use are hard to measure (but not impossible, although empirical studies are quite difficult to do and expensive), but the tradeoff is similar for all higher level languages. Consider the overhead for a class `Year`:

    `Year(2004)` --> valid
    `Year(200192)` --> Exception 
In its constructor validates integers.

Insane! Expensive! you might say. All it does is encapsulate some integer! But I'd take that little overhead over the scattered insecurity of my colleagues every day, who in every calling method will do the same "if then that else"-check for the year range over and over again, when they are handed an int and need to find out what's in it. The class provides locality for my concern that I only ever want to deal with valid years.

When, in your system you find a year-typed object somewhere, it is guaranteed that this is valid. This creates peace of mind, which is way more expensive than RAM.


It seems to me that this is mostly a design problem. Why would you need to check this data everywhere?

Checking the validity of the data is only necessary once. In a web context, this is the responsibility of the controller. Only once the data is sanitized should the controller inject it into 'anything else'. Sanitizing data is not the responsibility of a model/service/repository/view/anything else, and trying to do so indeed leads to a lot of bugs and headaches.

Having this kind of object that checks your data and may throw exceptions anywhere on the code only augments the failure surface of all your codebase by adding unexpected exceptions.

Comprehensibility is in no way related to using objects. Wether you choose an integer, a class or a subtype of integer does not make anything more readable, it all depends on the quality of the naming. A var named `year` or `startYear` will always make the code more readable than a var named `start` or `begin`, whether it contains an object or an integer is irrelevant.


> Checking the validity of the data is only necessary once.

Exactly, which is why a class is the perfect singular location to place it. The object itself is just a pointer, the method doesn't get copied around, so object appear to be the perfect method to localize code.

> unexpected exceptions

True, exceptions introduce a communicative issue, but so does returning in-band values.

For example, what should the result of

    open('file.txt').read()
be, when the current user does not have permissions to read file.txt?

Different approaches exist:

    status, content = read('file.txt')
    content = read('file.txt', &status)
    status = read(&content)
I wouldn't argue against any of them, although I have my preferences of course. I like the exception model here, but you are right, rare exceptions, communicated poorly can be surprising and painful.

> Comprehensibility is in no way related to using objects.

On itself this statement is false, ... (hear me out)

> It all depends on the quality of the naming

... But this makes me understand what you're trying to say, and I 100% agree with it. In fact, I conducted some experimental research on identifier naming: https://link.springer.com/article/10.1007%2Fs10664-018-9621-... (Sci-hub or I can provide a preprint).

You are right in that objects and their use don't automagically turn a codebase in a field of readily availabe knowledge. Many mechanisms applied in OOP languages really work AGAINST comprehension (for example, buried exceptions originating from deep within an object graph). But still, objects are tightly coupled to comprehension, even historically speaking. They were first used to make it possible to model physical simulations without requiring users to know much about computer architectures (Simula 67). Objects are meant to "model", that is, symbolically represent concepts, entities or physical things in such a way that they might show agentic behavior. This is fundamentally different from having stupid data, smart functions but actually a completley different means of "Erkenntnis" (translates to "insight", but is more accurately conceived as "Epistemology"). The relationship between objects and readability / comprehension is complex, but I wouldn't call them "in no way related" (OOP can break comprehension, but it was invented to improve it. Irony.)

Also I would, again, like to second your words: Identifier naming might be the most imporant aspect of readability and comprehensibility.


You only lose deferred execution in this case because it didn't go far enough away from OOP and toward FP. For example, since Haskell is a pure functional language, it can be lazy by default, meaning the function-only version transliterated there will have deferred execution.


That sounds very plausible. But then, is OOP vs. FP an argument about verbosity in syntax to achive the same thing eventually?


Why not make max or pow an object then? If you can model something as a pure function, then it should be a function. You can always add memorization later, if needed. OOP is fundamentally different from FP, objects are not just higher order functions, they are also coalgebras. There is no need to force every peg into a coalgebra hole.


Yes, why not have max and pow as objects?

`max` and `pow` can either run eagerly (as functions) or they can run lazily and allow for interesting lazy things. For example, if you put them into a lazy object graph, you could perform optimizing measures.

Check this out:

1/2 * 2/1 == 1

Would you actually calculate each intermediate result of that expression? Or would you just reduce the fraction? Should 1/2 be exectuted and yield 0.5 immediately, or could it be useful to have them hang around some more? FP vs. OOP is the difference of understanding / as "Please divide 1 by 2 now" vs "That IS a fraction; one half".

> There is no need to force every peg into a coalgebra hole.

True, but how awesome would it be if I did that anyway :D

Regarding `max` and `pow` as objects: Yegor Bugayenko argues for a similar thing in his book "Elegant Objects".


You can get the same effect if you model the expression as a free algebra (ADT). You can then simplify the terms in the same way. The paper: "The design of a pretty printing library", by Hughes, explains this nicely.

The main difference is that algebras are more natural in FP and coalgebras in OOP, but they can mostly do the same things, just a bit differently. Actually you can also do algebras in OOP as well (visitor pattern) and coalgebras is FP.

Basically my point was that you should use the simplest abstraction that gets the job done. Even if you implemented max and pow as objects, for your specific use case, you would probably just call the pure functions inside.

Thank you for the book recommendation.


A lot of time where I just write simple functions, I end up having to wrap them into objects because it is much more convenient when I want to do static polymorphism - sure, that function I'm writing now doesn't need state but then two days after I have to refactor because the next strategy I use does indeed require state. e.g. :

    template<typename F1, typename F2>
    struct MyAlgorithm {
        F1 f1; F2 f2;
        void operator()(...) { ... f1(whatever); ... f2(whatever); }
    };
you can't just pass functions to F1 or F2, they have to be objects.

I remember this happening to me at least half a dozen times so far so now I just don't waste time and write them as structs with an operator() directly if it's anything that is meant to be used as a callback somewhere.

See also item 46 on Scott Meyers' "Programming with the STL" which points out a few other issues which can arise from using functions.


This is a limitation of C++, not functions.


yeah... but that's a C++ article, written by someone who contributes to the C++ standard... the context seems unambiguous


> you can't just pass functions to F1 or F2, they have to be objects.

Why?

https://godbolt.org/z/2kJsT9


I'm new to C++, what are lines 11-12 for? I see that it doesn't compile without it. It doesn't seem to be the new 'arrow return type' syntax because there's no auto keyword.

Edit: I found it - it's a 'template deduction guide' https://stackoverflow.com/questions/40951697/what-are-templa...


Yes, the functionality was added in C++17, it is quite convenient, before you had to write boilerplate make_X 'constructor' functions.

In C++20 this specific deduction guide is no longer required as it is implicitly generated for template aggregates, so even less boilerplate.


that only works if you are in a context where you can deduce the type from ctor arguments, which is definitely not all cases (for instance, storing as a member and initializing in a ctor, etc...)


You can then specify the function pointer type, although mildly inconvenient. If you want to use lambdas though, then yes, you are out of luck as their type is unnameable.


but that fails as soon as you have a case where you want to pass an algorithm implementation that does have state. That was my point - whenever I assumed that I would not have state... well I ended up needing to refactor everything because I ended up needing to add a new strategy that did require some state later.

In the context of C++, struct / class types are more generic that function pointers and there's no real shortcut around this - that's how the language is specified.


There is nothing wrong with writing generic code that works both with functions and function objects. Note that stateless (empty) function objects can often be more efficient than function pointers.


It's a trade off - if you want dispatch efficiency then use functions, if you want flexibility use std::function and you can store functions, functors or lambdas.


> struct MyAlgorithm

This is a particularly bad name for your situation. An algorithm is not an object. Why not "my_state"?


> Why not "my_state"?

because it's a HN post and not real code ? And it contains the code of the algorithm, in the operator()... function. So my_state would only tell half the story.

When I use it, what do you think makes more sense :

    GameExecutionAlgorithm<TensorflowGameStrategy> game_algo;
    next_positions = game_algo(...);
or

    GameExecutionState<TensorflowGameStrategy> game_state;
    next_positions = game_state(...);
> An algorithm is not an object.

That is however completely conflating two different things. By that line of reasoning you couldn't write algorithms in e.g. Java or Smalltalk because there everything is an object and "an algorithm is not an object" => "there are no algorithms in Java / Smalltalk".

Objects are just a syntax construct without attached meaning (or, if you did attach one at some point by reading some 1980 OOP book, please detach from it as that only causes confusion).


>When I use it, what do you think makes more sense : > (...)

None of your examples make any sense to me. Why not name it simply "game"?


What about something like "in_order_traversal", which is an algorithm and you need to keep track of the traversed stack. You could either write it with functions in which case you would need to pass the stack as a parameter and have a constructor/initial state version of the function that passes the stack to the main function, or have a functor/function class that keeps the stack internally.


So there are 3 hard problems in programming.

1. Naming things, and .. I forget what the other one is.


likely "game" would be something that would be about the whole game, including graphics, sounds, etc.


> std::cout << DominoTilingCounter(4, 7).count() << '\n';

> // Fails to compile!

What? But this literally does compile. The author is pretty well-versed in C++ so this one is a bit baffling. Just because it's a temporary doesn't mean it's forced to be const or something.


The real meat of this post is:

> "No more class, no more worrying about const, no more worrying about memoization (it becomes the caller’s problem, for better or worse)."

By making the memoization the "caller's problem", this refactor has completely changed what the code does, for the sake of calling out "an OO antipattern".

Yes, there are of course other ways to implement some form of memoization or caching without classes. And without knowing the particular use case, it's impossible to form an opinion on whether it should be the caller's responsibility or not. But I find it unconvincing to make an argument for "here's a better way to solve problem X" and then present a solution for solving problem Y.


The memoization capability only disappears in the very last step, which I would argue is not the meat of the post. Up to that point, very valuable refactorings and simplications have been applied.

One could very well have replaced the final transformation with a factoring out of the algorithm into a free function, which the constructor merely calls and caches the result of, without meaningfully changing the point of the post. (In fact, that would call out the memoization capability as an orthogonal aspect even more strongly, since you could reuse the same construction for memoizing other free functions.)


Single-function classes make sense if you want the computation to happen lazily on-demand (it may be resource-intensive in CPU, GPU, storage, I/O, etc). The class acts as the place to hold the parameters needed for it and the result if/when it is computed. There's nothing wrong with that.

Down the line you may often need the capability to discard the computation to save storage (and maybe re-do it at a later time), at which point it is not even a single-function class anymore.


> The class acts as the place to hold the parameters needed for it and the result if/when it is computed. There's nothing wrong with that.

Right, and the constness problem can be overcome by making some fields mutable. This is exactly what "mutable" is for.

If the requirement is to have a lazy, memoized computation, then a class is good. If the requirement is to have an eager, non-memoized computation, a function is good. The article keeps shifting the goalposts and beating strawmen that implement different specifications. It's not very good at making the point it thinks it's making.


Mutable in C++11 land holds a weird space because the threading model says that const member methods are thread safe, which mutable member variables are not.


> threading model says that const member methods are thread safe

that's only true for standard library objects, although it is an useful guideline for all code.


I don't think that's true.

> A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this

Consider the case where I invoke `std::find_if`. It takes const iterators to a std::vector. I'm now indirectly modifying objects through standard library functions that are modifying objects by multiple threads through const arguments.

Pretty sure this is a viral requirement and using any part of the STL can effectively taint your program if you're not careful.


Yes, access to mutable members should be synchronized in const methods.


> A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this

Consider the case where I invoke `std::find_if`. It takes const iterators to a std::vector. I'm now indirectly modifying objects through standard library functions that are modifying objects by multiple threads through const arguments. Using a mutex doesn't change the fact that my usage of the STL is now in undefined behavior land.

I think the only safe thing to do is to have a mutable mutex (there may be other parts of the legalese) but outside of that you may run into serious trouble at some point compilers become intelligent enough to enforce that undefined behavior. Realistically I doubt any compiler would ever enforce that legalese because of how const & mutable both are interpreted to mean "this variable is thread-safe" by everyone involved so a mutable variable that is synchronized in some way will always work fine.


Yup. As in everything, the INTENT is very important when judging someone's code.

Unfortunately, this makes it very easy to set up strawmen, then add a click-baity title for the perfect gift for "your side".


Using classes and objects for laziness is a hack to work around the limitations of OOP. In more functional languages, there are better ways to accomplish that. For example, in Scala, you can just use "lazy val", and in Haskell, everything's lazy by default.


Finally, the first real example. Every single one so far has been pretty weak.


The zen is to realize that these are the exact same but totally different:

    myfunc(arg1, arg2)
    arg1.mymethod(arg2)
    myclosure(arg1)(arg2)


True. Python taught me that.


Author makes some valid points, but jumps to conclusions too quickly. Most importantly, code in examples that actually solves problem is omitted (`[...recursive solution omitted...]`) - and that code can be potentially very long. If you have very long function, you should break it into smaller functions. But, those functions need to operate on shared state. In some languages (Python) you can have nested functions that can operate on variables defined in parent function, but in some other languages you can't, so you can pass shared state in other ways, like function parameters or, wait for it, what can you use when you have bunch of functions operating on some shared state? Use classes! You can even wrap it simple function to have cleaner interface.

Other counter examples might be Template Method pattern etc.


What's wrong with having the functions stateless and just passing the state in as a parameter? OOP does exactly this behind the scenes.

> foo.do(bla)

is syntactic sugar for

> do(foo, bla)


I like the nested functions solution a lot.


There are a few places where you might want to capture state in an object and have one or two functions that operate on it (Command pattern is one such place). However, a long, complex function where you want to hold internal (i.e. temporary) shared state is a place where you want to avoid this (IMHO).

Imagine that your entire program is that long, complex function. It gets some data from somewhere (it doesn't matter where), processes it, and outputs it somewhere (it doesn't matter where). Once it processes the data and outputs it, the program is over.

Imagine that we didn't use a class, but still wanted to share the state so that all of the functions in our program could use it. Well, that's easy. We can make global variables in our program and then we don't have to pass parameters, or worry about the data flow in our algorithm. It all seems much simpler.

However, this is usually something that we wish to avoid as programmers -- shared state. It couples functionality and it makes it hard to reason about how the function operates. Ideally, we would like to make functions "idempotent". This means that with the same arguments passed to it, it produces the same output. This makes it easy to test: we just call the function with different arguments. It also makes it easy to reason about when debugging. You only have to look at the data that was passed to the function.

If you have shared state, then you need to be careful to set up the state to what you want before you test something. If it is really complex, then you may have some state that depends on other state. When you are debugging you have the same problem: how did that state get set? It is hard to isolate the code that is wrong. You have to single step through the entire program to understand how the state is constructed.

What is usually better is to write idempotent functions where everything it needs is passed to the function. This means that you have to think harder about how to design your code. It is more difficult to write because you can't just grab the data you need. You have to think about what part of your code needs what data and what parts shouldn't have that data. You may have to refactor your code numerous times to ensure that you can meet changing requirements.

The upside is simpler code that is actually easier to work with in the long run. It's easy to test. It's easy to debug (usually you don't need a debugger at all -- especially if you have tests). It's easy to modify because all of the state is explicit.

Even when you are writing OOP code, you should consider this as it is key to writing more simple code (at least IMHO ;-) ). It is often said that the most important thing you can do when refactoring code is to remove global state. By far, this will have the biggest impact on improving the design of your code.


I suspect the absense of free functions in Java (which is frequently the first language taught at universities) is a big reason this antipattern is prevalent.


That brings back frustrating memories of moving to Java from day one at university and trying to get up to speed. I remember a bunch of "tips" spreading around the computer lab, all related to IDE and language quirks, in a 100 level class. I hope this isn't still the case, 20 years later? It kind of spoiled me for Java, which made life difficult later when I joined a FOSS project which was built in Java.

After that experience I found returning not even just to functions but also e.g. Pascal procedures was pretty refreshing.


On the topic of eliminating simplistic classes: one of things an object is for is to enclose multiple pieces of data and provide multiple methods that access that data.

If one can compose ones problem into a lot of classes of object that only have one method then it is quite acceptable to just use closures instead:

  HI = ‘Hello’
  def greeter(title, name):
    def f():
      print(HI, title, name)
    return f
I wrote a whole bunch of Python like this, yesterday. It felt liberating! So terse!

Downside: one very common reason to have more than one method in a class is to define __str__. There will be no pretty debug printable version of my greeter() “class” for me, alas.


You can manipulate f.__name__ to improve the debugging experience, since presumably you actually care for what is in f(), not greeter.


I like it. It gives you (f)actual private fields in python, as title and name can't be accessed via the instance variable of f.

That also works in JavaScript:

    function Fraction(a, b) {
        this.value = function () {
            return a / b; 
        }
    }

    const half = new Fraction(1, 2);
It feels weird to me, that classes were eventually introduced in JS.


> It feels weird to me, that classes were eventually introduced in JS.

if you don't put value in the Fraction prototype, then value will be created each time Fraction is instanced.

But that's not why class where created. Class simplify writing classes, doing inheritance and introduce "super" static binding which wasn't possible before with functions.

Also, JS will now force you to use new when instanciating Fraction class as an object. You can't call it like a function.


It’s worth pointing out that classes in both of these languages implement another headline feature of OO: code reuse through prototypes and inheritance.


This antipattern is taken to the extreme with the `DOMParser` [1] class in the Browser.

The constructor takes no arguments and it has a single instance method that takes produces a result without mutating any internal state.

What's worse, the method returns a newly created object of another class, which in my head is an indication that maybe, just MAYBE it should have been a constructor of said other class instead.

[1] https://developer.mozilla.org/en-US/docs/Web/API/DOMParser


As it says in the HTML Standard[0]:

>The design of `DOMParser`, as a class that needs to be constructed and then have its `parseFromString()` method called, is an unfortunate historical artifact. If we were designing this functionality today it would be a standalone function.

[0]: https://html.spec.whatwg.org/multipage/dynamic-markup-insert...


> The design of `DOMParser` ... is an unfortunate historical artifact

My prediction is that in 20-30 years the same will be said about today's mainstream OOP.


The browser could be seen as a special case, as it's the most public API around. So classes are in many cases simply for namespacing and modularisation.

Interestingly, the HTML spec for DOMParser has a note about exactly your comment: https://html.spec.whatwg.org/multipage/dynamic-markup-insert... (scroll up 2 lines)


I'm fairly new to Unity programming in C# and I was surprised a few months ago that everything in C# must be in a class. As far as I can tell there is no such thing as just a function.

Now I have I have a class with some static functions in it.


A static class in C# isn't really a class, it's just a namespace to hold functions.


Classes in OOP are type dependent namespaces. That's the entire reason why classes even exist. You can now reuse function and member names inside classes instead of having to globally qualify them to prevent name collisions. In C you would do something like class_name_function_name(var, par1, par2). The OOP counterpart would look like this in Java: ClassName var = new ClassName(); var.functionName(par1, par2).

Now you might be asking if this is really such a big deal. I mean it is pretty obvious that this can be useful even if you limit yourself to static dispatch and completely avoid inheritance and polymorphism. The reality is that functional programming languages like Haskell don't support type dependent namespaces. When you name a field in a Haskell record then no other record is allowed to have a field with the same name.


A static class in C# can't have any instance data, instance methods, and can't be instantiated. It's literally a namespace in everything but name.


But it does have class scoping rules. You can have public and private static members.


C# is slowly moving away from "everything is a class". Static classes with static imports is basically a module or namespace. C# also allows stand-alone functions inside methods. But you still have to declare a static class to hold "top-level" functions, which is somewhat ugly.

Given the current development of C# I expect we see stand-alone functions in the future.


C# 9 will allow stand-alone functions in the top level program. They do however count as local functions to the top level programs, and will not be accessible from anywhere outside of that context.


Yeah, though it basically amounts to syntactic sugar at this point.


"everything in C# must be in a class"

As an aside, C# also has structure types which have value semantics - one example being DateTime:

https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...


Not to mention tuples and (soon in C# 9!) record types.


Classes are used for a variety of things. Let's look at it from a language designer's perspective. If design a language and you want to offer inheritance, specialization,.... you will often end up with offering classes. Once you have that you could decide to offer namespaces. But, a class with some static methods comes quite close to what you need to offer for a namespace. Hold on: you can use classes to combine some functionality that belongs together and come close to what you need for modules.

It's not a surprise the designers of java, c#, ... went that route.


https://stackoverflow.com/a/1027853

In honesty, statics accomplish the same thing.


In Python you can use class-like functions (source : https://charemza.name/blog/posts/python/state/you-might-not-...)

    def MyClass(...)  # Capitalised to make it clear it's a "constructor"

        state_1 = ...
        state_2 = ...

        def func_1():
            nonlocal state_1
            ...

        def func_2():
            nonlocal state_2
            ...

        def func_3():
            ...

        return func_1, func_2  # Typically, 1 to 2 funcs
I find the approach neat. Here is a concrete example where it is applied : https://github.com/uktrade/mobius3/blob/master/mobius3.py


I know that there's arguments for and against classes and functions. In recent years, I've come to love functions. But they are both tools that have cases of appropriateness.

However, I think a lot of people are looking at classes the wrong way.

In this thread, I'm hearing that classes should encapsulate functionality. While this is an aspect of classes, I don't think it's the main use case of a class.

A class should represent a type of state. That is, if you create an instance of a state, it should only support the attributes and default properties that are defined for it. This is mainly for shorthand(there's usually a syntax to build instances of classes) and for clear documentation. At a lower level, the availability of a class construct makes it possible for AST parsing to do interesting things to your OO code, although I don't know how common that is outside of JavaScript. If JavaScript didn't support class syntax, it'd probably be more difficult to implement things like decorators, both in terms of the Babel plugins that support it as well as future native syntax support. But I digress.

The way I see it, a class should be responsible for holding a mostly fixed set of attributes, setting defaults for those attributes, and have properties that compute off other properties.

What doesn't belong in a class are methods that do complex things with other objects and class instances. If your method does something complicated that affects outside state, it's time to extract it to a function that takes a context argument. This makes it easy to test your function without necessarily having to meet all the requirements of the classes you'd be using with it, and it makes the code more flexible because you can use it for other contexts besides a single class. (I know that you can use inheritance and composition to kind of do the same thing, but I think this sucks because inheritance introduces problems and composition just adds more code than just importing a function where you actually need to use it.)

More concisely, a class shouldn't be looked at as a module, or a set of related functions that do complex things, especially when not all of those functions rely on a state. That's the job of an actual module construct in the language. Classes have a specific job, which is to build an instance of a type. If your class has a lot of functionality that's not totally dependent on the class instance itself, it doesn't belong there in my opinion. Just because a class can be used like a module doesn't mean that it should.


The problem is many popular languages place a heavy emphasis on object oriented programming. Primarily functional or procedural programming, with the ability to fallback to OOP when necessary, seems to better align with the software design sensibilities you lay out here.

You can of course write FP-like code in Java, or OOP-like code in Clojure (or Golang), but I find I get more done when working in ecosystems I swim with rather than against.

I think OOP is great. In certain circumstances. Usually rare ones. I don't think the number of problems it does a good enough job solving warrants designing a whole language around, because the language will be less than optimal at solving most other problems.


That's totally fair. I'm not against OOP per se. OO and FP are both tools that, if they fit the task, should be used in a way that's not dogmatic. But obviously you wouldn't try to turn a flat-head screw with a Philips head screwdriver!


I wonder what the author means when he says “ classical polymorphic OOP”. In my experience, subtyping is the absolute worst part of OOP. More so in an enterprise setting, where after two decades, you end up with some really mutated weird ass looking ducks (Animal).

As far as functions vs class go, both are terrible to maintain in the long run. Both lead you on a never ending path of go-to-definition, because the documentation is rotten and the tests aren’t right/even there. The only difference is whether or not you want to search a few large or a metric fuckton of single responsibility files.

Maybe it’s different if your code bases aren’t crap and you’re a better developer than me.


While I agree that many things can be simplified into functions, I would like to point out functional languages don't necessary means you are going to have cleaner code automatically.

I write Haskell for a company that it seems people here like to experiment with extensible-effects-interpreters-whatever pattern, that we end up with some 81 "effects-model-repository-handlers-command-query" packages in a project, the FactoryCommandQueryEffectContextGeneratorRepositoryHandlers type is not limited to Java.

The main reason behind this kind of code seems to be "what if you need <insert a property here>?". As other comments pointed out, using a class over function can help caching the result, save / restore the state, share the state and so on, but do we need these properties? The author is talking about a homework, it's not a piece of code that you run on some very important production servers, there's no need to cache / save / restore or whatever. What if you need it in the future? Update the code.

I somehow start to think maybe this kind of mindset is inevitable in a project's lifetime, until people are confident enough to say "If we need this need code to be cachable / restorable / whatever, give me time, and I can update the code."


Is there any real harm though? Free functions pollute the global namespace, which is something I tend to avoid (at least in Ruby where everything shares the same namespace).


Just put the function in a namespace?


In terms of ruby, we may be splitting hairs when it comes to namespaces vs classes. Modules, which are typically used for namespacing, can have state and extend themselves, making them effectively singleton classes.


> Modules, which are typically used for namespacing, can have state

But there's nothing about state that makes it specific to classes. Namespaces in C++ can hold (static) state, for example. So it's actually the other way around: singleton classes can effectively be namespaces/modules.


Object without state but with functions is just a namespace. But that depends on how the language implements it.


In Ruby that looks a lot like what the article says is an anti-pattern.


In Ruby, that basically comes down to creating a static class.


What's the difference between polluting the global namespace with a class name, or with a function name?

I get it, I also experience some reticience when adding free/bare functions in Swift. But maybe it's because of 15 years of OOP programming.


Maybe the problem here is the global namespace. I really like modern javascript where you have to explicitly import everything.


Maybe not so much of a problem in C++ but look at JavaScript. With the millions of dependencies / libraries you import, if they all used free standing functions you would inevitably end up with collisions.


In c++ functions can be marked ‘static’ to make them translation-unit local, or you can put them in a library namespace, if they’re public. In a particular js bundler/runtime usually a module system is used (or just iife/umd wrap) which prevents function names from populating globals.


The problem is unqualified imports though, not free functions.


The standard "right tool for the right job" adage comes to mind. If I write Java or JavaScript, then using objects is fairly important (in fact, I have a hard time writing good JavaScript, because it is really an "object-based," as opposed to "object-oriented" language that rewards runtime modification, and my mind doesn't really work that way).

As was mentioned earlier in the comments, if we are designing code for reuse, then using a reusable design pattern is important. It doesn't have to be a class (I program in Swift, which uses structs and enums more often than classes), but it should be in a form that can easily be extended or derived from.

I will also do stuff like refactor a bunch of code out in a project that I'm developing, and create an entirely new project, based on that, so it can be reused. In that case, I may take a simple, focused tool, and make it a bit more generic and/or complex, widening its utility (of course, that also means that I add a bunch of testing that would not have happened, otherwise).

I sometimes think that we get caught up in the tools or dogma; letting them define us. I say this, having been through exactly that.


“the count() member function sounds like it should be non-modifying, but in fact it needs to update member data and thus cannot be const”

That isn’t correct. Just declare count as mutable. See https://en.cppreference.com/w/cpp/language/cv. Memoization is a valid use case for that feature.


Often it's the motivating example when the feature gets explained.


I would argue that mutexs or other concurrency primitives are the quintessential example, but caching is the other big one.


Add to that, that sometimes you want to pass procedures or functions to others (I know, crazy, right?). With this "everything must be a class" approach, it becomes needlessly convoluted to do that. If you don't have static methods, then you need to create an object first, for the sake of giving that object's methods to another procedure or function. It makes using the functionality more cumbersome.

For usage I'd say:

Use functions, whenever you can get away with only using functions, as long as their arguments stay reasonably few. Only use a class or struct or other "putting-together/wrapping concept", if you have to. For example, if your functions or procedures would have 10 configuration arguments, it is probably a good idea to wrap those in a struct and give them as 1 argument only.

Where I see OOP mostly is with GUI frameworks. I know functional GUI frameworks do exist, for example https://docs.racket-lang.org/gui/index.html (well functionally using objects at least), but in many cases the framework will be written in an OOP style and one needs to adapt to that, to have a good time.

Where I don't see a reason for OOP is for pure calculation things. For example recently I wrote a simulator for calculating probabilities in the board game risk. No need to do any OOP there at all. It's all functions or procedures, except for 1 struct, which wraps the rules of the game and is passed mostly to all calculation procedures, so that they can take from that struct whatever they need for calculation.

Many things inside a project will be pure calculation things, where this kind of approach is applicable.


> pass procedures or functions to others

"Object interfaces are essentially higher-order types, in the same sense that passing functions as values is higher-order. Any time an object is passed as a value, or returned as a value, the object-oriented program is passing functions as values and returning functions as values. The fact that the functions are collected into records and called methods is irrelevant. As a result, the typical object-oriented program makes far more use of higher-order values than many func- tional programs."

William Cook, On Understanding Data Abstraction, Revisited

https://www.cs.utexas.edu/~wcook/Drafts/2009/essay.pdf


Only that you need to artificially create all those things, even if they would not be necessary, while, when you work with simple functions, you just do them.

Here is what I mean:

For example you would need to create an interface for some classes (or interface for other concept your language offers), which tells you in another place in the code, that an object implementing the interface will definitely have that one method you need. You need to create a class or whatever your programming language offers, to implement the interface and give that as argument.

There is a lot of boilerplate in this. It also is a question of explicit vs implicit at times. I usually like having things explicit instead of hidden or implicit and encoding knowledge in types is often great. However, if I am forced to create an interface, to be able to create a class implementing the interface, to be able to create an instance of that class, to be able to give that as an argument ... I prefer just being able to pass a procedure or function instead. At some point enough is enough and it does not make understanding the code easier, when I have to check in 3 places.


Alan Perlis programming epigram #11: If you have a procedure with 10 parameters, you probably missed some.


general rule of thumb here is: to use classes if there are some invariants that can be checked/maintained. or, in other words, invariants exist to justify the existence of classes. ofcourse, the constructor establishes the invariants, and everyone else maintains it.

if you have to do things like 'get_name(...)' and 'set_name(...)' then it is kind of silly (imho). just stick to more canonical means.


Practically speaking - when I started using OOP in a large structured-code environment, I was sold on it pretty quickly because it became much easier to find the functions I was looking for and to understand their contexts. Things have changed - younger programmers who spend their days writing small functions that are called by what is effectively a black-box system may not understand that advantage.


Maybe it's not that relevant here, but in most ruby on rails projects nowadays there're a lot of "service objects" which are intended to be instantiated with all the needed inputs, and to have just one public method "call" without arguments, meant to be called just once.

So, from all the perspectives, they are just functions. But they're not defined as such usually, only because it'd be slightly awkward in ruby for those functions(methods, actually) to have private functions inside. Because there's no import mechanism in ruby, only mixins, which "imports" all the private methods in the module as well, polluting namespace of your class.

As a side measure, those service classes usually have a class method "call", which just passes all the args to the new instance of the class and calls the "call" on it, so you can later just do "ServiceObjectClass.call(args)" or even weirdly looking "ServiceObjectClass.(args)"

It looks very awkward IMHO, but I haven't seen better alternatives yet.


Although he's correct at a high level, I'd say this should be handled more like the advice given to programmers on optimization:

Optimization for beginners: Don't optimize

Optimization for experts: Don't optimize - yet.

What I've observed, over and over (for the past 20 years of Java development) is that beginning programmers don't really see much point in object-oriented design, and default to static functions (Java's equivalent of "free functions"), but they also end up needing some shared state... so they make the shared state static, too (Java's equivalent of a global variable). Every "enterprise" Java project I've worked on since about 2002 has been "designed" this way: almost all the data is public static (e.g. the "singleton" abomination) so you can't run any of it without running all of it.

On the other hand, if developers just defaulted to designing objects even if they don't really get why, they'll end up with a composable, testable system by accident.


Composable maybe, but testable, not necessarily.

If the function doesn't read or modify a global state, it can be much easier to test in isolation than a class, which might e.g. throw an exception in the constructor causing your test to fail before you even get to run your method.

If it does touch a global state, it's probably instead better as part of a class.


I had to look up what memoization is- guess I’m getting rusty? But it turns out it’s just a new word for an old concept (caching).

I’ve always done it this way:

double calculation(double arg) {

  thread_local bool prev_arg_valid = false;
  thread_local double prev_arg;
  thread_local double prev_result;

  double result;
  if(prev_arg_valid && prev_arg == arg)
    result = prev_result;
  else {
    //do the calculation 
    result = ...;
    prev_arg = arg;
    prev_result = result;
    prev_arg_valid = true;
  }
  return result;
}

Doing it this way is something you would only ever do if you knew beforehand the user would be asking for the same value over and over, but it’s clean and invisible to the calling code and doesn’t break your assumptions about what the function is going to return.

The memoization pattern is valid too of course, I just find it interesting I’ve always done the same thing when advantageous but in a different way.


> I had to look up what memoization is- guess I’m getting rusty? But it turns out it’s just a new word for an old concept (caching).

The term memoization was coined in 1968 and quite possibly predates the term cache (with respect to computing).


Interesting!

I still maintain though that memoization is a special case of caching with n=1. :)


I try to avoid classes as much as possible these days. I do mac/iOS app development, so I can't avoid it since I need them for defining UI components. However, for non-UI code, I tend to use classes to compose separate pieces of state at very high level. The class acts as the "state machine" that updates these separate pieces of data that can only transform in a few explicit ways. Functions define transformations of these pieces state, so they're easy to comprehend and test. I didn't come up with it[1] and it's worked out really well for me.

[1] https://www.destroyallsoftware.com/screencasts/catalog/funct...


Interesting. I usually default to writing functions over classes for the simple reason that it makes testing much simpler as you just need to test inputs versus outputs to a function.

With classes, you sometimes have to manage and test the state of it. For a database example of something I had to design a while back, it required me to manage 4 different state transitions each one depending on the state of the previous one. Thus, I ended up with this long test case running through each transition which was not ideal. Granted, I could have split the state transitions into multiple tests by setting up the internal state manually in for each test/transition, but I prefer not to fiddle with internal state in tests.


I have to admit i do write far far fewer classes these days - it seems to be my last go to approach usually once I have understood the domain and needs quite well (i tend to do a lot of exploratory work)


Over time in a project, "flexibility" often starts to become synonymous with bug-prone, and poor organization of classes/functions/data is another big killer. Three functions in different places doing almost the same thing but with unique quirks, and you being fairly new to the codebase don't know that any of them exist, so you write a fourth.

Neither OO nor functional approaches solve this inherently, but the tools of OO provide some nice options. Just think about why you'd be using them for each case.


Depending on the language it could be tricky. For example in Ruby everything literally is an object. There are no standalone functions. Not having a class simply makes it hard to use. I can't autoload methods. I could group them in a module but that's not the point. So while coding in Ruby I'm planning to stay with small classes. I treat class as a smallest testable entity in Ruby.

On the contrary, in Python, a function is a first class citizen which can be selectively imported and easily tested.


> For example in Ruby everything literally is an object. There are no standalone functions. Not having a class simply makes it hard to use

Nothing prevents you from writing one function in a file, requiring that file and calling that function. Not sure what's so hard about it.


If you write Ruby from scratch, sure. If you work in Rails you have to work around everything being autoloaded on boot.

Best bet is to just build modules to namespace your functions.


To me the original sin of the example is not to perform the calculation in the constructor. The third version, the one with the "int count() const" method and without the h and w members, is perfectly valid and (correct me if I'm wrong) thread-safe. It is a bit longer than the 'functional' implementation but does handle memoization. This is basically the C++ way to make a 'closure'.

The point is certainly valid, but the example could have been more carefully chosen.


So one of the problems with that function is that it's global. The advantage of having a single class that either has a hidden constructor and only a static function or needs to be initialized but only offers the stateless function is that you know what concept the function is related to.

Sometimes I have an entirely stateless class that has a cluster of 10 functions, both private and public just to have that particular functionality grouped somewhere.

So class-as-a-namespace if you wish.


If you use c++ (as the example in the article) you can just use explicit namespaces at least.


True, I'm working with Swift which doesn't have that concept. But you can't have collisions between similarly named functions that are in the main or a different namespace? For example when somebody defines it's own version of 'print'?


You can have name collisions of functions with the same name, same namespace and same signature. But you can have nested namespaces, so namespaces are still the answer in c++.

In languages where those are not available or where you are forced to use classes anyways (e.g., java) I see no problem with classes-as-a-namespace, but especially in java there is no point in discussing "classes that should be free functions", too, as those don't exist.


C++ has namespaces. Python has modules.


Whether or not a class can be reduced to an int is something to consider, but should not automatically proscribe the use of a class. What is way more important is to carefully consider the API that you provide to calling code. Once other code starts depending on your decision, it becomes harder (or even impossible) to change. So you might decide to use a class for this reason, even if it can be reduced to an int.


As a Python programmer, I've found that since I started using `mypy` I've written a lot more classes - I generally like using multiple small functions, but classes have two big benefits for me:

1. I don't have to repeat / import type signatures as much

2. I can collapse a bunch of related functions more easily in my editor.

So functional programming (in python at least!) has a few things to solve regarding ergonomics for me to return to it.


While not bad advice, I feel that this post does a very poor job of explaining when and why to apply it. The original class, while certainly inefficient, exposed fields and memoizes results, the final function does not. He’s combining two pieces of advice - don’t over complicate your design, and don’t create classes for a single use function - but neither one is demonstrated all that poignantly here.


Here's my view: the real world doesn't have any free functions. Say a plate breaks in your kitchen. What happened? Obviously some kind of an object derived from a circle and and and some sort of factory? And, uh. Josh you mentioned broken plates. So, as far as a broken plate, I think we can address this by adding some methods for how many pieces the plate is currently in and which broken piece we're talking about then we can specify each broken piece as a simple polygon from the origin of the virtual object, that being the theoretical still-intact plate as manufactured. We can also specify each plate in terms of the deviations from the platonic ideal plate as specified in the CAD designs. So let's change all the signatures to refer to EITHER 1 whole piece, being the whole plate, or the first broken piece, second broken piece and so on, which can all end up in different places in the world. Ultimately this will cover just about everything except if someone makes an artisanal plate by gluing together previously broken plates, where they're not from the same plate. That will make garbage collection difficult as the artisanal factory might leak memory if it doesn't use every piece. The solution is to simply do manual garbage collection where we replace each derived plate that is made up of broken plates, with a new plate that is pre-broken, and we just have to set what pieces it is made of, and then the original pieces can be freed. So actually the base case does need to know whether it is considered "whole" despite being made up of broken pieces, and then it can have virtual pieces that comprise it but know that they have to move and be together as one. This basically captures the plate abstraction pretty well, as long as you remember that we decided that for the dinner function we decided to set a plate, rather than set a table, so you just do the glass.set() the fork.set() and plate.set() so on, and then you can check that it's set, whereas the table they're set on might have unrelated items such as a candle, that isn't really considered set or not.

Javascript: how about we all just use paper plates. do whatever you want with them.

Programmer: also works! Obviously we'll automatically update the DOM tree whenever the server thinks anything's changed, but that's just common sense.

Javascript: ...also feel free to pour soda in them, paper plates are cups too.

Typescript: hold it! Not so fast!


What is it like to auto complete in a functional language?

In OO if I have an instance of a file

    f = new File(...)
Then in my editor I type `f.` and I get Close/Read/Write as possible completions.

In some functional language I have a `f` an instance of a File, what do I press to get all the list of common things I can do with with a file?

Is that better or worse or what?


That kind of intelligent auto-complete does not require OO. It requires static types.


you didn't answer my question. I already said I had a type, the type is `File`. Instance of that is `f`. What do I type to see ways to use `f`?


¯\_(ツ)_/¯ depends on your code editor and your tooling.

In strongly typed functional languages (Haskell, MLs), you don’t even use OO syntax. Instead you pass your objects into functions. E.g. in Haskell:

    writeToFile file data
So the topic of the question does not really apply to functional languages. Personally I gave up on auto-complete with Haskell a long time ago, and it hasn’t been a difficult adjustment. Writing Haskell is still way more pleasant than C++.


In other words, don't use instance variables for argument passing: http://www.nuke24.net/docs/2012/InstanceVariableMisuse.html


We recently ran into a library for motor control that was an absolutely impenetrable set of classes. After navigating though the code we distilled the entire library to one import (the same one they were importing) and a single line of code. Yup.

Not sure why people do this.



In typescript I will for example make a class called 'Tools' with static 'free form' functions and no constructor. I do it just because it's easy to organize, import and call the functions. Is this bad?


Not saying this is "bad", but why not use a module for that?


Not sure. Maybe time to convert some classes to modules.



Why make it a class even, it doesn't hold any state. Also I'd do it like:

DominoTilingCounter tc; std::cout << tc.count(4, 7) << '\n';


Then, count could be a static method, and you wouldn’t need to construct the DominoTilingCounter, yielding

  std::cout << DominoTilingCounter.count(4, 7) << '\n';
That’s close to a free function, using the class only for grouping (say of private helper functions, or for memoization data structures)


In this case you can use a namespace too:

    std::cout << tc::count(4, 7) << '\n';


His cppcon talks are very insightful and interesting.


Well, there is one subtle, possible advantage and that is literally just the logical grouping of code, not really encapsulation in the technical sense.

Second, if you have a handful of functions like this, even remotely related ... they make a nice 'Library' and frankly there is nothing wrong with that.

As I see it, I don't think it's an anti-pattern depending on how it's used.

In some other instances, first-class functions or lambdas may be just as appropriate.


-er classes! (shakes fists at the sky)


Put a FooHelper on it!


<deleted>


There is, it's called static. Just hide it in a .cpp file and expose the real function in a header file.


For anyone who wants to post their opinions on whether OOP is good or bad, may I suggest briefly explaining what you consider to be "object-oriented programming"?

I've seen in a lot of threads like these, people often end up talking past each other, because one person's idea of what an "object" turned out to be different from someone else's.


I would really love to hear it too. I made some OOP on various programming languages (mainly PHP/Ruby) and where sometimes this is obvious that using OOP is the solution (interfaces, abstract classes etc), sometimes I feel it is completely overkill and I don't even know why I'm using it.

For example recently, as an exercise, I made a little scraper in Python to extract movies data from a website [1]. This could have been done in procedural programming directly (actually the first iteration was like that), but I rewrote the whole thing as a class and I still don't know why. I think it's completely overkill.

If I was doing a scraper for multiple websites related to movies, that would make sense to use classes as they share the same goal and I could make good use of heritage/interfaces/abstract classes. But here, it's not. I read again and again about when to use OOP or not but I still struggle to find most of the times the good decision. It's like I doing it just because it looks "better".

[1]: https://github.com/kinoute/scraper-allocine


Traditionally, I don't really like classes, as I started with a mostly functional language (R).

However, when writing Python (for a poker simulator), I came to the conclusion that objects can actually be really useful for the following reasons: 1) managing state: this is the big one, if you need data with your functions, a class is an obvious way of doing it.

2) documentation: a class with methods is a higher-level structure than a bunch of functions and you are more likely not to forget the existence of a method on a class relative to a function somewhere in your code base (this second idea shamelessly stolen from Martin Fowler).


For PHP, one reason why I decide to use a class because there is no autoloading support for functions.

This allows me to logically group functions _and_ throw in autoloading support.


The title is clickbait, the article isn't about whether OOP is bad


Funny you should mention that. For my (disclaimer:unfinished) PhD I did a literature survey including 17.000 publications from 1960 to 2018 in order to find a definition of object-oriented programming that would allow us to improve on our fMRI research designs.

It turns out: some 95% of all official ACM/IEEE publications mentioning OOP, never explain what they mean. Some even have oop in their title but then just explain some crude java library, never explaining why oop is a good fit for their problem to begin with.

Finding a good definition is REALLY hard, but there appear to be two departments, outlined perfectly in the book "Object thinking" by David West:

1. Formalist (G. Booch et al.): Object-Oriented Programming is Programming with Objects, Classes and Inheritance

2. Hermeneutic (A. Kay): Objects should be inclusive, are a recursive projection of a cell-like organism metaphor.

Many try to mathematize the idea, use it for analysis and to validate programs, derive some sort of object calculus and care about the mechanisms. Others (the hermeneutic camp) where more about how objects do or do not support your conception of reality so that you can model and simulate it using rocks that we tricked into exibiting calculative thinking.

I found that one ubiquitous definition (Kay's infamous "messaging, local retention and protection and hiding of state-process") isn't helping at all, but people have been struggling to find a clear and consicse definition, which is why Booch and Kay are always put forward.

This is the only thing I ever got out of it:

http://pi.informatik.uni-siegen.de/gi/stt/38_2/01_Fachgruppe...


I think the object-oriented paradigm, is the most hated aspect:

- "Object-Oriented design" ( class diagrams, use case diagrams, Abbott Textual Analysis... and all the bike bikeshedding fun of UML, Rational Unified process..) generally pushed by the likes of IBM, Oracle, and heavily taught in SE courses.

- a watered-down version of the above, where formalism is discarded, but the first problem-solving step is to decompose the system into classes. an infamous example is the chess-board interview. I think the author is criticizing this part.

I think the majority of people don't hate things like vector<int> or set<int>, despite them being classes.


Patterns and antipatterns are important, but naming is even more important. Calling "using a class when a function would suffice" "the OO antipattern" isn't going to help anybody.

I wish we wouldn't let today's tendency towards clickbait titles extend into engineering terminology.


I agree - it's baity and misleading, and so should be changed (https://news.ycombinator.com/newsguidelines.html). I've replaced it with the article's phrase for what it's really about.


In this case I don't find it terribly misleading.

One thing that occurs to me regularly now is that I click on a submission that's new to me – only to find that I've already seen it under a different title.

I agree with the policy of renaming submissions, but I wish you'd adjust your trigger level slightly towards less changes.


tl;dr Use the right tools appropriately.


This is why I like languages where you just can't have a free function!

I'm arguing the opposite, free functions are an annoying and confusing anti-pattern when dealing with OO languages that allow them.

C++ is often just a maze of mostly write only code.


You aren't arguing anything, you are only stating a very shaky premise.


Not any less meaningful then your comment. You aren't arguing anything, you are only stating a very shaky premise.


What I said wasn't a premise, it was pointing out that you didn't back up what you were saying with any substance, which can be checked by reading your comment.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: