Live data from Hacker News

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

quuxplusone.github.io

181–190 of 406 posts

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

#181

Earlier quoted context omitted.

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

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!

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

#182

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

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

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

#183

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.

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

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

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

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

#185

Earlier quoted context omitted.

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.

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

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.

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

#186
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

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

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

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

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

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

#189

Earlier quoted context omitted.

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

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

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.

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

#190
post #159

Earlier quoted context omitted.

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

Post reply on HN