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

I don't know any purely functional languages[], so my viewpoint is skewed here. Whenever I have seen a python developer drink the functional kool-aid they end up either storing state in global variables, environment variables or in a dictionary they end up passing to every function. I then have to explain to them that passing a dictionary around and expecting variables to be in it is just half-assed OOP.

My rule of thumb is anything that needs state should be in a class, and anything that can be run without state or side effects should be a function. It is even good to have functions that use your classes, as long as there is a way to write them with a reasonable set of arguments. This can let you use those functions to do specific tasks in a functional way, while still internally organizing things reasonably for the problem at hand.

The minute people start trying to force things to be all functional or all OOP, then you know they've lost the plot.

[] I have been wanting to learn lisp for over a decade, I just never get around to it.



I'm mathematically trained so in the beginning I really like writing python program in pure functional paradigm. Until I was facing this issue.

But, adopting OOP doesn't mean one have to give up on pure functional paradigm, there's a book which basically is talking about how you can incorporate more functional paradigm into OOP.

One way to look at it is (perhaps trivially) that methods are just functions, and class is just something with properties. So in a sense class defines a type, where the methods/functions expects this type.

In python I find properties (and the related cached properties), dataclass etc can really make the above more apparent in construction. To just give one example, having a property that returns a pure function would acts practically the same as methods (but not in docs unfortunately, and related auto completion kind of stuffs.)

Yet another way of looking at this is to treat Python class as singular dispatch in first argument only.

I find thinking this way enables me to reap the benefits of both OOP and pure functional paradigm.


I strongly agree. Rule one: avoid and simplify state (e.g. recalculate data if it's cheap instead of updating it when any of its inputs change). Rule two: if you need state, keep it coherent by tightly managing it (go through functions that maintain the invariants) and exposing as little as possible. That is where classes come in.


Pure functional programming is orthogonal to that. In other words: you can have your class that contains a maximum simplified state, no problem. Extending this to be pure functional means that in addition to everything else that you said, the calls to the class that manages the state are now considered as needing "special treatment" in the sense that you can't merely call them, you have to also explain what should happen if there are multiple calls. I.e. the order of execution is not depending on the order of lines of code anymore, but it is defined through the means/syntax that the programming language gives you.


Could you ELI5 or perhaps give an example? I’m not sure I understand.


It's not easy but I'll try:

    counter = new Counter

    currentValue = counter.value
    newValue = currentValue + 5
    counter.set(newValue)
In most programming languages, each of those lines is executed sequentially. Therefore we are used to it.

If this is just a script then it's simple and pure functional programming (PFP) as no benefits here. The reason is that the order of calls is always the same (it's the same as the order of lines)

Things change when the order of calls is not static anymore but becomes dynamic. Think about a webserver. Or a any system that receives calls from the outside - or has something "running" like a cron job.

In that case, you can't just look at the lines of code to understand how the program operates. You know have to simulate not only the state, but also the access/change to the state (including external state).

Here PFP comes in, making those things explicit and therefore decoupling it from the order of lines of code.

In the example of a simple script, this is just annoying because we now have to be explicit even though we now everything should be ordered as the lines of code:

    counter = new Counter

    currentValue = counter.value
    newValue = currentValue + 5 // does not compile, because currentValue now is an "effect"
    counter.set(newValue)
currentValue is now an action/effect that might be run at some point or maybe not. Therefore we have to rewrite it:

    counter = new Counter

    currentValue = counter.value
    newValue = currentValue.onceItHappenedModify(value -> value + 5)
    // newValue is now also an effect
    updateCounter = newValue.onceItHappenedExecuteOneMore(value -> counter.set(value))
    updateCounter.execute()
   
In the end we have to execute the "updateCounter" effect because until this point it is just a datastructure. A blueprint for an execution if you want so. However, in PFP we don't actually execute it - that's the whole clue! We just pass the blueprint around and it gets bigger and bigger. Until the point where we return it as datastructure to the main method. And then, the programming languages runtime executes it!

If you find that complicated, you are right. That's why PFP only works in language that support this concept and make it ergonomic. I often use languages that don't (e.g. typescript) and in there, I don't use this technique because it has more drawbacks than benefits.

Anyways, it becomes more interesting once things happen in parallel/concurrently and from different points in the application. The reason is that when you work with those blueprints, you are forced to explicitly combine/merge effects.

You can, for instance, do this:

    fireRockets = fireRocketsEffect()
    activateLasers = activateLasersEffect()
Nothing has happened so far. We only created two blueprints. In other languages, things would be running already, but not here. We now explicitly have to decide how to run those:

    fireRockets.onceItHappenedExecuteOneMore(activateLasers)
or

    activateLasers.onceItHappenedExecuteOneMore(fireRockets)
or

    activateLasers.executeAtTheSameTimeAs(fireRockets)

And so on. As you can imagine, you quickly end up with combinators for e.g. running a list of effects either in parallel or sequential or in parallel but max X at the same time and so on.

I hope that explanation makes sense. I found it hard to grasp without actually building something myself.




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

Search: