12 ms·
How side effects work in FP
- gegtik 5y agodweb link: > ipfs resolve -r /ipns/nauseam.eth/coding/random/how-side-effects-work-in-fp/: no link named "coding" under QmdzzonFE9eX6FGs8UbCyoC2XS5NQjaG6gaqhgeUyTHnag
- anchpop 5y agoThanks! I was accidentally pushing garbage to my IPNS name. It should be resolved now.
- Jensson 5y agoHaskell is a framework that takes a monad interface implementation and performs IO based on it. The monad in question is used like a self flattening iterator / generator, and together with lazy evaluation it can function like a regular program even though the monad itself is implemented using pure functions.
- crdrost 5y ago> I don't know why people try to explain monads without explaining this first I mean, I get where you're coming from, but the thing that you've described is that a monad and there's not an easy way to get it there. But yes, I am with you, you want to explain to someone that Haskell's model of side effects is a sort of metaprogramming, much simpler than macros, we just give you a data type for “a program which does some stuff and eventually can produce a ______” and ask you to define a value called `main` which is a program which produces nothing special. And it's the compiler's job to take that program and give it to you as a binary executable that you can run whenever you like. You also want to give people a number of other examples of things that are monads. “a nullable ____”, “a list of ____s,” “an int (summable) with a ____,” and maybe an example that is not like “a function from ____s to ints,” or “a set of ___s.” The key to telling someone what a monad is, involves trying to explain to them that in some sense “a program which produces a program which produces an int” is not terribly more descriptive than just “a program which produces an int.” If you can combine this with the adjective being outputtish and universal you have a monad.
- ashton314 5y ago> Equational Reasoning Probably the best reason to use FP imo. It just cuts out so much overhead when trying to read other people’s code.
- beaconstudios 5y agoIt actually has a name and theory surrounding it too - it's called referential transparency, and is one of the key attributes of a pure function.
- substation13 5y agoA related advantage is expression-orientated code. In ML languages, code forms a beautiful tree structure where you can see the binding definition of each binding by looking down and right. let x = something really big and complicated whereas in a language built around statements it will be scattered all over: let x = null; x = something(); x = somethingElse(x); x = anotherThing(x); In the first case the definition of x is in one indented block; you can read it and move on.
- tarsinge 5y agoI just did some PHP the other day and I stumbled on a structure I was happy to have forgotten: something(x, result); somethingElse(result, result2); anotherThing(result2, result3); with the occasional surprise mutation when you just have to do something(x) and x contains the result. I also got caught recently with MomentJS with that one when doing mydate.add and discovering it also mutated the original instead of just returning the result. I'm a very average programmer and far from a FP purist (I mostly use JS and Rails) and I'm surprised how much I now use some FP principles and how it feels very natural to me.
- pintxo 5y agoYou are free to structure it differently: let x = anotherThing( somethingElse( something() ) );
- 5y ago
- arciini 5y agoAs someone who has never really understood monads (or tried that hard to) but who has done a good amount of Javascript programming, I really like the description of a monad as a series of nested data structures describing the next steps of a project. Ultimately, that's very similar to what old-school ways of declaring Promises do in Javascript. You're creating a data structure that you then attach a new function to execute with the results.
- compressedgas 5y agoNot just similar. Promises are a monad. Though they don't strictly follow the monad laws as they are collapsing in JavaScript due to how Promise.resolve never allows a promise to resolve to a promise but only to the value of a promise.
- kqr 5y agoI mean, as long as you have a flatMap/concatMap function JavaScript arrays are technically a monad too. But that is a practically meaningless thing to say. What we talk about when we have about monads is the highly generic interface coupled with the highly generic combinators. This is not something shared by arrays and promises. In other words, none of them are monads in any practically meaningful sense.
- chriswarbo 5y ago> I mean, as long as you have a flatMap/concatMap function JavaScript arrays are technically a monad too. But that is a practically meaningless thing to say. It's not "practically meaningless"; it's depth-first-search logic programming (as per How to Replace Failure by a List of Successes https://rkrishnan.org/files/wadler-1985.pdf https://rkrishnan.org/files/wadler-1985.pdf )
- kqr 5y agoI'm not saying the operations themselves are meaningless. I'm saying that "being a monad" only means something beyond "supporting flatMap" when there's a library of generic monad combinators involved.
- marcan_42 5y agoWell, that's the first time side effects in Haskell made sense to me. Well done. Not that I've ever seriously tried to learn Haskell, but in the past every time I've lazily come across an article about it it's always seemed like a bizarre confusing world, even though I know how functional programming (in the sense of purity) works. Now there's just one thing missing. We all know what this style of programming is. It's asynchronous programming with callbacks. Seriously, Haskell folks, if you started with "all side effecting functions are kind of like async operations with a completion callback (the stuff people do in JavaScript all day), and then we have some syntactic sugar to make it suck less" you'd have a much easier time getting people to wrap their head around all this. (Yes, I know the details aren't exactly the same, but drawing parallels to stuff people already know matters) Seriously, this idea of there being a central effect dispatcher (the bit that runs `main` behind the scenes) is so eerily like the coroutine scheduler in async coroutine paradigms that I can't believe more people haven't drawn parallels between these programming styles.
- kqr 5y agoI think the problem is that people have learned the monad concept and then go overboard with it. Only monads are like async code with callbacks. If people stuck to just explaining the IO monad specifically, which is a lot like async code with callbacks, then things would be better. I wrote about this a few years ago: https://two-wrongs.com/the-what-are-monads-fallacy https://two-wrongs.com/the-what-are-monads-fallacy
- frogulis 5y agoI like the analogy with musical instruments. Very practical. I think you're right in saying that async is not representative of all monads, but it does help with questions that many beginners have, like "How do I get the value out of the maybe/IO/your-monad-here?"
- kqr 5y agoI don't think it generalises that well, unfortunately. How to get the value out of Maybe is "just pattern match it". There's nothing process-like about it at all -- it's just a plain container/wrapper. The async analogy works for some monads like ST and IO and whathaveyou but it's not generally useful, due to the wide variety of types that are monads.
- rebeccaskinner 5y agoThis is a great way to help people get an intuition for monadic IO.
- Ruthe 5y ago[dead]
- dmitriid 5y ago"How to do side effects in s/FP/Haskell" Erlang is a functional programming language and doesn't need to jump through hoops to do side effects. So is OCaml. So is...
- pjmlp 5y agoSomehow there is this school of thought that FP === Mirada/Haskell, go figure.
- chii 5y agoI guess they should describe FP as 'referentially transparent' or not. Haskell is referentially transparent, but you can't say the same for many other FP languages like erlang, or ocaml.
- adrian_b 5y agoAll the FP languages include a subset that is referentially transparent. I do not consider the attempts of making the entire language referentially transparent and the exclusive use of lazy evaluation, like in Haskell, as being useful. Obviously there are people who like these features and who use Haskell, but in any case whenever FP languages are mentioned they should not be reduced to those that have made the controversial Haskell choices.
- chii 5y agoI do believe that monads are what made it possible to be referentially transparent not for just a subset, but the entire language. It's a good choice - it enforces equational reasoning, even for things like IO and "side effects". It is harder to write code like this, if you're not used to it, but i imagine that if you can, the code would be of higher quality.
- edgyquant 5y agoAs someone who is just getting into FP I’d say Haskell was the most famous language I knew of before a month ago. Lisp might count but it isn’t widely known to be a functional language most people just think it’s a weird language.
- olliej 5y agoSomething that I always found funny is that main clearly has to take something as input (otherwise it would not be able to produce different output between runs). So in GHC the main method (main :: IO()) internally takes a value of type RealWorld (theRealWorld) and returns the new RealWorld produced by running the program. I imagine by the time it's lowered to actual assembly they've dropped that, but I've never dealt with ghc's machine codegen
- shirogane86x 5y agoI am not at all an expert on GHC's codegen, but having glanced at that code before (mostly to get an intuition of what IO is under all that sugar), RealWorld doesn't even... exist. Realworld and its close friend, State# are tokens of sort. they're zero-sized and deeply magical (the GHC.Prim is quite enlightening to read). All the primops are defined as infinite loops... it's quite a sight to behold. As something totally unrelated to the above, a lot of the more internal GHC stuff (so the GHC.* modules, the stdlib/base, etc) are in my opinion readable and most of all, well commented, so it's quite fun to take a look sometimes.
- olliej 5y agoOh of course - I only ever worked with the GHC Core language which is completely and explicitly typed so it was especially blatant :D I do understand that in reality it's simply a tag value to ensure that the compiler correctly orders and threads operations correctly, but I still think it's semantically cute :D
- FeepingCreature 5y agoSo this is basically Eternalism[1]? Instead of writing a program that reads inputs, experiences a sequence of state mutations and takes actions, you generate the graph of every possible state and path between states annotated with inputs on the edges and outputs on the vertices, and let the runtime pick the actual path through it. And this works because you're generating it lazily, but that's just an implementation detail. And the program in itself isn't ever in any particular state, because the program is the description of every possible state. [1] https://en.wikipedia.org/wiki/Eternalism_(philosophy_of_time) https://en.wikipedia.org/wiki/Eternalism_(philosophy_of_time...
- epolanski 5y agoI like to explain functional programs like this. Functional programs don't _do_ anything, they are a list of commands some external interpreter (what you call the runtime, but doesn't have to be a runtime) will execute or compile. Functional programs only contain descriptions of the various commands, but it is an external interpreter executing the commands. Functional programs return "recipes". "recipes" are picked up by some executor that will turn them in dishes (the side effect). If all of this seems generic allow me some typescript. ``` // we can declare a a function that takes no argument and return a value IO. type IO<A> = () => A declare const program: () => void; // thus can be rewritten as declare const program: IO<void> // this is the PURE function I can reason about declare const execute: IO<void> => void // this is the interpreter that will execute the commands and have side effects ``` Now, let's declare a side effectful functions: ``` const log: (s: string) => IO<void> // desugarized: (s: string) => () => void `` ` Notice how the signature is similar to the standard console.log: (s: string) => void except that it's lazy. Laziness is one of the most convenient ways to express "commands" rather than side effects in a language like JavaScript, but there are also alternatives. Now we can have a pure program that logs to the console some string. `log("foo")` does not "execute" any console.log, it still needs to be executed: ``` const program: IO<void> = log("foo"); // program() will actually print "foo" in stdout ``` Note: we could've encoded IO in different ways, e.g. with a struct/interface rather than a function and had the interpreter actually reason about the side effect on its own. Now, the only missing part is: how do I compose such IO functions together? Well, there are various ways but the most common ones are applicative functors and monads. I am not going to delve deeper in this comment on those topics because it would take long but I hope I have transmitted my point: in functional programs you return programs (I like to think about them as recipes, recipes don't DO anything), those programs may compose effectful commands, the actual execution of the commands is shoved inside an external interpreter. This is quite obvious in some languages like Haskell, it's less obvious in others.
- Tainnor 5y agoOne benefit of describing state change as a pure data structure, instead of directly executing it, is that you may now write functions that run these state changes in different ways. E.g. you may have a dry-run function that doesn't actually change the state but only describes what it would do. Or you could have a function that generates a verbose log of all steps executed. Etc. That doesn't necessarily work for IO, as that gets special treatment by the runtime, but you can do it with your own types.
- anchpop 5y agoYeah! That's why I'm excited for the effect system to land in OCaml. In general I think effect systems are more user friendly than monads and it makes the choice of how to handle the effects more explicit.
- delegate 5y agoI don't speak Rust well, but I can share my perspective from Clojure, where I tend to think in data. A program is a sequence of immutable functions taking input from the world and reducing that to instructions to mutate the world (aka side effects). The world here can also be some place that stores state. Ideally mutation is the last step of the pipeline, which takes the instructions and produces the side effects. Or simply input -> fn1 -> fn2 -> fnn -> mutate! Where 'fn' are all immutable.
- Someone 5y agoThat model fits functional reasoning. It doesn’t fit all programs, though, as it doesn’t allow the input to depend on the _execution_ of the mutation instructions. Example: input “I step forward”, output “monster appears”, next input “I try to shoot it”. That second input wouldn’t be there if the output weren’t executed.
- marginalia_nu 5y agoOutputting and manipulating side-effects can be useful even in imperative code. I had real performance problems a while back attempting to build a very large file, my code could only produce the write instructions out of order and the file was too big to hold in memory, so what I ended up doing was writing writing instructions in radix-grouped batches to a bunch of temporary files, and then reading and evaluating them to build the large file. This seems counter-intuitive, as it more than doubles both the amount of data written to disk as well as adds a reading-step, but doing it this way means the data is written in a way the hardware can deal with a lot more efficiently. Sequential access to and from the instruction files (off a mechanical drive), and densely clustered writes to the big output file. (on an SSD, strictly sequential writes matters less than being in the same block) This reduced the runtime from several hours to like 5 minutes.
- themulticaster 5y agoI think it's always fascinating to find situations where counterintuitively it is faster to do more work. For example, it took me a while to realize that most of the it's actually faster to read/write compressed data overall - you'd think that reading from a disk and decompressing the data would be slower than just reading uncompressed data from a disk directly, but due to the vast difference in disk IO performance and CPU decompression performance it's almost always faster to perform disk IO compressed. I'm writing almost always since I'm not sure how the tradeoff looks for current high performance PCIe SSDs (or other storage devices with very fast IO).
- ReleaseCandidat 5y agoOh, haven't seen a monad article in quite some time. > Monads really are just a convenient way to build up action values Except when they aren't, because lists/arrays (with map) and Maybe (with map) and .. are something totally different. And that's the problem with _all_ these monad tutorials/how-tos/...: the problem is, that monads (and functors) are not burritos or elephants but _both_. So they are hard to understand looking only at a single instance. Btw. monads are not the only way FP deals with effects, see algebraic effects. https://github.com/yallop/effects-bibliography https://github.com/yallop/effects-bibliography https://www.youtube.com/watch?v=DNp3ifNpgPM https://www.youtube.com/watch?v=DNp3ifNpgPM
- vmchale 5y agoA bunch of those mentioned don't address laziness and how the IO monad deals with it: http://blog.vmchale.com/article/effects http://blog.vmchale.com/article/effects
- ReleaseCandidat 5y agoActually the most important part of monads in Haskell is the do-notation. Without that, monadic code would look like JS' callback hell (although composition of monads using monad transformers/monad stacks isn't much better). Haskell without laziness is Purescript, btw.
- nerdponx 5y agoA lot of people I've talked to seem to think Purescript is a better Haskell.
- shirogane86x 5y agoDisclaimer: personal opinion Having used both extensively over the years... I'll have to disagree. Don't get me wrong, purescript is awesome in its own way, but it falls short of haskell in a few major ways. - Lacks ergonomics (for example it has better extensible records, but they are clunky to manipulate due to the lack of features at the type level) - as of now it's tied mostly to the JS ecosystem and runtime(s): that means no TCO, which _really_ hurts when using monads that require on a lot of recursion. And in fact purescript sometimes has to take a performance penalty and use specific trampolined stack-safe monads to avoid stack overflows - Haskell layout rules may be complex, but purescript's break in weird and unintuitive ways (by layout rules I mean the indentation-sensitive syntax) - Haskell is catching up on _a lot_ of the feature that made/make purescript great (mostly talking about syntactic things right now, like RecordDotSyntax, QualifiedDo, BlockArguments...) - as it turns out, when programming "Haskell-style", laziness is really damn effective. So many times I've had to forcefully (and painfully) introduce laziness in purescript to make things behave well... As much as I like purescript, I think if you want that kind of strict programming, maybe OCaML or a derivative thereof (or maybe even ReasonML/Rescript?) would work better? I feel like whenever I use purescript it looks so similar to Haskell that I try doing things the haskell way and it just doesn't work, and the "purescript" way will often look weird to a trained haskeller.
- vmchale 5y agoThe author doesn't mention laziness, which is pretty huge. Laziness is half of immutable data structures! I have a tidbit in the first paragraphs: http://blog.vmchale.com/article/effects http://blog.vmchale.com/article/effects
- shwestrick 5y agoI think laziness would be a distraction here. The idea of encoding effects as "action values" is relevant and interesting, even under eager evaluation. Certainly laziness has influenced a lot of things in the world of FP. But it's really helpful to distinguish laziness from FP, especially considering the problems that laziness introduces.
- treis 5y agoFP explanations like this annoy me: That is, this code: x = foo() y = bar(x) z = bar(x) Should mean the same thing as: y = bar(foo()) z = bar(foo()) Because it's obvious to anyone that has written any sort of complex program that those are not the same thing. foo() can be an expensive operation like an HTTP call. Or it might depend on a database which can change state underneath it. I assume FP has answers for these things but the tutorials never cover them. They all imagine a world without state or expensive operations to show how wonderful it is. And that's an easy world to program in.
- draw_down 5y ago
- jbjohns 5y agoActually in some FP languages like Haskell those two might be literally the same thing. When the compiler sees the second, it does the first (obviously over simplifying all over the place here, but that's the idea I think is being expressed).
- yakubin 5y ago> Because it's obvious to anyone that has written any sort of complex program that those are not the same thing. They are the same thing in Haskell (except for when forcing the thunks into eager values happens, due to the weirdness of laziness, but that has nothing to do with purity). > I assume FP has answers for these things but the tutorials never cover them. Except this one does: > The <- works like an =, except it signals that equational reasoning doesn't apply to this value. You can't replace what_the_user_typed with getline - your program won't compile.
- treis 5y ago>They are the same thing in Haskell That just raises a different problem: y = bar(generateUUID()) z = bar(generateUUID()) >Except this one does: It doesn't explain it in the context of real world programming.
- Slackwise 5y ago> You're viewing my site on the centralized web. Check me out on the dweb ! (Warning: it's slow.) It's really hard to take someone serious when they make big, untrue, statements saying the real web is "centralized" right on every page of their site, to peddle crypto scams. https://chadnauseam.com/reasoning-quiz https://chadnauseam.com/reasoning-quiz > Neo-Nazis are holding a demonstration in a small town, waving swastikas around and shouting about Hitler. They seem to be pretty peaceful so far, so the First Amendment says you probably can’t get rid of them. However, their demonstration is near a main street and it could be a minor inconvenience to the traffic trying to go through. > > [ ] Allow the neo-Nazis to demonstrate. > > [ ] Break up the demonstration on the grounds of ‘blocking traffic’. I am at a loss for words. Nazi sympathy under the guise of tolerance.
- anchpop 5y ago> big, untrue, statements saying the real web is "centralized" Well, it's not completely centralized, but it's more centralized than using IPFS for the backend and ENS for the namespace. I think that's hard to debate right? If I take down my server right now chadnauseam.com will go down for everyone. But if anyone has my IPFS page pinned, it will stay up for everyone no matter what I do (barring exploits in IPFS I don't know about). So in that sense it really is more decentralized. > Nazi sympathy under the guise of tolerance. I don't understand why you see a quiz that gives you the option to pick either way as promoting one option over the other
- presentation 5y agoThe ligature makes the Haskell look even more magical, can't tell what I'd type to make that >>=== character.
- quchen 5y agoIt’s >>= but with a font very poorly chosen for a tutorial :-| FWIW C and C++ also have this operator, it does bitwise right shift assignment there.