The use of `class` for things that should be simple free functions
quuxplusone.github.io
quuxplusone.github.io
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 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.
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?
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.
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.
But it is - the programming language is now repeatedly quizzing you on something you've already told it.
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.
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 :)
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.
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:
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.
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.
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.
https://news.ycombinator.com/item?id=23338700
You will see that it is far more concise and intuitive to do it with a function.
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.
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.
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.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).
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?
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.
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.
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.
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.
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.
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.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();
};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.
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...
Generally using classes when modelling persistent state is not an anti-pattern, because that's what classes are for.
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.
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.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.
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?
That's exactly what a 'class' is.
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.
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).
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.
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.(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.
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).
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!
class BFS(object):
def __init__(self, origin): self.origin = origin; ...
def next(self): ... 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.)
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.
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.
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.
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.
- 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.”
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)
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
(From https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/...)
EDIT: typo, changed link
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.
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
> 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.
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.
But the parent comment applies
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. 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 laterThis 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?
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".
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, 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.
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.
>>> 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. >>> 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 assignmentLeading single underscore is private by convention. Still should generally be avoided, but it’s the proper use.
def x():
expensive precompute
def y():
cheap stuff
return y
It's a matter of taste I guess. 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.# pylint: disable=too-few-public-methods
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.
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.
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.
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.
(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.
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).
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?
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 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.
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.
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 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...
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.
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).
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.
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.
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.
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.
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.
`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".
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.
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.
Why?
Edit: I found it - it's a 'template deduction guide' https://stackoverflow.com/questions/40951697/what-are-templa...
In C++20 this specific deduction guide is no longer required as it is implicitly generated for template aggregates, so even less boilerplate.
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.
This is a particularly bad name for your situation. An algorithm is not an object. 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).
None of your examples make any sense to me. Why not name it simply "game"?
1. Naming things, and .. I forget what the other one is.
> // 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.
> "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.
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.)
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.
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.
that's only true for standard library objects, although it is an useful guideline for all code.
Unfortunately, this makes it very easy to set up strawmen, then add a click-baity title for the perfect gift for "your side".
myfunc(arg1, arg2)
arg1.mymethod(arg2)
myclosure(arg1)(arg2)Other counter examples might be Template Method pattern etc.
> foo.do(bla)
is syntactic sugar for
> do(foo, bla)
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.
After that experience I found returning not even just to functions but also e.g. Pascal procedures was pretty refreshing.
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.
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.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.
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
>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...
My prediction is that in 20-30 years the same will be said about today's mainstream OOP.
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)
Now I have I have a class with some static functions in it.
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.
Given the current development of C# I expect we see stand-alone functions in the future.
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...
It's not a surprise the designers of java, c#, ... went that route.
In honesty, statics accomplish the same thing.
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.pyHowever, 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.
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.
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.
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."
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.
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.
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.
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.
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.
"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
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.
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.
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.
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.
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’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.
The term memoization was coined in 1968 and quite possibly predates the term cache (with respect to computing).
I still maintain though that memoization is a special case of caching with n=1. :)
[1] https://www.destroyallsoftware.com/screencasts/catalog/funct...
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.
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.
On the contrary, in Python, a function is a first class citizen which can be selectively imported and easily tested.
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.
Best bet is to just build modules to namespace your functions.
The point is certainly valid, but the example could have been more carefully chosen.
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.
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.
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.
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!
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?
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++.Not sure why people do this.
DominoTilingCounter tc; std::cout << tc.count(4, 7) << '\n';
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) std::cout << tc::count(4, 7) << '\n';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.
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.
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".
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).
This allows me to logically group functions _and_ throw in autoloading support.
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...
- "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.
I wish we wouldn't let today's tendency towards clickbait titles extend into engineering terminology.
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.
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.