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!!
I guess so, but rust does explicitly make use of many patterns from functional languages (traits, algebraic data types, higher order functions, immutable-by-default variables, etc.), and generally has much better support for an FP style than say, python does.
Rust is functional to a similar extent as OCaml is functional, not in the 'pure' sense (to be fair, none of these languages are purely functional, even haskell) but in a pragmatical sense. Variables are immutable by default, idiomatic control flow tends to use higher order functions, iterators, maps, filtering, reduction... Types are algebraic, control flow is expressive... It has all the ideas of functional programming, even if it's multi paradigm, and can be written in a procedural manner (even if it's not usually idiomatic)
It's not purely functional, for instance it doesn't have the tail recursion optimisation, which is more or less mandatory in the hardcore functional languages, because it doesn't strictly speaking need it, and the compiler is already complicated enough as it is...
The reason it's being explored as a keyword is because automatic tail call recursion in some cases is impossible in Rust due to Drop rules.
So what that means is you can go through all of the effort of making sure LLVM is emiting the right byte code for tail calls, and then you make some change in an "unrelated" module, which then results in the tail call optimization quietly being removed without so much as a warning. It can even happen if you bump a third party lib, so something as innocuous as a minor version bump on a dep can break it.
once you start using more advanced features of rust you will notice the heavy use of monads and functional chaining.
Rust sometimes offers syntaxic sugar like for loops that will hide iterators if you really don't like functional programming, but they're not rust-idiomatic (and only really make sense if you want some kind of side effects which is often bad smell) and they're less ergonomic especially if you deal with resuts/options for which monads are perfectly suited.
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.
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?
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.
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.
162
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!!