Live data from Hacker News

Admitting That Functional Programming Can Be Awkward (2007)

prog21.dadgum.com

131–140 of 148 posts

Re: Admitting That Functional Programming Can Be Awkward (2007)

#131

I would much rather write the logic the author describes in Haskell than C. The C code will have to be carefully managed during development and maintenance to make sure the side effects are happening in the expected order and that no changes ever introduce unexpected interleaving of mutation. The Haskell version will prevent that trivially. You can't even express doing it the wrong way, because you can't mutate anyth…

Order of execution bugs rarely come up in game programming to be honest.

If that's true, why is it an issue John Carmack discusses as affecting difficulty of writing games that work correctly in modern environments?

> When you start thinking about running, say, all the characters in a game world in parallel, it starts sinking in that the object-oriented approach of updating objects has some deep difficulties in parallel environments. Maybe if all of the object just referenced a read only version of the world state, and we copied over the updated version at the end of the frame… Hey, wait a minute…

https://gamasutra.com/view/news/169296/Indepth_Functional_pr...

Re: Admitting That Functional Programming Can Be Awkward (2007)

#133
post #119

Functional programs tend to be more modular than imperative or OOP counter parts but nobody really knows why nor do they understand the cases where FP becomes less modular. FP is only modular when you use combinators. If you use closures then it's no longer modular. f x y = x + y g y = y * 2 w x = (f x) . g g and f are combinators and modular and w is the composition of both of those combinators. w = \x -> (\y -> (x…

Huh, what do you mean by "modular" that isn't satisfied by the closures?

Both examples have multiple functions defined as the definition of w.

But in one example f and g can be reused in other contexts, in the other example the two functions are tied together by free variables. Haskell heavily promotes the latter style with do notation. Any function written in the first style is decomposable into component combinators, any function written in the latter style cannot be decomposed.

Re: Admitting That Functional Programming Can Be Awkward (2007)

#134
post #3

I think anyone who has coded long enough would recognize that OOP, functional, imperative, etc all have their strengths and weaknesses, and all have problem domains they are better suited for. Unfortunately, this sometimes gets lost in the language fan wars - it’s easy to lose nuance when discussing something you are passionate about, I have made this mistake myself.

I think you're right.

When I program I basically think in terms of abstraction and state - if the code is obviously going to be multi-threaded then I will focus on the latter. Being stateful can slow you down on one thread but fuck you hard if it's in multi-threaded environment. Abstraction should really be free if you know what you're doing (in your language if choice I suppose)

Re: Admitting That Functional Programming Can Be Awkward (2007)

#135
post #120
post #102

Earlier quoted context omitted.

Maybe you mean optional totality? As in, a function can be declared to be total and the compiler tries to prove it? A language cannot be Turing-complete if it completely forbids non-termination, there is no way around this. Or stated differently: If a language is Turing-complete, then there are programs that don't terminate. If a Turing-complete language only has functions which are total (i.e., which always terminat…

> A language cannot be Turing-complete if it completely forbids non-termination, there is no way around this. I do acknowledge this. My point is that turing-completeness is not necessarily a desirable aspect of a language. You can check out Agda if you are interested in what can be done with a total language. For some use-cases, like seting up a mainloop, you need an escape hatch because that mainloop is not going to…

> My point is that turing-completeness is not necessarily a desirable aspect of a language.

Ah, okay, I see.

> For some use-cases, like seting up a mainloop, you need an escape hatch because that mainloop is not going to be total. But not every program needs that.

Hmm, I would argue that in practice, nearly all programs will need that, because they're either potentially non-terminating (let's call this "type 1") or have a runtime with a bound which is not determined by any of its inputs ("type 2"). Examples for type 1 include firmware, kernels, drivers, database engines, any kind of server, all programs with a GUI, all REPLs, and many Unix-style utilities that process or generate streams (thing grep, cat, sed, dd). Examples for type 2 include all programs with non-blocking IO (because even if the input and output are known to be bounded, the number of read/write calls is potentially unbounded) and most programs which take a file name as input (because the file size is not part of the program's input).

That doesn't leave many exceptions. I admit, however, that in most cases you could structure your program so that it has a non-total mainloop which exclusively calls proven total functions. But I don't think this is very useful, because even in safety-relevant, hard real-time contexts, termination is not sufficient, but termination within a certain timespan.

Re: Admitting That Functional Programming Can Be Awkward (2007)

#136

Earlier quoted context omitted.

It's a bit more subtle but the lack of statements that is, everything returns a value, is an important feature/quality.

Functional languages do have statements. a : Int -> Int a x = x + 2 both `:` and `=` are statements, not expressions.

How do any of those qualify as statements? Statements represent actions in imperative programming languages. Declarations and definitions are not actions.

Re: Admitting That Functional Programming Can Be Awkward (2007)

#137
post #116

Earlier quoted context omitted.

I’d agree with this. In my experience, choosing a language/platform based on the teams experience instead of the best tool for the job is a siren song. It’s less pain upfront, but way more amortized pain over the life of the project. Start with good engineers who understand CS fundamentals. After a week or two of immersion, they’re passable in a new language. After a month or two, there’s basically no difference with…

You might learn 95% in two months, but the last 5% might take decades. One fun thing is that the less you know the more productive you are. So a good strategy is to leave before you learn how horrible your code is, don't stay longer then two years so you don't have to deal with maintainability, performance, scaleability, security, etc.

If it were any other profession, I would clearly recognize this as satire.

Re: Admitting That Functional Programming Can Be Awkward (2007)

#138
post #135
post #120

Earlier quoted context omitted.

> A language cannot be Turing-complete if it completely forbids non-termination, there is no way around this. I do acknowledge this. My point is that turing-completeness is not necessarily a desirable aspect of a language. You can check out Agda if you are interested in what can be done with a total language. For some use-cases, like seting up a mainloop, you need an escape hatch because that mainloop is not going to…

> My point is that turing-completeness is not necessarily a desirable aspect of a language. Ah, okay, I see. > For some use-cases, like seting up a mainloop, you need an escape hatch because that mainloop is not going to be total. But not every program needs that. Hmm, I would argue that in practice, nearly all programs will need that, because they're either potentially non-terminating (let's call this "type 1") or h…

> That doesn't leave many exceptions.

Examples of programs which are useful and terminate are batch programs. Besides that, even in a server, you may want to ensure that a subset of the program terminates. For instance, if you're using a serverless architecture, knowing that your lambda will terminate may be useful.

Much like the separation between pure and impure functions in fp, having a distinction between total and non-total functions means that you can limit your non-total code to a couple dozen LoCs, which is a reasonable thing to manually check.

> But I don't think this is very useful, because even in safety-relevant, hard real-time contexts, termination is not sufficient, but termination within a certain timespan.

Once you've broken the non-termination issue, it becomes much more reasonable to start working on time estimates. Basically, once you've said that the number of instructions needed to solve the problem is a function of your input size, calculating that function becomes a possibility.

Re: Admitting That Functional Programming Can Be Awkward (2007)

#139
post #80

Earlier quoted context omitted.

Right but global variables are implicit. Function inputs/outputs are explicit. Another advantage is testing. That said, I still think passing the world in and out of every function is too onerous for most real world programs, and functional programming definitely takes a too-extreme stance to be pleasant. I think a better approach is a traditional language but with a functional subsystem where you can mark functions…

> That said, I still think passing the world in and out of every function is too onerous for most real world programs [...] This is a bit of a strawman. At least in my limited experience, functional programmers think so too. Part of what monads let you do is a kind of inversion of control: instead of changing the world directly (as you would if you were seriously threading the world through your domain logic), you re…

You still have to pass your logging code to every function that does logging right? And if you add logging to a function deep in your call stack you have to go all the way up the stack adding it as a parameter everywhere.

It's rigorously correct, but I feel like it's a step too far for most programs.

Re: Admitting That Functional Programming Can Be Awkward (2007)

#140
post #80

Earlier quoted context omitted.

> That said, I still think passing the world in and out of every function is too onerous for most real world programs [...] This is a bit of a strawman. At least in my limited experience, functional programmers think so too. Part of what monads let you do is a kind of inversion of control: instead of changing the world directly (as you would if you were seriously threading the world through your domain logic), you re…

You still have to pass your logging code to every function that does logging right? And if you add logging to a function deep in your call stack you have to go all the way up the stack adding it as a parameter everywhere. It's rigorously correct, but I feel like it's a step too far for most programs.

> You still have to pass your logging code to every function that does logging right?

Yes, but no. Again, there are functional patterns you can apply.

First, functional programs tend to be more horizontally composed than vertically. Side effects usually happen at the same "level" of the application, rather than occuring at multiple depths. So the need to thread logging apparati is already reduced, simply because logging only happens higher up in the application.

Second, because side-effects only really occur at this thin layer, you can capture logging as a monadic action, avoiding the need to explicitly thread the logger state around. (This is the same situation as the "thread the world" problem we discussed earlier.)

Third, because so much more of your program is pure, it is much easier to perform unit tests and check assertions in other ways. At that level, logging is a bandaid over poor testing and assurance, not a goal in itself.

Finally, if you do find yourself deep down-stack with a need to perform some kind of side-effect, you can apply logical methods (a la LVars, CRDTs) to give a stateful interaction between separated parts of your program. The Haxl paper [0] is a good example of this: most of the design is pure functional, but for the part that involve an interaction not easily captured by the fundamental call-return / request-response pattern, they utilize monotonic state to maintain a registry of fulfilled and unfulfilled requests.

[0] https://simonmar.github.io/bib/papers/haxl-icfp14.pdf

> It's rigorously correct, but I feel like it's a step too far for most programs.

I feel you're being too dismissive. There is an entire community of developers -- many of whom operate in industry -- who can and do effectively solve the problems you are raising.

"Rigorously correct" is one of those things non-FP folks think FP is about. You can write buggy code in any paradigm. What people like about FP is that it's easier to reason about, and it's generally easier to find precise abstractions that support that kind of reasoning. "Rigorously correct" is missing the point.

Post reply on HN