6 ms·
As someone who still doesn't get functional programming, I would like to see a real-world example of how to deal with side effects. Eg: write something into a f
by jafaku 13y ago
As someone who still doesn't get functional programming, I would like to see a real-world example of how to deal with side effects. Eg: write something into a file or a DB. Because of course methods/functions with no side effects are easier to deal with, it sounds good, but I don't think it would be easy nor convenient to separate every side effect in a real-world program (as opposed to academic programs, where you just write a Fibonacci or whatever). Even in good OOP, you can have lots of side effects in a method.
Halp!
- ufo 13y agoUsing side effects in Haskell is easy. The only thing is that they need to be explicit and you aren't allowed to hide them inside pure functions. For a database example, here is the chapter from Real World Haskell about databases (a bit old but still relevant): http://book.realworldhaskell.org/read/using-databases.html http://book.realworldhaskell.org/read/using-databases.html And here is some example code: do conn <- connectSqlite3 "test1.db" stmt <- prepare conn "INSERT INTO test VALUES (?, ?)" execute stmt [toSql 1, toSql "one"] execute stmt [toSql 2, toSql "two"] execute stmt [toSql 3, toSql "three"] execute stmt [toSql 4, SqlNull] commit conn disconnect conn As you can see, it looks very similar to how you would write it in a regular imperative language. The main difference is that since they database operations are IO operations then they need to be in the special do-notation block and cannot be mixed with regular code. As for the question of wanting to mix pure and impure code that much I don't think it actually comes up that much. First of all, I find that I don't often find myself with an impure function that I want to add a side effect in the middle seamlessly - firstly, side-effecting stuff tend to be side-effecting from the beggining and secondly if I do want to turn a pure function into an impure one then forcing me to change the interface helps make sure that I am not breaking any code that used to work because it assumed it was working with pure code. Additionally, Haskell is really good at abstracting code so its not that hard to take a big chunk of logic and split it into a pure and an impure part. Finally, converting code from pure to impure is not that bad - sure, you need to change the syntax a bit but the type checker helps you find all the places that need to change and also helps you check all the code that calles the method you changed so you can be sure that you aren't breaking those. side effecting operations in the IO monad so you can't run them in the middle of any function and instead must run them inside a special "do-notation block" like I did. You can have impure code call both other impure code and pure functions and all you can't do is call impure functions from inside pure functions. Sure, its a bit annoying if you ever have to convert a pure function to an impure one (you need to change some of the syntax and all the places that call tha tfunction need to start treating it as impure) but its not that bad: first of all the type checker tells you all the spots where you need to update your code and secondly, Haskell is super good at abstracting code so its not hard to take a big chunk of code with some side effects in the middle and split it into a pure and an impure part. > to academic program Its a little pet peeve of mine but it seems that nowadays "academic" doesn't have a real meaning and just tends to refer to whatever people don't like. :)
- dllthomas 13y ago"do" is just syntax. You could write the above as: connectSqlite3 "test1.db" >>= \ conn -> prepare conn "INSERT INTO test VALUES (?, ?)" >>= \ stmt -> >> execute stmt [toSql 1, toSql "one"] >> execute stmt [toSql 2, toSql "two"] >> execute stmt [toSql 3, toSql "three"] >> execute stmt [toSql 4, SqlNull] >> commit conn >> disconnect conn and it's "just" a bunch of nested lambdas with no "do" in sight. What's different from "regular" code is that those lambdas 1) return IO actions, and 2) are strung together with monadic bind (>>=) to build one big IO action. To be sure, in this case the do version is much easier to read (that's why "do notation" exists). It can be used with any monad, though - nothing ties it particularly to IO.
- ufo 13y agoMaybe I should have been clearer but the basic point is that monadic code has a different interface bar (foo x) --pure Haskell vs foo x >>= bar --monadic Haskell vs bar(foo(x)) -- in C you write both cases like this.
- dllthomas 13y agoYou're close, but it's not really so clear cut. foo x ++ bar -- "pure" or "monadic"? foo x >>= baz -- "pure" or "monadic"? What if (foo x) is a list in both cases? (>>=) is just an operator. The values that it operates on are anything with a Monad instance, just like the values that (+) operates on are anything with a Num instance. There is nothing voodoo about monads or about bind (>>=); such voodoo as there is lies solely in IO, which is interacted with the same way as other values (though certainly what you can do with (IO a) is more limited than what you can do with (Maybe a)).
- Dewie 13y agoI used to think that Monad was some alternative realm in Haskell, a realm where purity didn't exist and everything was written in an alternative, built-in language (do-notation). That's because people tend to refer to using Monads as "working in the X-Monad", as if it is some... place. But then it turned out... Oh, so it's basically just a signature/interface/type class.
- daGrevis 13y agoI suggest to dig in some web framework for functional programming. One example would be Snap for Haskell. http://snapframework.com/ http://snapframework.com/
- jafaku 13y agoDoes it make sense to have to learn Haskell and a Haskell framework just to understand Clojure? Because I'm already sold on the idea that Clojure is the best language™, being a lisp, portable, with a great community, already with a mature library for everything (because it has access to the JVM), and with some dirty shortcuts for convenience. But the frameworks are lacking. The only web framework available got dissolved, and I don't have the energy to wire everything myself in a new language and paradigm that I don't even fully understand).
- daGrevis 13y ago> Does it make sense to have to learn Haskell and a Haskell framework just to understand Clojure? I have a strong feeling that once you understand either Haskell or Clojure, it will be a breeze to learn other. My point was to get framework for functional programming that deals with a lot of I/O and side-effects and see docs for real-life examples of usage.
- hisham_hm 13y ago> I have a strong feeling that once you understand either Haskell or Clojure, it will be a breeze to learn other. Does Clojure use lazy evaluation?
- nonrecursive 13y agoOne thing I find myself doing over and over is taking as much logic as possible out of side effecting functions and placing that in a pure function, then passing the return value to the side effecting function.
- TheZenPsycho 13y agoIn the end, there is always some kind of side effect. The value that functional programming adds is you keep the part of the program that does the side effect in one spot, doing one specific well defined thoroughly debugged thing and you limit its scope so it can't screw up the rest of your program's operation no matter how bad it goes wrong. It only goes "out". it doesn't cause any side effect inside your program. One example of a way to structure a functional program is categorising functions according to whether they are a "source" a "filter" or a "sink", (terms stolen from circuit design). The source being your inputs (not pure), filters doing some transformation on the input, over time (pure) and handing off to a sink that causes some side effect (not pure). In the "real world" You've probably made a lot of simple programs that mostly follow this structure if you've used unix pipes. If you go along with this, you find that once you start making more sophisticated programs, you need some kind of "memory" so you can progress. Strictly speaking, memory, as we typically use it in imperative languages, is a kind of side effect. In the model above, though, you can make a special kind of "source" which is something like a feedback loop- a source that contains the output of the previous iteration of the program- or some sub portion of the program. Then maintaining a memory becomes a process of continually taking some input, transforming it, creating an output, and then using that output as an input again. (what you might call recursion) Why would you do this? Because each part of a program like this is self contained, and its ability to do damage to some distant part of a program is extremely limited. It becomes much easier to reason about such a program. When you find a bug (and these are still possible) you can trace the origin from output to source, or source to output. There is no possibility of some phantom distant subroutine screwing up the dataflow. And this kind of structure is very useful for games, interactive multimedia programs, and other such graphical interactive program. A commonly cited "real world" example is xmonad. What I find more insightful is this article on replicating pacman in a functional language. http://prog21.dadgum.com/23.html http://prog21.dadgum.com/23.html
- brudgers 13y agoThe idea is to generate side effects that don't effect program flow. This keyboard generates side effects as I type because it changes the state of the display. But pressing the "B" to start this sentence did not change the way the keyboard operates - each key press produced the same results as it would had I pressed "H". And had I pressed "H" and begun the previous sentence with "However," the screen layout would have been somewhat different, but not in a way I typically care about when writing (as opposed to doing typography). Now this is not to say that my keyboard is purely functional. The shift key creates state. But that state is isolated and short lived to the point where we can treat it as functional across two key combinations. It is the caps lock key which creates an unpredictable state - I do not know what output I will get without checking its status - the problem with state might be illustrated by the results of hitting capslock when I meant to hit tab while typing a program.
- jafaku 13y agoSo how should you program the capslock? You (the functional guys) keep telling me what's wrong, but you don't tell me how to do it right. Not having a capslock key is not an option, people need it.
- TheZenPsycho 13y agoActually a lot of people have decided they don't need it. They remap caps lock to something like spotlight search. The alternative to the caps lock key is the shift key. And this is a really bad metaphor for functional programming. Sorry GP. I suppose the important point here is it's not possible for something going wrong with your monitor or speakers to suddenly remap your keyboard without warning.
- brudgers 13y agoI'm not saying it's a great analogy, but among it's strengths is handiness. Among it's other strengths is that functional programs can be built bottom up from lots of little pieces and when we get unexpected results, we can track them down. If suddenly my keyboard's "b" key stops working, the problem is almost certainly with the switch beneath it and not because of something the "h" key is doing. Another strength of the analogy is that the most disorienting behaviors of a keyboard are tied to state: the worst one for me is numlock off where the behavior shifts to dependence on the state of the display.
- tel 13y ago"Side-effect free" doesn't mean there are no side-effects, it simply means that their existence is controlled, separated, and minimized. For instance, in Haskell, you have the pure function "unwords . map upcase . words" which, reading right to left as is the convention for function composition, breaks a string on whitespace, upcases each chunk, then glues them back together with spaces again. It's net signature is "String -> String". By itself, this function is useless, but "interact" is a function with signature "(String -> String) -> IO ()" which means that it turns our "unwords . map upcase . words" into an interaction with the environment. There are whole debate here as to how interact is actually still pure, but for the purposes of this argument, interact is an impure effect. The net program however is very simple: "interact (unwords . map upcase . words)". The pure part is decomposed into many other pure pieces, each with simple, small contracts and independent testability. The impure part also has a simple contract which is described in terms of pure computation. Another great example is some of the streaming libraries available in Haskell today. Pipes for instance will let you compose a pipeline of pure and impure computations and have them work together nicely. "fibsPipe >-> Pipes.take 20 >-> Pipes.print" The first two segments are pure, but have empty "placeholder" slots for impure effects (identity monadic layers). These get merged seamlessly with the "Pipes.print" segment which has impure effects. All together, you get an infinite producer "fibsPipe" producing an impure effect "Pipes.print" while having its resource utilization and termination controlled by another pure computation "Pipes.take 20" which is actually maintaining its own implicit state (the current count of numbers it's let through). But then you can decompose each piece and understand it on its own in a pure or simple impure fashion.
- ajuc 13y agoCompiler is a good example of big program that's suitable for functional programming. You have specified input and output, no need for interactivity, and you can localize all mutations of state in one place (writing generated files to disk). Other batch processing programs works as well.
- Athas 13y agoThis is true. I'm currently working on a research compiler targeting GPUs. It's 7500 lines of Haskell with all comments and blank lines removed, and the only impure part is the command line interface. Avoiding IO is not really painful for these kinds of programs.
- saosebastiao 13y agoI'm not a Haskeller, nor do I write large amounts of software, but the one thing I took away from my very short foray into Haskell is that you want all of your state management and IO in one place. Clojure doesn't enforce this on you at all, but ever since learning that concept, my Clojure code has become an order of magnitude cleaner and less buggy.
- dragonwriter 13y ago> As someone who still doesn't get functional programming, I would like to see a real-world example of how to deal with side effects. Eg: write something into a file or a DB. Plenty of that here: http://book.realworldhaskell.org/read/ http://book.realworldhaskell.org/read/
- breckinloggins 13y agoYou probably don't need Yet Another Reply to your question, but I can't resist. As others have noted, there's really no such thing as a "purely functional program" unless all you're looking for is turning your CPU into a space heater. That said, there's a very real sense in which the parts of your program that ARE pure are much safer and easier to reason about. The best way to visualize this is that you can theoretically turn an impure function into a pure one, watch: void printAGreeting(string greeting) { IO.printLn(greeting); } That's impure. The string goes into a black hole and does nothing from the program's perspective, but changes the state of the world from everyone else's perspective (it probably makes pixels change on a display somewhere). So how do you make that function pure? Like this: TheEntireUniverse printGreeting(string greeting, TheEntireUniverse u) { return u.makeSomeAtomsMoveAroundBobsConsole(greeting); } This function takes not only the string greeting, but everything else in the universe. You, me, everyone who's ever lived, bob's console, everything. It performs the same function by returning a new universe with everything the same except for the atoms moved to caused the greeting to appear on Bob's console. Obviously we can't do this, but (in Haskell, say) the IO monad is a context in which we understand that anything done inside of it has the affect of changing the "real world". If you pretend that you really had to do the whole "universe dragging" thing around for anything in the universe that WASN'T part of your program (we're assuming it itself is in some black hole somewhere to avoid paradoxes and such), then you would darn well make sure you kept this pain-in-the-ass operation as "thin" as possible. Why? Well, you're lazy! When you look at things that way, there's an analogy to writing stuff on bare metal hardware. You know you're going to have to write some assembly language (or maybe even direct machine code if you don't have an assembler). What you want is to get to the nice cozy world of C as soon as possible, so what you're going to do is use assembly to code only the smallest surface area you need so that writing in C makes sense, and everything else that doesn't need to be in assembly for performance sake you do in C. So it is in a purely functional language. The idea is to "get out of IO land" as fast as possible. To use a DB as an example, you'd like to live in a world where your little view of the DB could be expressed in purely functional terms (writing functions that result in a modified version of that view) and only at the VERY LAST MINUTE would you send that DB view to an IO function that actually sent the view down the wire to the DB. The same goes for files, sockets, gamepad IO, you name it. And you'd be surprised how little you need to actually interact with the world between getting your data and pushing out state changes. A game, for example, can do a WHOLE LOT OF STUFF between reading the input state and writing graphics commands.