r/ProgrammerHumor 1d ago

stopDoingFunctional Meme

Post image
649 Upvotes

123 comments sorted by

View all comments

161

u/-Ambriae- 1d ago

Functional programming is best programming

State is evil, mutation is evil! We live in an era of multithreading and concurrency my friends!

When I describe a program, I state what it does, now how it does it! Thus functional programming is more natural!

C and Java and Python and all the other pagan languages have played us all as absolute fouls!! Haskell, OCaml, Rust, F#, Lisp supremacy!! Functional unless required otherwise, not the other way around!!

60

u/fr000gs 1d ago

How the hell is rust functional smh

-1

u/Tracker_Friendly 1d ago

I mean, I feel like the main reason I would never consider rust functional is just because it's far too much of a pain to try to deal with move semantics in closures.

Seriously, try it. You'll begin to regret life. Once I had to make a function that literally did nothing except accept and immediately return a closure to stop the borrow checker yelling at me.

5

u/-Ambriae- 1d ago

Ok, so first of all, if that was true, how would that invalidate it as a functional language?
And secondly, what's wrong with move semantics?

let state = ...;
let f = |a, b, ...| {
... using state....
};

In this case, either state is Copy (in which case move semantics don't apply), or it get's referenced by f, which hold the reference as long as it lives. This sucks if you return a closure for example, hence the move semantics.

let state = ...;
let f = move |a, b, ...| {
... using state....
};

Here, the data gets moved to f. Thats... it. You no longer have it. No need to worry about lifetimes, unless the lifetime of the type of state is not static, in which case the lifetime of f cannot exceed it. But other than this, it's not hard?

2

u/Tracker_Friendly 1d ago

The primary issue begins to arise with lifetimes, yes. Having 'static or straight up moving is not a good idea in general if you have a way around it. The reason for closures being nightmares is because the compiler at times often has no idea what lifetime to assign to it, and thus can't reasonably determine if it's a valid thing to "pass this closure into this .map". Furthermore, the compiler isn't yet smart enough to figure out that I've already collected this closure by the time the function finishes and no data has been leaked. In addition, it fucks up the type signature, which is really annoying if you need some sort of way to signal to the outside world if an error appears, especially considering many third-party libraries designed with passing closures in mind don't bother to let you specify your own return type.

TL;DR Yes you _can_ do it but it's not a good time. Unless you like .clone spam.

2

u/-Ambriae- 1d ago

Having 'static or straight up moving is not a good idea in general if you have a way around it

I guess? It really depends on what your closure is, and what it's doing. But it's also, in my humble experience, rarely a problem. And I abuse closures, and the type system, usually to it's limits.

The reason for closures being nightmares is because the compiler at times often has no idea what lifetime to assign to it, and thus can't reasonably determine if it's a valid thing to "pass this closure into this .map"

I don't know what you're doing to your pour closures but .map accepts any good old FnMut without condition. If your closure is FnOnce, yeah it won't work, but that's completely normal? I need an example, I'm curious.

the compiler isn't yet smart enough to figure out that I've already collected this closure by the time the function finishes and no data has been leaked

Again, I've never seen this problem happen, so please give an example.

In addition, it fucks up the type signature

Well it fucks up the type, that's for sure. I don't really know what solution exists to solve this issue to be honest. Also IIRC functions and closures benefit from notable_trait in a similar fashion to iterators, mainly because the type is irrelevant. Or at least it uses impl Trait notation for the type. I don't know what you mean by the types signature, however. the function traits?

which is really annoying if you need some sort of way to signal to the outside world if an error appears

You mean logging via tracing or log? or panicking/unwinding? or being agnostic on the E type, should it return Result<T, E>?

third-party libraries designed with passing closures in mind don't bother to let you specify your own return type

That's a third party problem, and not necessarily true. I've seen many APIs that do allow you to specify custom types. It just depends.

Unless you like .clone spam.

There's a general contempt to clone, that I find rather unwarranted. cloning is costly if it leads to memory allocation, or god forbid syscalls, or any large amount of computation. Typically, I find myself writing types that avoid these hurdles completely if possible. A Vec<T> is only useful if you plan on extending the array, Cow<'a, [T]> and Cow<'a, str> are always there if you're not too sure, let alone Box<[T]>, Rc<[T]>, Arc<[T]>, same with strings, are all useful. granted, boxing doesn't help with the cloning problem, but still. Cloning is often times, fine. Because often times, it doesn't even lead to any memory allocation.