Hacker News new | ask | show | jobs
by perrygeo 439 days ago
Not 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.

5 comments

> 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.

My work needs pseudorandom numbers throughout the big middle, for example, drawing samples from probability distributions and running randomized algorithms. That's pretty messy in a FP setting, particularly when the PRNGs get generated within deeply nested libraries.

At what point does this get messy?
When deeply nested libraries generate PRNGs, all that layering becomes impure and must be treated like any other stateful or IO code. In Haskell, that typically means living with a monad transformer or effect system managing the whole stack, and relatively little pure code remains.

The messiness gets worse when libraries use different conventions to manage their PRNG statefulness. This is a non-issue in most languages but a mess in a 100% pure setting.

What I don't understand about your comment is: Where do these "deeply nested libraries" come from? I use one library or even std library and pass the RNG along in function arguments or as a parameter. Why would there be "deeply nested" libraries? Is it like that in Haskell or something? Perhaps we are using different definitions of "library"?
It's not that bad if you're using a splittable RNG, is it? Any function that (transitively) depends on an RNG needs an extra input, but that's it.
>Not 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.

So a program it's a function that transforms the input to the output.

>separate the IO from the computation.

What about managing state? I think that is an important part and it's easy to mess it.

Each step calculates the next state and returns it. You can then compose those state calculators. If you need to save the state that’s IO and you have a bit specifically for it.
It takes a bit of discipline, but generally all state additions should be scoped to the current context. Meaning, when you enter a subcontext, it has become input and treated as holy, and when you leave to the parent context, only the result matters.

But that particular context has become inpure and decried as such in the documentation, so that carefulness is increased when interacting with it.

> 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) describes, but I fail to see why it is a functional programming concept.

> but I fail to see why it is a functional programming concept.

"Functional programming" means that you primarily use functions (not C functions, but mathematical pure functions) to solve your problems.

This means you won't do IO in your computation because you can't do that. It also means you won't modify data, because you can't do that either. Also you might have access to first class functions, and can pass them around as values.

If you do procedural programming in C++ but your functions don't do IO or modify (not local) values, then congrats, you're doing functional programming.

Thanks. I now see why it makes sense to me. I work in DE so in most of our cases we do streaming (IO) without any transformation (computation), and then we do transformation in a total different pipeline. We never transform anything we consumed, always keep the original copy, even if it's bad.
> 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.

Think 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

Having 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.
> The interesting part is what you actually do in the middle to transforms inputs -> outputs.

Can you actually name something? The only thing I can come up with is working with interesting algorithms or datastructures, but that kind of fundamental work is very rare in my experience. Even if you do, it's quite often a very small part of the entire project.

A whole web app. The IO are generally user facing network connections (request and response), IPC and RPC (databases, other services), and files interaction. Anything else is logic. An FP programs is a collection of pipes, and IO are the endpoints. With FP the blob of data passes cleanly from one section to another while in imperative, some of it sticks. In OOP, there’s a lot of blob, that flings stuff at each other and in the process create more blobs.
A general "web app"'s germane parts are:

- The part that receives the connection

- The part that sends back a response

- Interacting with other unspecified systems through IPC, RPC or whatever (databases mainly)

The shit in between, calculating a derivative or setting up a fancy data structure of some kind or something, is interesting but how much of that do we actually do as programmers? I'm not being obtuse - intentionally anyway - I'm actually curious what interesting things functional programmers do because I'm not seeing much of it.

Edit: my point is, you say "Anything else is logic." to which I respond "What's left?"

> calculating a derivative or setting up a fancy data structure of some kind or something, is interesting but how much of that do we actually do as programmers?

A LOT, depending on the domain. There are many R&D and HPC labs throughout the US in which programmers work directly with specialists in the hard sciences. A significant percentage of their work is akin to "calculating a derivative".

There's lots left!

"When a customer in our East Coast location makes this purchase then we apply this rate, blah blah blah".

"When someone with >X karma visits HN they get downvote buttons on comments, blah blah blah".

Yes! In most projects, those requirements are stretched across tecnicalities like IOs. But you can pull them back to the core of your project. It takes effort, but the end result is a pleasure to work with. It can be done with FP, OOP, LP,…
> Even if you do, it's quite often a very small part of the entire project.

So your projects are only moving bits from one place to another? I've literally never seen that in 20 years of programming professionally. Even network systems that are seen as "dumb pipes" need to parse and interpret packet headers, apply validation rules, maintain BGP routing tables, add their own headers etc.

Surely the program calculates something, otherwise why would you need to run the program at all if the output is just a copy of the input?

Yes and I notice you still did not provide an interesting example. Surely parsing packets is not an interesting example of functional programming's powers?

What interesting things do you do as a programmer, really?

> parse and interpret packet headers, apply validation rules, maintain BGP routing tables, add their own headers etc.

That's a few more than zero. I don't do network programming, that was just an example to show how even the quintessential IO-heavy application requires non-trivial calculations internally.

Fair enough. It's just that in my experience the "cool bits" are quickly done and then we get bogged down in endless layers of inter-systems communication (HTTP, RPC, file systems, caches). I often see FP people saying stuff like "it's not 100% pure, of course there are some isolated side-effects" and I'm thinking.. my brother, I live inside side-effects. The days I can have even a few pure functions are few and far between. I'm honestly curious what percentage of your code bases can be this pure.

But of course this heavily depends on the domain you are working in. Some people work in simulation or physics or whatever and that's where the interesting bits begin. (Even then I'm thinking "programming" is not the interesting bit, it's the physics)

> my brother, I live inside side-effects. The days I can have even a few pure functions are few and far between. I'm honestly curious what percentage of your code bases can be this pure.

I've never seen what you work on so there is no way I can say this with certainty, but generally people unfamiliar eith functional programming have way more code that is / or can be pure in their code base than they realize. Or put the opposite, directly as is if you were to go line by line in your code (skipping lines of comments and whitespace) and give every line a yes/no on whether is performs IO, what percentage are actually performing IO? Not are related to IO, or are preparing for or handing the results of IO, but how many lines are the actual line to write to the file or send the network packet?

Generally, it's a much smaller percentage than people are thinking because they are usually associating actual IO with things "related to" or "preparing for" or "handing results from" IO.

And then after finding that percentage to be lower than expected, it can also be made to be significantly lower by following a few functional programming design approaches.

> The days I can have even a few pure functions are few and far between. I'm honestly curious what percentage of your code bases can be this pure.

A big part of it, I'm sure, but it requires some work. Pushing the side effects to the edge requires some abstractions to not directly mess with the original mutable state.

You are, in fact designing a state diagram from something that was evolving continuously on a single dimension: time. The transition of the state diagram are the code and the node are the inputs and output of that code. Then it became clear that IOs only matters when storing and loading those nodes. Because those nodes are finite and well defined, then the non-FP code for dealing with them became simpler to write.

It's a matter of framing. Think of any of the following:

- Refreshing daily "points" in some mobile app (handling the clock running backward, network connectivity lapses, ...)

- Deciding whether to send an marketing e-mail (have you been unsubscribed, how recently did you send one, have you sent the same one, should you fail open or closed, is this person receptive to marketing, ...)

- How do you represent a person's name and transform it into the things your system needs (different name fields, capitalization rules, max characters, what it you try to put it on an envelope and it doesn't fit, ...)

- Authorization logic (it's not enough to "just use a framework" no matter your programming style; you'll still have important business logic about who can access what when and how the whole thing works together)

And so on. Everything you're doing is mapping inputs to outputs, and it's important that you at least get it kind of close to correct. Some people think functional programming helps with that.

When I see this list all I can think of is how all these things are just generic, abstract rules and have nothing to do with programming. This, of course, is my problem. I have a strange mental model of things.

I can't shake off the feeling we should be defining some clean sort of "business algebra" that can be used to describe these kind of notions in a proper closed form and can then be used to derive or generate the actual code in whatever paradigm you need. What we call code feels like a distraction.

I am wrong and strange. But thanks for the list, it's helpful and I see FP's points.

You're maybe strange (probably not, when restricted to people interested in code), but wrongness hasn't been proven yet.

I'd push back, slightly, in that you need to encode those abstract rules _somehow_, and in any modern parlance that "somehow" would be a programming language, even if it looks very different from what we're used to.

From the FP side of things, they'd tend to agree with you. The point is that these really are generic, abstract rules, and we should _just_ encode the rules and not the other state mutations and whatnot that also gets bundled in.

That implicitly assumes a certain rule representation though -- one which takes in data and outputs data. It's perfectly possible, in theory, to describe constraints instead. Looking at the example of daily scheduling in the presence of the clock running backward; you can define that in terms of inputs and outputs, or you can say that the desired result satisfies (a) never less than the wall clock, (b) never decreases, (c) is the minimal such solution. Whether that's right or not is another story (it probably isn't, by itself -- lots of mobile games have bugs like that allowing you to skip ads or payment forever), but it's an interesting avenue for exploration given that those rules can be understood completely orthogonally and are the business rules we _actually_ care about, whereas the FP, OOP, and imperative versions must be holistically analyzed to ensure they satisfy business rules which are never actually written down in code.

I agree.

Especially when reading Rust or C++.

That's code I would prefer to have generated for me as needed in many cases, I'm generally not that interested in manually filling in all the details.

Whatever it is, it hasn't been created yet.

You can name almost anything (these are general-purpose languages, after all), but I'll just throw a couple of things out there:

1. A compiler. The actual algorithms and datastructures might not be all that interesting (or they might be if you're really interested in that sort of thing), but the kinds of transformations you're doing from stage to stage are sophisticated.

2. An analytics pipeline. If you're working in the Spark/Scala world, you're writing high-level functional code that represents the transformation of data from input to output, and the framework is compiling it into a distributed program that loads your data across a cluster of nodes, executes the necessary transformations, and assembles the results. In this case there is a ton of stateful I/O involved, all interleaved with your code, but the framework abstracts it away from you.

Thanks, especially two is very interesting. Admittedly the framework itself is the actually interesting part and that's what I meant with this work being "rare" (I mean how many people work on those kinds of frameworks fulltime? It's not zero, but..)

I think what I engaged with is the notion that most programming "has some side-effects" ("it's not 100% pure"), but much of what I see is like 95% side-effects with some cool, interesting bits stuffed in between the endless layers of communication (without which the "interesting" stuff won't be worth shit).

I feel FP is very, very cool if you got yourself isolated in one of those interesting layers but I feel that's a rare place to be.

Yeah, it's not that e.g. Haskell won't allow side effects, it's that side effects are constrained: 1) all the side-effectful operations have types that forbid you from using them outside of a side-effect context; 2) and it's a good thing they do, because Haskell's laziness means the results you would get otherwise are counterintuitive.

Other FP frameworks are far less strict about such things, and many FP features are now firmly in the mainstream. So no, I don't think this stuff is particularly rare, though Haskell/OCaml systems probably still are. There are pluses and minuses with structuring code in a pure-core-with-side-effect-shell way – FP enthusiasts tend to think the pluses outweigh the minuses.

Best, I think, to view FP not as dogma or as a class of FP-only languages, but rather as a paradigm first, a set of languages second.