7 ms·
Whenever I hear someone talking about purely functional programming, no side effects, I wonder what kind of programs they are writing. Pretty much anything I've
by lukaslalinsky 2y ago
Whenever I hear someone talking about purely functional programming, no side effects, I wonder what kind of programs they are writing. Pretty much anything I've written over the last 30 years, the main purpose was to do I/O, it doesn't matter whether it's disk, network, or display. And that's where the most complications come from, these devices you are communicating with have quirks that need you need to deal with. Purely functional programming is very nice in theory, but how far can you actually get away with it?
- throw0101d 2y ago> Pretty much anything I've written over the last 30 years, the main purpose was to do I/O, it doesn't matter whether it's disk, network, or display. Erlang is a strictly (?) a functional language, and the reason why it was invented was to do network-y stuff in the telco space. So I'm not sure why I/O and functional programming would be opposed to each other like you imply.
- troupo 2y ago> Erlang is a strictly (?) a functional language, First and foremost Erlang is a pragmatic programming language :)
- mejutoco 2y agoAny useful program has side-effects. IMHO the point is to isolate the part of the code that has the side-effects as much as possible, and keep the rest purely functionsl. That makes it easier to debug, test, and create good abstractions. Long term it is a very good approach.
- fulafel 2y agoThis is discussing Common Lisp which is not even a mostly-functional language, and far from purely functional.
- vkazanov 2y agoSomehow haskell and friends shifted the discussion around functional programming to pure vs non-pure! I am pretty sure it started with functions as first order objects as differentiator in schemes, lisps and ml family languages. Thus functional, but that's just a guess.
- mrkeen 2y ago> Somehow haskell and friends shifted the discussion around functional programming to pure vs non-pure In direct response every other language in the mid 2010s saying, "Look, we're functional too, we can pass functions to other functions, see?" foo.bar() .map(x => fireTheMissiles()) .collect(); C's had that forever: void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *))
- milesrout 2y agoNo, functions aren't first class in C. When you use a function in an expression it undergoes lvalue conversion and "decays" to a pointer to the function. You can only call, store, etc function pointers, not functions. Function pointers are first class. Functions are not as you can't create them at runtime. A functional programming language is one with first class functions.
- grandempire 2y agoWhat is the impact on the user of having first class functions vs first class function pointers? Last I checked when you implement lambda in lisp it's also a pointer to the lambda internally.
- xigoi 2y agoFunction pointers can’t close over variables.
- 2y ago
- vkazanov 2y agoI had the same question until I understood one key pattern of pure functional programming. Not sure it has a name but here goes. There is world, and there is a model of the world - your program. The point of the program, and all functions, is to interact with the model. This part, data structures and all, is pure. The world interacts with the model through an IO layer, as in haskell. Purity is just an enforcement of this separation.
- skydhash 2y agoI thinks it the imperative shell, functional core. The shell provide the world, the core act on it, the the shell commit it at various intervals. Functional React follows this pattern. The issue is when the programmer thinks the world is some kind of stable state that you can store results in. It’s not, the whole point is to be created anew and restart the whole computation flow. The escape hatches are the hooks. And each have a specific usage and pattern to follow to survive world recreation. Which why you should be careful with them as they are effectively world for subcomponents. So when you add to the world with hooks, interactions with the addition should stay at the same level
- perrygeo 2y agoNot even the most fanatical functional programming zealots would claim that programs can be 100% functional. By definition, a program requires inputs and outputs, otherwise there is literally no reason to run it. Functional programming simply says: separate the IO from the computation. > Pretty much anything I've written over the last 30 years, the main purpose was to do I/O, it doesn't matter whether it's disk, network, or display. Every useful program ever written takes inputs and produces outputs. The interesting part is what you actually do in the middle to transforms inputs -> outputs. And that can be entirely functional.
- markus_zhang 2y ago> separate the IO from the computation. Can you please elaborate on this point? I read it as this web page (https://wiki.c2.com/?SeparateIoFromCalculation https://wiki.c2.com/?SeparateIoFromCalculation) describes, but I fail to see why it is a functional programming concept.
- mrkeen 2y ago> I fail to see why it is a functional programming concept. Excellent! You will encounter 0 friction in using an FP then. To the extent that programmers find friction using Haskell, it's usually because their computations unintentionally update the state of the world, and the compiler tells them off for it.
- AstroBen 2y agoThink about this: if a function calls another function that produces a side effect, both functions become impure (non-functional). Simply separating them isn't enough. That's the difference when thinking of it in functional terms Normally what functional programmers will do is pull their state and side effects up as high as they can so that most of their program is functional
- teddyh 2y agoHaving functions which do nothing but computation is core functional programming. I/O should be delegated to the edges of your program, where it is necessary.
- PhilipRoman 2y agoThere is no reason you can't use side effects in pure functional programming. You just need to provide the appropriate description of the side effect to avoid caching and force a particular evaluation order. If you have linear types, you do it by passing around opaque tokens. I'm not entirely sure how IO works in Haskell, but I think the implementation is similar. Even C compilers use a system like that internally.
- mrkeen 2y agoThink of it like other features: * Encapsulation? What's the point of having it if's perfectly sealed off from the world? Just dead-code eliminate it. * Private? It's not really private if I can Get() to it. I want access to that variable, so why hide it from myself? Private adds nothing because I can just choose not to use that variable. * Const? A constant variable is an oxymoron. All the programs I write change variables. If I want a variable to remain the same, I just wont update it. Of course I don't believe in any of the framings above, but it's how arguments against FP typically sound. Anyway, the above features are small potatoes compared to the big hammer that is functional purity: you (and the compiler) will know and agree upon whether the same input will yield the same output. Where am I using it right now? I'm doing some record linkage - matching old transactions with new transactions, where some details may have shifted. I say "shifted", but what really happened was that upstream decided to mutate its data in-place. If they'd had an FPer on the team, they would not have mutated shared state, and I wouldn't even need to do this work. But I digress. Now I'm trying out Dijkstra's algorithm, to most efficiently match pairs of transactions. It's a search algorithm, which tries out different alternatives, so it can never mutate things in-place - mutating inside one alternative will silently break another alternative. I'm in C#, and was pleasantly surprised that ImmutableList etc actually exist. But I wish I didn't have to be so vigilant. I really miss Haskell doing that part of my carefulness for me.
- DeathArrow 2y ago>I'm in C#, and was pleasantly surprised that ImmutableList etc actually exist. C# has introduced many functional concepts. Records, pattern matching, lambda functions, LINQ. The only thing I am missing and will come later is discriminated unions. Of course, F# is more fited for the job if you want a mostly functional workflow.
- mrkeen 2y agoI don't want functional-flavoured programming, I want functional programming. Back when I was more into pushing Haskell on my team (10+ years ago), I pitched the idea something like: You get: the knowledge that your function's output will only depend on its input. You pay: you gotta stop using those for-loops and [i]ndexes, and start using maps, folds, filters etc. Those higher-order functions are a tough sell for programmers who only ever want to do things the way they've always done them. But 5 years after that, in Java-land everyone was using maps, folds and filters like crazy (Or in C# land, Selects and Wheres and SelectManys etc,) with some half-thought-out bullshit reasoning like "it's functional, so it must good!" So we paid the price, but didn't get the reward.
- Xmd5a 2y agoThe boundary between the program and the rest of the system allows I/O of course. What FP does is "virtualize" I/O by representing it as data (thus it can be passed around). Then at some point these changes get "committed" to the outside. Representing I/O separately from how it is carried out allows a lot of things to be done, such as cancelling (ctrl+z) operations.
- AstroBen 2y agoIt's about minimizing and isolating state and side effects, not eliminating them completely Functional core, imperative shell is a common pattern. Keep the side effects on the outside. Instead of doing side effects directly, just return a data structure that can be used to enact the side effect
- zozbot234 2y agoThe point of functional, "sans I/O" style is to separate the definition of I/O from the rest of your logic. You're still doing I/O, but what sorts of I/O you're doing has a clear self-contained definition within your program. https://sans-io.readthedocs.io/how-to-sans-io.html https://sans-io.readthedocs.io/how-to-sans-io.html
- brap 2y agoThere are ways to handle side effects with pure functions only (it’s kind of cheating, because the actual side effects are performed by the non-pure runtime/framework that’s abstracted away, while the pure user code just defines when to perform them and how to respond to them). It’s possible, but it gets very awkward very fast. I wouldn’t use FP for any part of the code that deals with IO.
- lukaslalinsky 2y agoThat was the question, what would you use FP for?
- hnfong 2y agoI never believed any FP evangelist ever since I realized I can't even write quicksort with it *. (* Yes, you can technically write it procedurally like a good C programmer, sure.)
- larve 2y agoIt's always hard to parse if people mean functional programming when bringing up Lisp. Common Lisp certainly is anything but a functional language. Sure, you have first order functions, but you in a way have that in pretty much all programming languages (including C!). But most functions in Common Lisp do mutate things, there is an extensive OO system and the most hideous macros like LOOP. I certainly never felt constrained writing Common Lisp. That said, there are pretty effective patterns for dealing with IO that allow you to stay in a mostly functional / compositional flow (dare I say monads? but that sounds way more clever than it is in practice).
- behnamoh 2y ago> It's always hard to parse if people mean functional programming when bringing up Lisp. Common Lisp certainly is anything but a functional language. Sure, you have first order functions, but you in a way have that in pretty much all programming languages (including C!). It's less about what the language "allows" you to do and more about how the ecosystem and libraries "encourage" you to do.
- jcranmer 2y agoThe idea of pure functional programming is that you can really go quite far if you think of your program as a pure function f(input) -> outputs with a messy impure thing that calls f and does the necessary I/O before/after that. Batch programs are easy to fit in this model generally. A compiler is pretty clearly a pure function f(program source code) -> list of instructions, with just a very thin layer to read/write the input/output to files. Web servers can often fit this model well too: a web server is an f(request, database snapshot) -> (response, database update). Making that work well is going to be gnarly in the impure side of things, but it's going to be quite doable for a lot of basic CRUD servers--probably every web server I've ever written (which is a lot of tiny stuff, to be fair) could be done purely functional without much issue. Display also can be made work: it's f(input event, state) -> (display frame, new state). Building the display frame here is something like an immediate mode GUI, where instead of mutating the state of widgets, you're building the entire widget tree from scratch each time. In many cases, the limitations of purely functional isn't that somebody somewhere has to do I/O, but rather the impracticality of faking immutability if the state is too complicated.
- lukaslalinsky 2y agoI guess my point is that you actually have to write the impure code somehow and it's hard, external world has tendencies to fail, needs to be retried, coordinated with other things. You have to fake all these issues. In your web server examples, if you need to a cache layer for certain part of the data, you really can't without encoding it to the state management tooling. And this point you are writing a lot of non-functional code in order to glue it together with pure functions and maybe do some simple transformation in the middle. Is it worth it? I have respect for OCaml, but that's mostly because it allows you to write mutable code fairly easily. Roc codifies the world vs core split, but I'm skeptical how much of the world logic can be actually reused across multiple instances of FP applications.
- eduction 2y agoThere's a spectrum of FP languages with Haskell near the "pure" end where it truly becomes a pain to do things like io and Clojure at the more pragmatic end where not only is it accepted that you'll need to do non functional things but specific facilities are provided to help you do them well and in a way that can be readily brought into the functional parts of the language. (I'm biased though as I am immersed in Clojure and have never coded in Haskell. But the creator of Clojure has gone out of his way to praise Haskell a bunch and openly admits where he looked at or borrowed ideas from it.)
- timonoko 2y agoOpenSCAD is such a good school of functional programming. There is no "time" or flow of execution. Or variables, scopes and environments. You are not constructing a program, but a static model which has desired properties in space and time.
- diggan 2y ago> Whenever I hear someone talking about purely functional programming, no side effects, I wonder what kind of programs they are writing Where have you ever heard anyone talk about side-effect free programs, outside of academic exercises? The linked post certainly isn't about 100% side-effect/state free code. Usually, people talk about minimizing side-effects as much as possible, but since we build programs to do something, sometimes connected to the real world, it's basically impossible to build a program that is both useful and 100% side-effect free, as you wouldn't be able to print anything to the screen, or communicate with other programs. And minimizing side-effects (and minimizing state overall) have a real impact on how easy it is to reason about the program. Being really carefully about where you mutate things, leads to most of the code being very explicit about what it's doing, and code only affects data that is close to where the code itself is, compared to intertwined state mutation, where things everywhere in the codebase can affect state anywhere.
- djha-skin 2y agoI wrote a recursive descent parser in Lisp for a YAML replacement language[1]. It wasn't difficult. Lisp makes it easy to write I/O, but also easy to separate logic from I/O. This made it easy for me to write unit tests without mocking. I also wrote a toy resource scheduler at an HTTP endpoint in Haskell[2]. Writing I/O in Haskell was a learning curve but was ultimately fine. Keeping logic separate from I/O was the easy thing to do. 1: https://github.com/djha-skin/nrdl https://github.com/djha-skin/nrdl 2: https://github.com/djha-skin/lighthouse https://github.com/djha-skin/lighthouse
- epgui 2y agoEveryone writes real programs that have side effects. Functional programming is no different. But the side effects happen in specific ways or places, rather than all over the place.
- johnnyjeans 2y agoAs others have said, a pure program is a useless program. The only place stuff like that has in this world is as a proof assistant. What I will add is look up how the GHC runtime works, and the STGM. You may find it extremely interesting. I didn't "get" functional programming until I found out about how exotic efficient execution of functional programs ends up being.
- yogsototh 2y ago"Purely Functional Programming", I guess mostly Haskell/Purescript. So this only really mean: Purely Functional Programming by default. In most programming languages you can write "hello " + readLine() And this would intermix pure function (string concatenation) and impure effect (asking the user to write some text). And this would work perfectly. By doing so, the order of evaluation becomes essential. With a pure functional programming (by default). you must explicitely separate the part of your program doing I/O and the part of your program doing only pure computation. And this is enforced using a type system focusing on I/O. Thus the difference between Haskell default `IO` and OCamL that does not need it for example. in Haskell you are forced by the type system to write something like: do name <- getLine let s = "Hello " <> name <> "!" putStrLn s you cannot mix the `getLine` directly in the middle of the concatenation operation. But while this is a very different style of programming, I/O are just more explicit, and they "cost" more, because writing code with I/O is not as elegant, and easy to manipulate than pure code. Thus it naturally induce a way of coding that try to really makes you conscious about the part of your program that need IO and the part that you could do with only pure function. In practice, ... yep, you endup working in a "Specific to your application domain" Monad that looks a lot like the IO Monad, but will most often contains IO. Another option is to use a free monad for your entire program that makes you able to write in your own domain language and control its evaluation (either using IO or another system that simulates IO but is not really IO, typically for testing purpose).
- lukaslalinsky 2y agoThis is a good write up. Thank you for it. My experience with Haskell only comes from university and the focus there was primarily on the pure side of the code. I'll have a look at how Haskell deals with the pure/inpure split for some real-world tasks. The Lisp way of doing it just seems weird to me, too ad hoc, not really structured.
- dehrmann 2y agoThe pragmatic approach is to see that FP's key point is statelessness and use that in your code (written in more mainstream languages) when appropriate.
- krferriter 2y agoMost of the code in most programs is not the part that is doing the I/O. It's doing stuff on a set of values to transform them. It gets values from somewhere, does stuff using those values, and then outputs some values. The complicated part is not the transfer of the final byte sequence to whatever I/O interface they go to, the core behavior of the program is the stuff that happens before that.