Live data from Hacker News

The use of `class` for things that should be simple free functions

quuxplusone.github.io

141–150 of 406 posts

Re: The use of `class` for things that should be simple free functions

#141
post #96

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. Howev…

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.

Re: The use of `class` for things that should be simple free functions

#142
post #49

Earlier quoted context omitted.

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…

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.

Re: The use of `class` for things that should be simple free functions

#143
post #115
post #95

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 remo…

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.

Re: The use of `class` for things that should be simple free functions

#144

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 tha…

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.

Re: The use of `class` for things that should be simple free functions

#145

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 struct MyAlgorithm { F1 f1; F2 f2; void operator()(...) { ... f1(whatever); ...…

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

Why?

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

Re: The use of `class` for things that should be simple free functions

#146
post #138
post #132

Earlier quoted context omitted.

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.

Re: The use of `class` for things that should be simple free functions

#148

Earlier quoted context omitted.

> 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…

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.

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

Re: The use of `class` for things that should be simple free functions

#149

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 wit…

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.

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

Re: The use of `class` for things that should be simple free functions

#150

Earlier quoted context omitted.

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 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.

The problem is unqualified imports though, not free functions.
Post reply on HN