6 ms·
I just can't get behind all of this. Sure, its great to be able to reason clearly because you can see your whole state, but its exhausting always carrying your
by astazangasta 11y ago
I just can't get behind all of this. Sure, its great to be able to reason clearly because you can see your whole state, but its exhausting always carrying your context around like a hobo with a huge backpack full of junk. Am I mistaken?
- saryant 11y agoExhausting is being in this situation: object Foo { var x: Int = 0 def bar(y: Int) = x + y } And trying to figure out what bar will return at any given point.
- pekk 11y agoAre you somehow not in control of how x is being changed? You seem to have said that literally any statefulness is "exhausting".
- gnuvince 11y agoAnyone, anywhere could modify x, which is what makes reasoning about this code difficult. When your entire program is a collection of mutable state that can be mutated by any piece of code, understanding what's going on becomes really difficult if not impossible. One thing that people are adopting from FP is to limit what is mutable, and when possible make state explicit.
- pekk 11y agoThat isn't true. I don't understand why you can't use a language that offers encapsulation.
- S4M 11y agoWhat convinced me that avoiding mutations as much as possible was having to deal with code like that (in java): public class MyBigClass() { private int a; private int b; //hundreds of lines of code private int calculateSomething() { //do something a = a+1; return b*b; } //hundreds lines of code private int calculateSomethingElse() { int c = calculateSomething(); return a+c; } //hundreds of lines of code public returnSomething() { return calculateSomethingElse(); } } In this example. It's hard because you have to remember constantly which functions are changing your classes before you use them, and if you haven't written the functions, it's very easy to introduce a bug by involuntarily modify the states using the function calculateSomething, just wanting its output and not the modification of a.
- pekk 11y agoThat demonstrates some design flaws of Java, yeah, because your "private" variables are effectively just globals anyway. The guy who is not using encapsulation at all this year will be making similar mistakes (massively long bodies, proliferation of unnecessary entities, etc.) if he switches to functional programming next year. It's not a panacea. I'm afraid this doesn't demonstrate that any tiny bit of statefulness is "exhausting."
- eli_gottlieb 11y agoThe monad type-class operations carry the context around for you. You can just write in imperative-seeming do-notation. The difficult part is trying to combine monads.
- sago 11y agoHere's what I don't get with Monads, why I've not felt the need to create a Monad-like system in my (non Haskell) code. The 'world' is carried on the back of one parameter, right? So if I want to run a function taking two parameters in a particular 'world', which parameter is the Monad? If both of them are, what if they disagree which world is used? Perhaps I haven't reached the fabled epiphany of monads, but they seem to be just implicit state attached to a single value, and only a single value. I'm not sure what this implicit stuff is better than a system where you are explicitly passing the 'world' that each function needs. Why is roll(dieSides: Random<int>) -> int Clearer than roll(random: Random, int dieSides) -> int I get the passing of a world out of a function with a single value getNumber() -> IO<int> or using the extra implicit state to represent other possible values, like Maybe<int> but, using it to carry around state? That seems to me to be unnecessarily implicit. What am I missing?
- prodigal_erik 11y agoIn a language with immutable values and pure functions, roll always returns the same number from the same generator. If you want to call roll twice and get different numbers, the first call needs to return a new generator (with an updated seed) to pass to the second call. Rather than having to write something like do (x, g2) <- roll g1 (y, g3) <- roll g2 (z, g4) <- roll g3 it's convenient to have plumbing that hides all those single-use g values.
- sago 11y agoThanks. So the issue is that Monads save us having multiple returns? That makes sense.
- 11y ago
- 5outh 11y agoWell, sort of. One of the big focuses of functional programming is _minimizing_ state; in other words, only what must be passed around should be. Sure, you'll end up having a large context that the entire program operates in, but having to pass around a context incentivizes passing around the minimal amount of context to sub-parts (functions) in the program. You don't need the whole global state? Great; only pass around a small part of it to that part of the program. You don't need the whole context all the time so you don't pass the whole thing around all the time.
- peterfirefly 11y agoKinda. There are tricks to make it less burdensome. One of the tricks is the use of monads in Haskell. Monads are really just a small set of functions that operate in a certain way (that "satisfies the monadic laws") on a type. What that gives you is (among many other things) a way to carry an implicit "world" around with you without needing to refer to it all the time. One use for such a "world" would be a seed for a random number generator so different parts of your program can use their own, isolated streams of random numbers (nice for determinism, testing, debugging, etc), even if they execute asynchronously because Haskell is a "lazy" language where code doesn't necessarily get evaluated unless it really needs to. Another use is for the "world" to actually represent the real world -- all of it -- so the I/O interactions happen in the right order. That's very important in a language where the evaluation order is so "loose" as it is in Haskell. There are other tricks as well. One of them is to have lots of small transformation functions that don't need to understand all of the world, just their little bit of it. You create the big world transformations by composing the smaller transformations. The data structures you use are slightly different: you don't want to modify your data structures, you want to create new ones all the time, i.e. inserting an object into a tree shouldn't modify the tree but return a completely new tree with the new object in it WHILE KEEPING THE OLD TREE AROUND UNCHANGED. This doesn't have to be as expensive as it sounds, in fact, it can be quite cheap. Data structures where you can do this cheaply are called persistent data structures and they work really well with a functional programming style. (They don't have to implemented purely functionally internally as long as they present a pure interface.) https://en.wikipedia.org/wiki/Persistent_data_structure https://en.wikipedia.org/wiki/Persistent_data_structure Persistent data structures can also be confluent, which means that they use sharing internally if two versions of a data structure end up being logically the same. Tarjan and Sleator have some really nice papers on that -- well worth reading, even if you don't care one bit about functional programming.
- jcurbo 11y agoThis isn't a strict definition, but I just think of it as turning the usual model inside out. You always have a state that you're modifying, if you're doing functional or imperative programming (or a mix). Usually you're "inside" the state, changing it via pokes and prods from within. (Think global/shared state typical of C/C++ programs) In FP you pass around a big ball of state and no modification happens outside of it (there is no global or shared state, or at least access to it is tightly controlled if it's not a pure language). The functions live 'outside' the state, so to speak. Programming this way was, to me, one of the major revelations of learning the functional style.