Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

As a 25+ years coder who's very accustomed to thinking in my own ways, I have a problem with functional-purism similar to the problem I had with globals and factory functions. The fact is that most programs have a lot of states and these states have to be represented somehow. Allowing the objects to contain functions that act on those states is a perfectly good analogy for most business logic or game logic you're modeling. Abstracting what you can abstract into atomic functions is great. But so many functions are more comprehensible when they take place bound to the scope of what it is they're acting upon... again, not to produce an answer, but to change/track/maintain state which is a critical part of what people experience when they interact with a program. I don't think that thinking in terms of objects or even global states should be considered a code smell when what you need to do is to get things done. If/when you need to make some functions synchronous across asynchronous systems, by atomizing their logic, then you will hit upon functional programming methods by default.


The argument isn’t that state is bad or doesn’t need to be managed. It’s more that spreading that state over a large number of areas leads to a lot of complexity and cognitive overhead in terms of expected behaviour at any given point of execution. Functional programming gives you ways to be much more explicit about the transformation being performed and the before/after states (except arguably when you start getting overly cute with zippers and lenses and such). To the extent that this approach hasn’t magically transformed programmer productivity or software quality, we know that both approaches are admissible.


Isn't encapsulating state - and functions that act on states - within the smallest-possible class ancestor a pretty reasonable way of handling that? I'm not suggesting spaghetti or`GOTO 10400` or `GlobalCatBehaviorFactory.StartMeowing(cat)` or something. I mean we have a paradigm, and it's basically OOP with a dash of functional programming at the functional level.

Maybe what I'm trying to say is that a lot of functional programming takes place within the OO model when it's efficient to do so; but functional paradigms just generally ignore state altogether, so absolute purity doesn't leave a route for daily coders writing regular programs to do their jobs.


I would say it's often the opposite, unless I'm misunderstanding you. Instead of being encapsulated "deeply" in a class ancestor, state in FP is in the "top" of the program, in the "shallow" part, as explicit as possible and as close to the entry point as possible, while the functional part is the thing that is in the middle. The term for this is "Functional core, imperative shell". In something like Haskell, side-effects are dealt with even "before" the entry point: they're handed by the runtime, which is the part that calls the entry point.

This also translates well to OOP in the form of things like hexagonal architecture (eg: business logic is pure, and state is modified by a database adapter), or to modern frontend programming (eg: the render tree is pure, visual representation state is stored/modified with by React), including flux architecture (eg: reducers are pure, state is stored/modified by Redux).

I really like this presentation about it: https://www.destroyallsoftware.com/screencasts/catalog/funct...


Um... ok. I think we're sort of both talking about keeping logic unbound from artifacts like display and from objects that contain state. Where I think OO makes sense is that once you admit that objects have properties it makes sense to take the top form of that object and put the functionality there - rather than abstracting it away into some completely other logical place.

Maybe this all comes down to what I find easier to follow in 200 code files and we're talking about roughly the same thing. If the general rule is to make logic as independent and no-need-to-retype-it as possible in the hierarchy, I'm onboard. And letting functions manage state themselves makes no sense; even in OO paradigms, functions should be pure and not rely on the state per se. (Put in plain terms, I make heavy use of getters and setters on instances; but complex functions are almost always static, and take instances of the class they're in to modify, rather than reproducing those functions and having them work on their own instance).


I think you’d love FP if you gave it a real, honest chance.


> I mean we have a paradigm, and it's basically OOP with a dash of functional programming at the functional level.

Well, but who really follows that idea? What usually happens is, that the state is stored in the object (of a class) and the result of subsequent method calls depends on previous calls and arguments, since it changed the state of the object. Maybe there are even calls to other objects in the system changing their state, and in turn changing return values of their methods.

Also: If we have actual functions (in a mathematical sense), then they depend only on their inputs / arguments. Why then have them in a class, and not in say for example, a module?


> absolute purity doesn't leave a route for daily coders writing regular programs to do their jobs

This speaks to the muscle memory of daily coders way more than to the details of FP/OO.

You could easily make the same argument about “absolute encapsulation”.


> these states have to be represented somehow

In data types!

> But so many functions are more comprehensible when they take place bound to the scope of what it is they're acting upon

To me, the most understandable function (lowest context needed, lowest cognitive overhead) is a black box that takes type x as input and produces type y as output, without side-effects

> I don't think that thinking in terms of objects or even global states should be considered a code smell

Classes can mix state and behaviour. I prefer when state is encoded in types and behaviour in functions, instead of having implicit state changes in a class, because it is more difficult to track, and constraint.

Of course, in the real world, if a library or platforms uses OOP, so be it. I am not dogmatic. But ideally, I avoid OOP because of this implicit mixing of state and behaviour.


> In data types!

Yes. With guardrails. I mean, I do a lot of writing in JS/TS but I'd never write something that tacked a dynamic property onto some class and picked it up later. That's just gauche.

> a black box that takes type x as input and produces type y as output

I agree, but I'd rather keep that function in Class X. If it's specific and X is a final/protected class, I'd say `protected doSomething()=>Y` where the argument is the implicit `this`.

I'd never allow some other thing to run this function, definitely not some other object floating out there; if it were going to be run that way it would have to be a `static DoSomething(x:X)=>Y` ...but I would still keep it in the file for X.


> To me, the most understandable function (lowest context needed, lowest cognitive overhead) is a black box that takes type x as input and produces type y as output, without side-effects

True, however we must be careful not to go completely bananas either; it's really hard for a reader to piece back together a full mental picture if the functionality is spread into dozens even hundreds of general-purpose 3-line functions.

Simple, `y = f(x)` types of functions are indeed the easiest to understand, but you might be no closer to understanding what the code is actually doing.


Agree. This is why starting with the data structures makes the code clearer.

Of course the functions can be grouped in modules. It is the same problem as having god objects, having lots of methods or many classes in OOP (it is orthogonal to OOP or functional programming).


Please read up on the definition of functional programming (or rather: pure functional programming, since you talk about "functional-purism").

Because, everything you are saying is either forbidden nor discouraged in (pure) functional programming. In fact, it is totally orthogonal.

The point of functional programming is solely to prevent the same code to behave differently just because it is used/called in different parts of the program. You can still use objects, you can still make them have methods that operate on only a limited part of the state and so on. No problem with that, I do it all them time in fully pure functional programs. In fact, I think this is best practice.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: