20 ms·
I’m a functional fan and I don’t disagree with your points. Like anything, real life functional programming is not a panacea and most languages that append fun
by steego 4y ago
I’m a functional fan and I don’t disagree with your points.
Like anything, real life functional programming is not a panacea and most languages that append functional constructs as an afterthought often come with a price (debugging, performance) and just as you pointed out, mixing functional code with imperative code indiscriminately can yield wonderfully nuanced bugs.
With that, I find languages like OCaml and F# provide a great ergonomic experience for writing both functional and imperative style code. (There’s nothing wrong with imperative code confined to the scope of a function or a well defined object)
Even with great tools, I still think there’s quite a bit that can be done to improve the hidden costs incurred from a functional-first approach.
- galaxyLogic 4y ago> There’s nothing wrong with imperative code confined to the scope of a function Thhat is a fine point which I rarely see discussed in connection with FP. (Pure) FP is commonly understood (?) to mean that there can be no mutable data. You can "bind" a value to a variable but only once. Right? But within a function you can have local variables. I don't see why it would be bad to assign multiple different values to the same local variable, which only exists during the execution of the function. The variable only exists in the "stack" not in the "heap" and is gone as soon as the function returns. So if I run a while-loop and repeatedly assign a new value to the local variable "i" for instance, would that be unacceptable according to "FP"? Why? I can't see any ill effects from modifying a variable whose value can not "linger" past the execution of the function. What am I missing? Why is it (supposed to be) bad to rebind any number of different values to a local variable?
- grumpyprole 4y agoPure functional programming is really about tracking and controlling effects by making them first-class, not avoiding them altogether. This has numerous benefits for reasoning, correctness and optimisation. Haskell allows mutable local variables, 'ST' references which can be used to build functions that are observably pure on the outside. The Haskell type checker will guarantee that no mutable state escapes. This is true "state encapsulation". It is completely acceptable for pure FP programming.
- galaxyLogic 4y agoSo mutable state is not bad, as long as it is "encapsulated"? I thought (from what I've read so far) that "no mutable state" is the definition of "pure FP". So should the definition of pure FP be "No un-encapsulated mutable state"? Instead state must always be "encapsulated". Where can I read more on this? Thanks
- jeffreygoesto 4y agoSomewhere down towards the transistors, the change has to happen. The registers of the machine are explicitly designed to be mutable. I don't know how functional languages are compiled and maybe somebody who does can chime in, but I would not be surprised if the IR already was imperative. So it may be easy to give some access to that in a language. I'd say "pure" is if the calculation can be done with a function without side effects and it is a deliberate choice to restrict a language to (almost) only that. No side effects at all would mean no interaction with "the outside world" and no access to input data though.
- zelphirkalt 4y agoWe can see FP as a tool to for us to express a functional thought so that the computer can understand it and will translate it into something the computer itself can run.
- kaba0 4y agoHaskell compiles down to C- first, which is an even less expressive version of C basically, so you are right there. But if you go low enough FP concepts pop up again, e.g. out-of-order execution is pretty functional, but to a degree so are vector instructions. And this is no surprise, Turing machines and lambda calculus (and recursive functions and anything Turing-complete) are equivalent. If you think about it, a Turing machine is just as side-effect free in itself, side effects are special to computers.
- jeffreygoesto 4y ago
- yodsanklai 4y agoThis is why it's common for languages to be multi-paradigm. For instance, in OCaml, it's perfectly fine to use while loop and mutable variable, and more generally mutable data-structures (typically records with mutable fields, or arrays). It's mostly a matter of preference. I think experienced programmers will favour the pure approach unless there's a good reason to do otherwise, and know well how to solve common problems without relying on mutable structures (typically, it's very rare that you need a while loop in OCaml). But sometimes, it makes more sense and is less verbose to use mutable DS.
- galaxyLogic 4y ago> This is why it's common for languages to be multi-paradigm I can agree with that. Yet I somehow perceive an opinion that it is not FP unless it is Pure FP (?). Like even a small amount of impurity can poison the whole well.
- fredrikholm 4y agoThe problem is that few functional languages can reach that level of purity (and for good reason IMO). It would necessitate that every side effect, every boundary and every interface to be pure, on top of internals also being pure. Haskell comes close, but we're skirting the real heavy hitters like Idris/Agda here. Erlang hits the sweet spot for me, where processes (async, stateful modules) communicate by message, whose internal functions are 'pure' .
- kaba0 4y ago> Erlang […] whose internal functions are 'pure' . Why is that a good thing? You can easily reason with mutable code on a small, local scope, yet the real hard part will be the interaction of all those messages. Sure, erlang has plenty of safeguards for those as well, but I don’t see what would be lost if local mutability would be allowed.
- fredrikholm 4y ago
- sakjur 4y agoI don’t know about good or bad, but I have a practical example of where rebinding variables necessitates rethinking the language a little. Erlang uses pattern matching for all variable assignments, where unbound variables are assigned and already assigned variables are used for pattern matching, returning an error if the pattern matching fails. In that particular context, allowing rebinding a variable change the language. Elixir behaves like Erlang but supports rebinding, so variables you wish to keep around need to be annotated with a “pin” character. I prefer the Erlang way, I find it to fairly nicely solve a problem with the only downside being that I can’t rebind variables, which encourages me to write smaller functions. And the language use fewer tokens, which is an nice if you ever want to parse it for some reason. https://www.erlang.org/doc/reference_manual/patterns.html https://www.erlang.org/doc/reference_manual/patterns.html https://elixir-lang.org/getting-started/pattern-matching.html https://elixir-lang.org/getting-started/pattern-matching.htm...
- amval 4y ago> Thhat is a fine point which I rarely see discussed in connection with FP. (Pure) FP is commonly understood (?) to mean that there can be no mutable data. Martin Odersky was discussing this recently. How they found examples of overly-complicated "functional programming" in their codebase (erroneous, and hard to understand) when an imperative function would be way easier, performant and correct. The takeout? The imperative version was still pure. Mutability was limited to the scope of the function.
- grumpyprole 4y agoIt might be "overly complicated" in Scala, but it works very well in Haskell and allows effects to be controlled, checked and reasoned about. In fact, monads were originally proposed to give formal semantics to imperative programming. Scala is really an object-oriented language, first and foremost, so please do not judge functional programming based on how it looks in Scala.
- amval 4y agoThe point of my comment was not to diss FP, but to explain that imperative code with some local mutability is not necessarily in direct opposition to pure FP. I have never tried Haskell, so I cannot really hold any truly fair opinion there, but I find Odersky's "values" or approach to programming language design much more in line with my own. Scala 3 seems like a very well designed language (pragmatic, expressive, conceptually solid).
- consilient 4y ago> imperative code with some local mutability Haskell allows this, you just have to actually demonstrate that the mutation is local: statefulSum :: [Int] -> Int statefulSum xs = runST $ do n <- newSTRef 0 traverse (\x -> modifySTRef n (\y -> y + x)) xs readSTRef n
- sideeffffect 4y agoAnd you can do it with a state monad in Scala too. But Odersky's point is that (at least in Scala) it is even simpler (and thus should be preferred) to do it with raw (but still local!) mutation. https://www.youtube.com/watch?v=QRcD9Zc7eq4&t=943s https://www.youtube.com/watch?v=QRcD9Zc7eq4&t=943s
- bjourne 4y agoThe problem is that by rebinding the same variable you introduce temporal dependencies. The statement "i = i + 1" writes to be variable i and therefore must be executed before any subsequent reads of the variable i. If the statement is in a loop nest, the subsequent reads might even precede the assignment statement in the program order. This makes it harder for the compiler to rearrange expressions and in general runs counter to fp's principles of being as "loose" as possible. And not requiring a program order is more "loose" than requiring one. Sophisticated optimizing compilers often can figure out and remove redundant/accidental temporal dependencies but not always.
- galaxyLogic 4y agoIs that really much different from one function calling another, the called function must be executed before the caller? There would seem to be a "temporal dependency" then as well and the compiler can not re-arrange the execution order of the said functions.
- bjourne 4y agoYes, it is. What is the value of str(5/i)? If i can't be redefined you can always replace i with its definition. If i can be redefined what string str(5/i) will evaluate to may be impossible to deduce a priori. Both the programmer and the compiler can replace i with its definition when it sees the expression "str(5/i)". That freedom is lost when the language allows rebinding.
- zelphirkalt 4y ago> (There’s nothing wrong with imperative code confined to the scope of a function or a well defined object) This is often the case with hash tables. Sometimes one simply wants to store an info and the fact, that this info has been discovered or processed or occurred. If one makes the hash table inside the function where it is used, then the important aspects of no side effects and no mutation of arguments of the function are still preserved. However, sometimes a problem can arise later, when wanting to parallelize something inside that function involving the hash table. So one needs to be a bit careful here as well.