7 ms·
> 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. (Pu
by 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 agoDeep down it must be, as the data is conveyored past fixed compute engines (which can be seen as pure functions), right? FPGA programming is pretty much functional as well. RAM is capturing the state there.
- efnx 4y agoIt’s not that it’s good or bad - mutation can only happen in a monad (IO or ST or similar). The monad is the encapsulation and that encapsulation is enforced by the type checker. It’s actually quite liberating not having to worry about what’s “right or wrong” and instead just do whatever you like that compiles. I suggest getting started with Haskell through http://www.learnyouahaskell.com/ http://www.learnyouahaskell.com/ - it was my favorite.
- galaxyLogic 4y agoI'm looking at it not from the perspective of "Which language is the best?" but from the point of view of I'm working in a specific language now, what are the principles of "Pure FP" that I could benefit from when using that language. How should I "encapsulate state" in JavaScript for instance? If the principles of FP are great surely they must be applicable in any Turing-complete programming language. The (Haskell etc.) compiler is no doubt a great tool, great at type-checking. But I see using any specific compiler or type-checker as a tooling issue, not a foundational insight about FP.
- efnx 4y agoAh yes, indeed there are benefits that come from programming in Haskell that can be applied to JavaScript, but I think that without the compiler it’s very hard to guarantee success.
- amelius 4y agoYes, but the point is that you can fully isolate any part of the program from having side effects. I.e., the caller is responsible for executing its callee's side effects.
- galaxyLogic 4y agoRight therefore it would seem that it is ok to mutate local variables inside a function anyway you want, because that can not have side-effects outside of the function. Right?
- grumpyprole 4y ago> I thought (from what I've read so far) that "no mutable state" is the definition of "pure FP". It's much more nuanced than that, certainly no arbitrary and unchecked mutable state is. There are ways though of achieving mutable state whilst maintaining referential transparency.
- throwaway858 4y agoThat is not the definition of FP. There is no definition of what FP is that is agreed upon by everyone, but the key is the word "functional" in FP. This comes from a mathematical function, which is a mapping of input values to output values. You could write a definition of a function f(x) to be such that the value of f(x) is the result of the execution of some imperative-looking code (including loops that mutate variables). Mathematicians would be satisfied with such a definition and would confirm that f(x) is a function just as valid as any other. And so functional programmers should also accept such a function. In fact, Haskell has specific syntactical support ("do" notation) to allow you to write functions using imperative-looking code including mutations (the ST monad, or other State monads).
- galaxyLogic 4y agoA "function" is a "function" if it always returns the same result for the same arguments, right? Now I'm trying to wrap my head around how can I call a function which takes no arguments but which returns a value the user entered on the keyboard? How can I write a function which returns the current mouse-position on the screen, while still abiding by the functional principle of same result for same arguments always? Can it be done? If not can we still call it "functional"?
- throwaway858 4y ago> A "function" is a "function" if it always returns the same result for the same arguments, right? Yes. This matches the definition of a mathematical function that I gave above. > how can I call a function which takes no arguments but which returns a value the user entered on the keyboard? You can't. If your language allows you to write such a function then I would argue you are no longer doing FP (at least in this part of the code). I believe that this is why many people like languages like Ocaml, F#, Scala. These are hybrid functional-OOP. You can use FP for the parts of the program that it's suitable for, and OOP/mutable-imperative for the other parts("the best of both worlds"). The purist FP programmers might claim that reading input from a keyboard is "uninteresting" and that programming is really about logic, algorithms, and computation, and therefore FP is enough for them (...in their ivory tower). I personally am a fan of Haskell, but I don't like calling it FP, because it does allow you to write "code" that reads input from the keyboard. They still claim to be pure FP by using a loophole and saying that "getLine" is not a "function" but rather an "IO action". But to me it doesn't matter what you call it, you are still effectively allowed to write code that does I/O and mutates external stuff, doesn't matter if you call it a "function" or something else. Of course the big advantage that Haskellers claim is that the language enforces the separation of effectful code from "pure" functions. In the other hybrid FP languages it is possible (and happens often) where you have a deep call chain of "pure" functions and then you realize you need some small side-effect somewhere, so you just add it. Haskell does not allow this at the language level, and forces you to refactor your code. In my opinion this does lead to cleaner architecture and more maintainable code in the long term. But to me Haskell is really interesting for a different reason. I don't like to call it an FP language, because like I said, a lot of the code you write isn't really "functional". A better name would be: "mathematical oriented language". And by math I mean that the code is built using logical systems that have mathematical rules that can be reasoned about. Going back the I/O (reading input from the keyboard): what the Haskell people have done is realized that you can actually model this behavior of user input as a mathematical model. This unlocks a type of thinking that allows you as the developer to go beyond the thinking of telling the computer: "do this, then do this, then do this, if this happens then do this, ...". An example: the mouse-position on the screen. There are haskell libraries called FRP that model the mouse position as a "signal function" (a well-defined term they invented) that is a 2D coordinate that changes over time. You can then combine this signal function with others to create behavior such as an image that follows the mouse. You can build complete GUI applications using this approach, and the code looks nothing like imperative programming, and more like an electric circuit of different components connected together. And that is just one example. The Haskell community has also come up with mathematical models for streams (think unix pipes) that allows you to write streaming code that is much more powerful and cleaner then what you see in other languages. So my summary: Yes, functional programming is limited, but if we go one level deeper, we could say that the essence of FP is really mathematical thinking. And if we embrace that and run with it then we can open up entirely new possibilities.
- agentultra 4y agoPurity is a poor word. It refers to referential transparency: if I give you an input to your foo function, “A”, and I get an output, “a”, then I can always substitute foo “A” for ”a” in my program and it will not change the meaning. It’s poor because it invites other meanings: some people think purity means immutability for example; but that’s only part of it.
- lgas 4y agoThere is no one definition of "pure FP". Everyone has their own.
- 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.