Well now, what the hell have I been doing all this time?
According to TFA, apparently nothing that "works".
If you're working in C#, you're most likely working in Visual Studio (Mono is just such a terrifically small proportion of C# users, and even then, some Mono users are using Visual Studio), so you have access to one of the best (and easiest to use!) debuggers in the world. This isn't embedded C++. It is common for .NET developers to know how to use the debugger. People don't use printf-style debugging, and even if they thought they would want to, there is no easy way for such an undertrained developer to do it. You have to make a special build to get the "console" window, and it's a setting most developers I've encountered just plain don't even know exists.
And if you're using LINQ, it's because you know you want deferred execution and that's explicitly what you're hoping for. Developers who don't know this feature/limitation of LINQ just plain don't know about LINQ.
So after all these C# strawmen, his proposed solution is... rewrite your entire world in Haskell. Yeah, that's totally going to happen. I'm going to get my stakeholders on board for that, I'm going to have all of the libraries I've come to love available to me, or reasonable analogues (Just looked at HDBC, it looks like a toy compared to ADO.NET). We're just going to switch to Haskell and everything is going to be peaches and cream.
Firstly, no, people still do. Not everyone, but people do.
But even if nobody did, that doesn't change anything. The article isn't about the dangers of printf, it's about the dangers of side effects: printf is just an example. A printf inside an otherwise pure function is not the only possible example of a side effect.
> And if you're using LINQ, it's because you know you want deferred execution and that's explicitly what you're hoping for. Developers who don't know this feature/limitation of LINQ just plain don't know about LINQ.
So? You can still make mistakes here and your compiler won't catch them.
I think that the author's point was that C# was imperative and linq is functional. But the merge of the two does not give you best of both worlds, but rather to worst of both.
In pure functional coding the lack of side-effect guarantees safety during parallelism at the cost imperativity(code readability for those who have only ever written imperative code.).
In purely imperative code you get a guaranteed order of execution at the cost of simple parallelism because you are managing shared state.
With both you get to juggle the order of execution and shared state with none of the guarantees!
It can be done and I have seen it done successfully, but I would argue it is not a good starting point for a project or a language design.
Pure functional programming provides benefits in domains where you don't need parallelism or concurrency, too. It controls side effects, allowing safe composition of functions.
As far as I can tell, the argument is that allowing side effects inside functions allows programmers to make mistakes, and therefore "mostly functional" programming is broken.
I'll happily extend this: "fully functional" programming does not work because I can define a mathematical function that divides by 0, and therefore does not work. I'd go a step further even: all programming does not work, because I can solve a problem in the wrong way if I make a mistake.
His point was you cannot fix against bad programmers. While I'm sure we'd all agree that it's preferable that languages enable developers by throwing up the least amount of hidden traps, at some point we need to stop blaming our tools like the proverbial bad workman.
Mostly functional programming is broken because it cannot solve the problem it sets out to, namely controlling side effects. Because the language type system does not control them, "mostly functional" programming is no more useful than standard imperative programming.
I feel like the author makes it easy for himself, by both defining what other languages try to achieve by being mostly functional and how they fail to achieve that. I don't believe he's right with his assumptions.
It's not like linq, Erlang, f#, ocaml, or even python func/itertools designers said - we're going to be as pure as possible and functional to get that almost-purity. Some problems are just easier to solve with functional-like approach. Whether you're doing it in a pure way or not doesn't matter. Some languages give you a way to write that functional-like solution. I doubt many think of purity while using them, but problems get solved. And we're still better off than writing nested loops instead.
For example I'm happy with write:
items.filter(f1)
.map(f2)
.map(f3)
.reduce(...
and I really really don't need or want an IO monad to be returned from f2 if it happens to need to log some warnings. It would be completely counterproductive.
> and I really really don't need or want an IO monad to be returned from f2 if it happens to need to log some warnings. It would be completely counterproductive.
Are you sure you don't? This means that anything calling your function now produces log entries, but it's not obvious without reading its code.
Allowing side-effects saves you time now, but you might hate yourself later when trying to figure out why code in one part of your codebase is magically affecting code in another part.
Why should anything calling f2 care whether it can produce a warning or not? If the contract is simple - return a value, then that's all we normally care about.
If logs are not convincing, then think about profiling. One day you want to know what kind of values are computed by some function. Same story: impure adds stats.record(x), pure requires IO redesign.
> Why should anything calling f2 care whether it can produce a warning or not? If the contract is simple - return a value, then that's all we normally care about.
What if you call f2 multiple times? What if you call f2 an unpredictable number of times? What if you call f2 in a situation where the logging system isn't initialised?
The parameters and return value are not the only things that matter.
These aren't situations specific to the pure/not issue. You have to know how you deal with them in either case. The uninitialised case it a specific case of: what if your logging target goes away / never existed. Signatures won't help here, because that's a dynamic property of the system.
Programmers can control side effects. While mistakes can/will occur, lessening the benefits compared to language enforced control over side effects, there's a big difference between lessening and eliminating. As such, I'd hesitate to say it's "no more useful".
Mostly functional programming is good enough to make cooperative multitasking and asynchronous programming, which makes for faster servers and easier to reason about user interfaces.
You just have to remember a few globals and try to keep things uncomplicated. Also, having tests helps a lot.
Who says that "Mostly functional programming" sets out to control side effects?
How the hell do you get to the conconculsion that's it's no more useful than standard imperative programming? That implies that e.g. first class functions do not have merit, which is, excuse me, horseshit.
Lisp doesn't do that and it works wonderful. Same for ML/F#, PerformunsaveIO, Ocaml, you name it.
1/0 is not infinity, it is undefined. The limit as x tends to 0 of 1/x is infinity, but there is no value for 1/0, which is why doing a calculation that involves a divide by 0 is mathematically invalid. That Haskell let's you do it and gives an approximate answer is OK, but if it was in a calculation for the number of apples you can eat, it would be a useless answer. You could end up with that if you made a mistake in defining your logic. In the same way, you can make a mistake when doing all manner of other things such as using a non-pure function or adding instead of subtracting. They're all forms of logic error that are either allowed or disallowed by tools. The tool isn't broken if it let's you make mistakes though.
A power drill will let you drill a nail into your leg. That doesn't make power drills broken. A smart drill that turned off the drill if your leg was in front would be better - but we still use drills without that function because there are trade offs when you make your drill do that. There are trade offs in functional languages too. That doesn't make C# "broken" for being a tool that let's you drill into your leg if you point your drill there.
Well then your formula which contains a division by zero is mathematically invalid as well. What do you think is valid - a floating point exception? That's how the hardware handles it.
I think you're being overly pedantic on this specific example, division by zero is a special case. Floating point numbers on all architectures are only approximate - 1/7 times 7 is seldom exactly equal to 1, in any language, but that doesn't mean floating point math on a computer is useless.
Sure, but that wasn't my point - my point is that if you're calculating the number of apples you can eat and you make an error in the logic and end up with a calculation of x/y with x=22.9 and y=0, you get an exception or an answer of 'infinity'.
My point was: either answer is obviously wrong. You can't eat infinity apples any more than you can eat undefined apples. It wasn't the real answer to the question you were trying to solve. Haskell (or whatever language) allowed you to enter logic that gave an invalid answer. You used the mathematical and functional operators to blow off your leg. You could probably use a more constrained language dealing specifically in "apples and eating" that would prevent you from getting an answer that wrong. Obviously you'd have trade offs in using that language though.
So let me try again: Haskell isn't any more a broken language because it let's you calculate apples incorrectly than C# is a broken language because you can print things multiple times.
The standard for floating point, IEEE 754, defines X/0, where X != 0 as Infinity. 0/0 is defined as NaN. I disagree quite a bit with the philosophy of a lot of functional programming, but this is an area where they're right.
You can blow up the runtime with integer division by zero, however.
λ 1 `quot` 0
*** Exception: divide by zero
With `1/0`, it uses `Double` because `(/)` requires a Fractional type.
Also, partial functions like `head` or `fromJust` can burn you:
λ head []
*** Exception: Prelude.head: empty list
The latter is a legacy behavior that is generally considered undesirable. The former is more debatable; some would say that you should avoid integer division by an unknown value as much as possible.
In Haskell, exceptions in non-IO code (other than deliberate hard crashes) are generally considered a wart to be avoided and mitigated as much as possible (because they interfere with functional programming).
I started out reading not believing that pureness is a necessity and sadly the arguments weren't framed in a way that I could understand them. So, I'm still quite unconvinced because I haven't seen how it doesn't work. But of course I know from experience that it happens that things are true I haven't seen yet. Can anybody try to explain it for people who don't live on Haskell planet?
Example time: I program python, not C++. And in the example with the try-catch I would have wrapped it around the loop anyway. So for me it wasn't quite convincing that there is a problem in the first place. Sure someone who hasn't crashed a few programs due to lazy evaluaton might run into trouble in such kind of situation. But after a few times you understand that being defined and being executed are different things and then you wrap the execution. As far as I can see the whole problem described with the example doesn't exist if you intuitively wrap the foreach loop and not the definition statement for q.
So, would really appreciate if someone could explain it in a more nooby fashion to me. Thanks.
> But after a few times you understand that being defined and being executed are different things and then you wrap the execution.
In this example, you've had to internalize a bit of extra knowledge, and have an extra "gotcha" to watch out for. A job that could have been done by a computer, if things were kept pure, now has to be done by you, incurring slight amounts of mental overhead.
I don't program routinely in Haskell (although things are quickly headed in that direction), but I think the sentiment of that community is:
1. Keeping a large system consistent in your head is hard
2. Offloading the constant checking of that consistency, in any amount, to a computer, is a huge win that compounds quickly over time.
Note that the "hardness" of holding a large system in your head, and the "hardness" of Haskell's learning curve have to be qualitatively different for those assumptions to hold. In my experience, they are; essentially, you're making a large upfront cognitive investment in order to save slight amounts of mental overhead every day.
"And in the example with the try-catch I would have wrapped it around the loop anyway."
It's very likely that, in reality, you would forget the exception could be thrown until it happened in production.
So the advantages of the Haskell type system and purity guarantees, I think, often come down to the compiler preventing you from making certain errors that don't surface in other languages until you run the program. (Disclaimer: not a Haskell programmer.)
The example of the exception being thrown late (essentially, an exception being a type of side-effect) is quite good.
Would it be fair to say that this is an advantage of the Haskell approach of (for example) encapsulating an operation that might fail inside a Maybe? You get to make the failure aspect explicitly part of a monad, and make sure you "know" where it's happening?
It will, if by "Erlang" you mean "language + set of libs + set of conventions (all of this battle-tested for years)".
In short: (some) Erlang systems have backup hardware and support moving running systems from one machine to the other (or one cluster to the other, etc), achieving very high "uptime" values.
If radiation hits, oh say, a CPU register it might just change the value in a register. That is completely uncatchable and almost unobservable on modern hardware.
With any system on relying on hot switching to backups you must have a mechanism to detect errors. The mechanisms exist in many languages there are plenty of robust C++, Java, php, ruby, and sadly cobol systems out there. No new language gets to claim backups as a core feature of the language, unless cobol does too.
I am moving to my third job that has a cobol legacy system being replaced by something new. All three of these shops have backup systems that the business owners would never consider separating from their legacy cobol apps.
It is catchable and observable on modern hardware; mainframes have been doing it for years, with the operation also existing on some specialty Intel chips. Cores are paired, with each operation going through each of them simultaneously with the results compared. If the results don't match, they're run through again. If they still don't match, that pair of cores is shut down and operations are redirected.
Pairing like that would find this kind of error. It is also believable that mainframe shops would be willing to cut their throughput in half to get that level of reliability.
I was completely unaware of it even in a research setting. Yet I was aware of things like ECC RAM, RAID, Chipkill RAM and algorithms for continuous checking of results including some implemented in hardware to isolate faulty CPUs but they all added extra hardware or software and none reused existing commodity cores. Its so simple, of course it should be a thing.
This link from wikipedia has pretty much a good laundry list of descriptions different companies have taken for doing lock-stepping, etc https://en.wikipedia.org/wiki/Lockstep_%28computing%29 . It's also one of the reasons this level of machine is so expensive. Each mainframe core would be two cores in a "normal" system, plus mainframes have logic to decommission a cpu without needing a shutdown, by migrating the workload to a spare processor.
I only spoke with such confidence in my previous post because I thought I had worked on high end machines. I worked with a number of Sun and oracle machine with CPU failure detection and CPU hot swapping. But This feature was always flaky. I am eager to learn about what I missed.
I think the parent poster's point is more that the article's extensive focus on side effects and monadic encapsulation as the central concern for reliable software engineering is short-sighted, as Erlang is a dynamically typed impure functional language that nevertheless is robust in its use of multi-node distribution, supervisor trees, sophisticated error/exception models, scheduling guarantees, SMP, dynamic code upgrade as first-class VM constructs, as well as the language itself being homoiconic and driven by a small number of basic data structures (tuples and lists).
The language's module system offers namespacing and then the OTP framework implements standard behaviors that have (in gen_server's case) state calculation, supervision, code change, termination and messaging callbacks all exported.
I presume "surviving hardware failures" was in reference to native Erlang epmd machine distribution, which is intended for fault tolerance and based on heartbeats. It's one step further than most languages go.
Ultimately, there is much more to be considered than state and side effects.
What? The only possible way that constraint could be satisfied (working under any hardware failure) would be if erlang didn't let you write any programs that did anything.
The argument is that Erlang (and its OTP environment) gives you the tools to distribute your system in such a manner that hardware failure is observed and that the failed portion of the system is restarted elsewhere. The granularity provided is better than most STONITH models. It provides a lot of help, but one still needs to make an effort to assure that, e.g., merchandise is not sent unless an invoice is generated.
I don't buy this. The paradigm I use often depends on the level of abstraction I'm working with. A functional runtime is ultimately built upon an imperative foundation. I write mostly purely functional code in Scheme, but from time to time I add my own imperative components to the foundation. I don't really want to use languages that restrict me to a single paradigm.
The functional runtime (i.e. not compiler) is built upon an imperative foundation. This is necessarily so as long as hardware computer are built upon state-modifying processors and random access, in-place updatable memories.
So what? It's Turing-complete so it can simulate any non-Von Neumann machine that can compute. And in fact it does so very well, see Forth, Lisp, and other languages from different paradigms for which there are dedicated processors.
If you use an abstraction (your job as a programmer) the innards of the processor should approach inconsequential.
That code is ultimately compiled to run inside of a register machine. A language may give you a functional interface to something like disk I/O, but the underlying implementation is inherently imperative. Or how about the purely functional programming language Elm, whose runtime is imperative JavaScript?
For the record, I prefer purely functional programming, but it's not the only tool in the tool box.
Sorry if I'm being dense here, I understand the pattern of argument you're using, but I just don't understand what point you were trying to make. Could you explain?
Sure, I agree. My point is that sometimes we have to build these abstractions, so I like to use languages where I can program mostly in a functional style, but use another paradigm when appropriate.
>Do you not know how to perform a specific task within functional code?
It's not that I didn't know, it's that the process was imperative at its core. For example, I am working on a rendering engine using OpenGL. The high-level scene graph interface is purely functional, but rendering such a scene graph is an imperative process: binding textures, vertex arrays, shaders, writing to a framebuffer, etc. This low-level rendering code is the functional runtime for the high-level API.
You could do that within a purely functional language (via monads), but given how locked to C and the imperative paradigm OpenGL is, it would make sense to simply write that in C, yes.
I think monads are orthogonal to this issue. Using a monad would provide a pure interface around the imperative stuff, but the imperative stuff would still be there. Someone's gotta write the gross stuff so that we can build our pleasant, pure world on top of it.
I find it hilarious that all the examples he gives in the beginning have weird semantics only because of LAZINESS, mixing effects with laziness gives nonsense, give a cbv semantics to the language and you get the intended behavior (intended even by HIM who is arguing for laziness). Unbelievable.
Edit: in my comment, oppose pureness (that is put forward in his rant) to laziness (what he unintentionally is arguing against).
Laziness is just one way you'd see that issue crop up. It's merely an example of the dangers of functional programming in a language with side effects. When you compose functions, you have to be very careful that they either 1) have no effects, or 2) have very predictable and controlled effects. This isn't a problem you can have in a purely functional language, because it's a compilation error.
Also, it's not an argument against laziness. Laziness is fine in a purely functional language. It's one of the things that makes Haskell so pleasant.
And yet the first example is in C# which is not a "lazy language". The problem is that in any language you do want lazy behaviour from time-to-time. For example in "get me the first element of this list that is positive" you do not want to traverse the rest of the list after you find your positive element. Laziness always has a weird interaction with effects and you always want (the potential for) some laziness, hence the problem.
1 - Why would you ever want to add a logger to a pure function?
2 - If you really, for some unknown reason, wants to do that, you can pass the logger as an argument to f2, and have it run inside the IO monad somewhere outside of that fragment. But really, read #1 first.
What if you could do "fully functional" programming within a language that also allows OO, procedural, and generic programming?
My exhibit would be D. Here is the 'identity' declaration:
T identity(T)(const T me) pure nothrow @safe @nogc;
It cannot have side effect (do IO,start a thread,etc) because "pure". It cannot throw any exceptions because "nothrow". It cannot do any pointer-magic shenanigans because "@safe". It cannot modify the argument because "const". It even guarantees that no memory will be allocated through the garbage collector because "@nogc".
(Yes, pure by default etc would be better, but such a change would break too much code at this point. Yes, Haskell looks cleaner, but this article is not about beauty.)
One thing that bothers me with these articles is how they never address the software engineering challenges of writing large complex systems in a pure functional style. There are some tasks where just adding a sprinkle of imperative programming can make the design of the entire system much easier to understand (a kind of "mostly functional"). An article that really crystallized this point is here:
That post's been on HN before. I don't know if it's really making a good point.
> Imagine you've implemented a large program in a purely functional way. All the data is properly threaded in and out of functions, and there are no truly destructive updates to speak of. Now pick the two lowest-level and most isolated functions in the entire codebase. They're used all over the place, but are never called from the same modules. Now make these dependent on each other: function A behaves differently depending on the number of times function B has been called and vice-versa.
This is a complaint that the programming language is preventing you from introducing a hidden dependency. This is a strange complaint, given that hidden dependencies are a problem in software maintainability.
I mean, yes, it's convenient now to be able to "just" add global state here and there, but it will come back to bite you later.
> [Single-assignment form] is cleaner in that you know variables won't change. They're not variables at all, but names for values. But writing [single-assignment form] directly can be awkward.
Well, you don't have to write single-assignment form functional code. If you want to modify state within a function in the same way we all know and love from C, you actually can do that. In Haskell you could do this with the state monad, for example. Purely functional programming enables the composing of operations in many different ways, so you actually have a huge amount of freedom in what style you write your code.
> For me, what has worked out is to go down the purely functional path as much as possible, but fall back on imperative techniques when too much code pressure has built up. Some cases of this are well-known and accepted, such as random number generation (where the seed is modified behind the scenes), and most any kind of I/O (where the position in the file is managed for you).
Random number generation doesn't require threading the seed if you really don't want to. In Haskell, for example, you can also:
* Generate an infinite list of random numbers
* Call the IO monad function to get a new random number, which advances the generator behind-the-scenes
While the author might have a point that some things are simpler in imperative code, I don't think their examples really support this. Perhaps they had not delved very deep into functional programming.
I wish I could find that article again, it's just a claim in a comment at that point but that guy said his team rewrote a c++ system deemed first in class / world class (real time trading or something close) and unbeatable by its 30-years-in-the-field authors. Yet the F# (IIRC) was smaller AND faster, because they had better abstractions to describe the system, find and solve bottlenecks while the previous team members were drowning in c++ LoC and hubris.
- write all functions related to math/geometry/combinatorics in pure functional style, even better with accompanying mathematical proofs to be sure they are principally sound (you won't avoid problems with floating numbers etc. that way)
- think about using functional programming in parallel/distributed tasks if you can manage communication complexity directly (i.e. this is not handled to you by some opaque, un-debuggable monad)
- write everything else in imperative style of your choice
I/O is the elephant in the room of functional programming, monads are "imperative" hacks to make functional programming useful in any real-world-related way, and most monad tutorials will only confuse you about what they are actually.
I found that as I was learning functional programming, I started changing the way I was writing code even in imperative mode. I was separating things better, programs became more semantic and there are many opportunities to use functional style, but I still think imperative code is faster and easier to write for small functions, up to 20-30 lines of code.
Also, because it is so similar in a way, I learned a new appreciation for unix pipes and the shell syntax for composing programs.
I will continue to learn FP just to upgrade my programming skills.
> monads are "imperative" hacks to make functional programming useful in any real-world-related way
Monads are a quite clever solution to control IO: instead of allowing functions to directly perform IO, allow them to compose a sequence of IO operations and return them, such that you can compose a program which the runtime will then execute.
They're not imperative: they represent programs in a purely functional way, and they don't let you opt out of any of the guarantees a purely functional language provides. And they're not a hack, they fit properly into the type system.
Unlike "mostly functional" programming, use of monadic IO in a purely functional language will force you to cleanly separate IO and computation. Not only does this make a more maintainable program (no side effects everywhere), but you get nice bonuses, like lazy computation.
> and most monad tutorials will only confuse you about what they are actually.
Perhaps because many monad tutorials try to hide how they work, despite this really being quite simple.
GHC has `unsafePerformIO` in order to implement pure abstractions. It's an implementation detail, Haskell proper does not support it, and you wouldn't use it in your code.
Personally I don't believe in 100% pure FP, it's both a pain in the ass and doesn't really give you more than 85% FP already would. Actually, most of the FP world seems to agree: out of many Lisps, Scala, OCaml, SML, F#, Erlang/Elixir etc. only Haskell is that hell-bent on purity. Even things like PureScript are more pragmatic than Haskell in this regard.
I've been very excited by many approaches before, then Haskell, and now I'm past it. While pure functional language will force you to cleanly separate computation and IO, with an app big enough you'll find yourself one day in a situation where independent data from 3 different places comes together in a way that the only reasonable way to proceed is to log a warning, tweak the value, and continue the computation. And that's what you'll do in any unpure language. In a pure one, you'll start to think how to redesign the computation to allow this... It's not wrong, but in many cases it's pointless.
In some cases - yes, pointless. You rarely care if a function logs something or not. If you do care, you can still return the log lines as an extra value and use the pure solution. At least you have a choice.
Monad tutorials are a problem because they try to teach you to play football without first learning to walk. Progressive tutorials/introductions on the category theory constructs leading up to and including monads are better.
The problem is that category theory is "elitist", i.e. most programmers aren't equipped to comprehend even the basics properly if they didn't study theoretical computer science, computability theory, recursion theory etc. or even philosophy :-(
The problem is that 99.9% of the material about Category Theory in existence just forget to talk about two features of it: what is it useful for, and how it relates to mode widely known concepts, like functions.
Haskell is accessible. Just ignore the monad tutorials.
1) mostly imperative programming has worked wonders for ages with a huge body of work to show for (almost every basic infrastrcuture, all major OSes, almost all popular programs etc are writen in one of C/C++/Pascal/Obj-C/Fortran/Ada/Java). Might have its security and stability warts, but it has shown pragmatic results.
2) "fully functional" might have its security and stability assurances, but it has little of importance historically to show for all its boasting (the AI winter is not something to be really proud of).
I'd call BS, and say that "mostly functional" works fine, thank you very much, in that it takes (1) that already works (with some issues) and makes it even better with a touch of (2).
Did you even read the article? His point was that introducing some functional features to imperative languages do not confer them the safety benefits found in type systems such as Haskell's. It has absolutely nothing to do with pragmatism or any such thing.
>His point was that introducing some functional features to imperative languages do not confer them the safety benefits found in type systems such as Haskell's.
And my point was that I don't want ALL the safety benefits found in type systems such as Haskells. If I would, I would be using Haskell. Or rather, while I might want it, I also want other things more, so I wouldn't sacrifice all of them to get it.
His point was that mostly functional "does not work" which is a BS thing to say, because it's an absolute statement. And his whole article revolves around that absolute, e.g.:
>The idea of "mostly functional programming" is unfeasible. It is impossible to make imperative programming languages safer by only partially removing implicit side effects.
Nope, you can very much make them safer. It might not make them the "safest" it can be, but it can totally make them safer. Even merely substituting for loops with map and fold will eliminate lots of potential bugs.
Heck, you can even make C much safer by just removing pointer arithmetic or just adding a built-in string type. Either of those two things alone would eliminate tons of bugs while the language would remain the imperative thing that it is.
Exactly right. While some purists will bemoan imperfection and invent all sorts of "horror stories" from their ivory tower, engineers who actually get things done are quite satisfied with "good enough." Functional purism after awhile starts to smack of the bad old days of waterfall development, where each step in the series requires perfection lest the remaining steps collapse (if everyone would just follow this perfect system, we'd all have rainbows and unicorns).
You can have perfect safety, or you can have a workable system delivered in a timely manner.
Considering the languages that are normally used to write aircraft control software, this is perhaps a questionable choice of example.
And there are probably very good reasons why a language like Haskell is not likely to be chosen for this kind of application. Consider predictability: Haskell may make it easy to prove algorithmic correctness, but its laziness makes runtime characteristics more difficult to reason about because they're hidden behind so much abstraction. This could make it very difficult to deal with stuff like subtle bugs related to timing issues that might crop up in an electromechanical system.
Perfectly safe systems like planes, missiles, space missions etc. are 99% done in imperative languages such as C and Ada.
Not Haskell and not even Lisp.
The last thing you want from a perfectly safe system is some lazyness smartness in the compiler hitting at the wrong time and affecting execution time, memory use etc, or things like GC pauses.
> Nope, you can very much make them safer. It might not make them the "safest" it can be, but it can totally make them safer.
This one might come down to conflicting definitions of "safe". The author seems to define safety purely in relation to the number of language features with which you can contrive to shoot yourself in the foot. You seem to also take into account the number of language features you can use to make it harder to shoot yourself in the foot.
I think there's some value in both definitions. The author's perspective captures a lot of what worries me about languages like C++. But it also fails to give C++ any credit for all the language improvements that have materially improved the overall quality of most C++ code over the past couple decades.
> It has absolutely nothing to do with pragmatism or any such thing.
Yeah, well, for those of us who actually write programs, pragmatism matters a lot. If you're going to tell us how we should do it, but without anything like pragmatism, you're not going to get much of a hearing from the people you think you're trying to talk to.
I'm not why tincholio expressed it like that. It's exactly to do with pragmatism. Mixing effects and laziness is not pragmatic exactly because it leads to very confusing orders of effect execution.
So you don't have to mix effects with laziness, you can mix effects and other functional constructs (pure functions, immutable values, closures, map, fold, homoiconicity and macros, etc) and you still get a huge benefit.
So the theme of the article "mostly functional can't work" is wrong.
> So the theme of the article "mostly functional can't work" is wrong.
You're right. It's not really an issue of "functional" at all. It's worth noting that the author is an incorrigible (yet gentle) troll who likes to poke fun at Haskell and non-Haskell languages alike.
I think the author means to say that it's kind of an all or nothing thing. You either go purely functional and get all the benefits, or you don't and barely get any benefits at all.
You could say that "almost functional" does not really exist, just like "almost secure" does not exist.
And yes, you can still program with a non-functional language just fine he does not refute that.
>I think the author means to say that it's kind of an all or nothing thing. You either go purely functional and get all the benefits, or you don't and barely get any benefits at all.
Yes. And what I say is that "I get enough benefits from going halfway functional, thank you very much".
>You could say that "almost functional" does not really exist, just like "almost secure" does not exist.
You could say that but you would be wrong. Most Lisp coders for example have also used imperative code where they need it, with side effects and all. Nobody cared much about such total "functionalness" and purity as you get today from Haskell programmers (most of them non professional of course since there are far more Haskell programmers than Haskell jobs) and the like...
Every time I try to play with Haskell I give up in just a few days. Too much math! Maybe you don't need a PHD, but from my perspective you do need more than an undergraduate in Engineering.
So, what's a good purely functional language that's easier to learn than Haskell. One that I can learn more by hacking about than trying to keep so many things juggling about in my memory all at once?
Ideally one that runs native on Windows and Linux and is suitable for web and business apps? So I might actually end up with something I could put in (light) production.
Maybe you don't need a PHD, but from my perspective you do need more than an undergraduate in Engineering.
You just need a lot of patience. Learning Haskell takes a lot more than a few days. But it's worth it.
Haskell is the only industrial-strength purely functional (i.e. referentially transparent) programming language. The other popular functional languages (SML, OCaml, F#, and various Lisp and Scheme dialects) are all impure--they all have type systems that allow side effects anywhere in your code.
Haskell does run on Windows, Mac and Linux and is suitable for web and business applications. It has several fully-featured web programming frameworks: https://wiki.haskell.org/Web/Frameworks
I am using Yesod and I highly recommend it. It's like Ruby on Rails except the compiler automatically typechecks everything when you make a code change and tells you if you messed up. It is virtually impossible to even have a broken link.
Don't confuse new terminology with math. Programming has lots of terminology, and some of it is backed by mathematically rigorous definitions, but that doesn't require you to know that in order to use them.
OO has 'Inheritance', 'Subclassing', 'SOLID', 'Liskov', every design pattern name, 'Object', 'Class', 'Overloading', 'Factory', etc, etc.
Haskell has similar words, covering both core concepts, and common patterns of implementation. 'Monoid', 'Monad', 'State', 'Reader', 'Function', 'Lambda', 'Typeclass' are all examples of terminology in Haskell
None of those words are inherently more difficult than the OO ones, you're just not familiar with them.
The best way to learn Haskell is to follow along with a course that has you do exercises, and actually do the exercises. See the https://github.com/bitemyapp/learnhaskell course for some class recommendations.
Not just terminology. It's more like there's only two levels of Haskell: first it's understandable and I say, "Okay just step by step and I got this. Then all of a sudden we run smack into something like this big wall of WFT:
* The infix application function (ma>>=\a->f(a)), commonly called bind, executes the computation ma to expose its effects, calling the resulting value a, and passes that to function f. Evidently, the result of this application (f a) should again be a potentially side-effecting computation; hence, the type signature for bind looks like this:
(>>=):: M a -> (a -> M b) -> M b.
* The injection function (return a) injects a pure value into a computation. Its type signature is: return :: a -> M a.
In practice, each effect usually comes with so-called nonproper morphisms. These domain-specific operations are unique to that effect ...
Seriously, how can I hack around and understand this?
From my experience, it only really falls in place after you write your first or second monad. To learn that you go in that order:
1 - Get used to use the IO monad in do notation.
2 - Learn a couple more monads. I'd recommend State and one of Parsec or Attoparsec. (As a bonus, use a bit of applicative syntax too.)
3 - Get used to write some somewhat complex one liners without the do notation.
4 - Abuse the List monad for practice.
5 - Write your own.
All the time, look at the types as much as you need, but do not obsess over them.
This article's core premise, given in the first sentence, strikes me as a blatant straw man.
Normally I've seen mostly functional programming being sold as a pragmatic solution that merely gives developers some tools that are helpful in certain situations. It's deeply uncharitable to suggest that users of multiparadigm languages are so naive as to believe that the mere presence of paradigm A erases the pitfalls of paradigm B.
I'm also deeply suspicious of any language evangelism that makes a whole lot of noise about the pitfalls of other languages and then implies that one's own favored language has no pitfalls whatsoever. This one just comes down to Occam's razor. Which is more likely: That your favorite language just happens to be the first perfect, wart-free language ever invented, and its relative lack of popularity is just because the rest of the world can't recognize its blinding awesomeness? Or that you've become so attached to your favorite language that your perception of it has become deeply biased?
The specific strawman that is being presented is the free intermixing of functional and imperative. We have really nice codebases where the execution of side effects is specifically constrained to known locations.
From an optimization perspective, imperative behavior can be introduced if the functional API is not affected. Caching systems fall into this category. The functional benefits and freedom of usage is not impeded; the difficulty of creating and optimizing a caching system is the same as any other project.
Any quality from a project comes from discipline. Even purely functional programs can become unwieldy spaghetti with copy/paste code, memory leaks, etc. It's less about the programming languages, and more about proper experienced perspective of what to do and how to design.
C# is a language where functional and imperative are freely intermixed, most notably through the LINQ library that shows up in some of the author's C# code examples. The purpose in doing so is not to constrain side effects. It's to make code more declarative and enable the use of compositional patterns.
I think that's where the article really comes across as tone deaf. If you're going to write an article with a title like, "Mostly functional programming does not work," then you better be prepared to demonstrate that mostly functional programming fails accomplish the thing it sets out to do. Instead, what he really demonstrates is that he fails to understand that there are desirable things about functional programming aside from purity and laziness. Which is in itself a rather fascinating thing to miss considering that the majority of functional languages are neither lazy nor pure.
Scheme is not "fully functional". Neither is Common Lisp. No one will say they are mostly imperative either. But they work; before AI winter there were many machines and OS's built on top of them and their immediate predecessors.
If this author's definition of "does not work" means that they can't be used to build operating systems or high level AI programs well...
143 comments
[ 4.3 ms ] story [ 206 ms ] threadAccording to TFA, apparently nothing that "works".
If you're working in C#, you're most likely working in Visual Studio (Mono is just such a terrifically small proportion of C# users, and even then, some Mono users are using Visual Studio), so you have access to one of the best (and easiest to use!) debuggers in the world. This isn't embedded C++. It is common for .NET developers to know how to use the debugger. People don't use printf-style debugging, and even if they thought they would want to, there is no easy way for such an undertrained developer to do it. You have to make a special build to get the "console" window, and it's a setting most developers I've encountered just plain don't even know exists.
And if you're using LINQ, it's because you know you want deferred execution and that's explicitly what you're hoping for. Developers who don't know this feature/limitation of LINQ just plain don't know about LINQ.
So after all these C# strawmen, his proposed solution is... rewrite your entire world in Haskell. Yeah, that's totally going to happen. I'm going to get my stakeholders on board for that, I'm going to have all of the libraries I've come to love available to me, or reasonable analogues (Just looked at HDBC, it looks like a toy compared to ADO.NET). We're just going to switch to Haskell and everything is going to be peaches and cream.
Firstly, no, people still do. Not everyone, but people do.
But even if nobody did, that doesn't change anything. The article isn't about the dangers of printf, it's about the dangers of side effects: printf is just an example. A printf inside an otherwise pure function is not the only possible example of a side effect.
> And if you're using LINQ, it's because you know you want deferred execution and that's explicitly what you're hoping for. Developers who don't know this feature/limitation of LINQ just plain don't know about LINQ.
So? You can still make mistakes here and your compiler won't catch them.
In pure functional coding the lack of side-effect guarantees safety during parallelism at the cost imperativity(code readability for those who have only ever written imperative code.).
In purely imperative code you get a guaranteed order of execution at the cost of simple parallelism because you are managing shared state.
With both you get to juggle the order of execution and shared state with none of the guarantees!
It can be done and I have seen it done successfully, but I would argue it is not a good starting point for a project or a language design.
I'll happily extend this: "fully functional" programming does not work because I can define a mathematical function that divides by 0, and therefore does not work. I'd go a step further even: all programming does not work, because I can solve a problem in the wrong way if I make a mistake.
You can either think that everything is broken or nothing is broken, or you can try to make everything the least broken possible.
RyanZAG does not think that way, just tries to point out that the premise of the article seems broken.
It's not like linq, Erlang, f#, ocaml, or even python func/itertools designers said - we're going to be as pure as possible and functional to get that almost-purity. Some problems are just easier to solve with functional-like approach. Whether you're doing it in a pure way or not doesn't matter. Some languages give you a way to write that functional-like solution. I doubt many think of purity while using them, but problems get solved. And we're still better off than writing nested loops instead.
For example I'm happy with write:
and I really really don't need or want an IO monad to be returned from f2 if it happens to need to log some warnings. It would be completely counterproductive.Are you sure you don't? This means that anything calling your function now produces log entries, but it's not obvious without reading its code.
Allowing side-effects saves you time now, but you might hate yourself later when trying to figure out why code in one part of your codebase is magically affecting code in another part.
If logs are not convincing, then think about profiling. One day you want to know what kind of values are computed by some function. Same story: impure adds stats.record(x), pure requires IO redesign.
What if you call f2 multiple times? What if you call f2 an unpredictable number of times? What if you call f2 in a situation where the logging system isn't initialised?
The parameters and return value are not the only things that matter.
These aren't situations specific to the pure/not issue. You have to know how you deal with them in either case. The uninitialised case it a specific case of: what if your logging target goes away / never existed. Signatures won't help here, because that's a dynamic property of the system.
You just have to remember a few globals and try to keep things uncomplicated. Also, having tests helps a lot.
How the hell do you get to the conconculsion that's it's no more useful than standard imperative programming? That implies that e.g. first class functions do not have merit, which is, excuse me, horseshit.
Lisp doesn't do that and it works wonderful. Same for ML/F#, PerformunsaveIO, Ocaml, you name it.
A power drill will let you drill a nail into your leg. That doesn't make power drills broken. A smart drill that turned off the drill if your leg was in front would be better - but we still use drills without that function because there are trade offs when you make your drill do that. There are trade offs in functional languages too. That doesn't make C# "broken" for being a tool that let's you drill into your leg if you point your drill there.
I think you're being overly pedantic on this specific example, division by zero is a special case. Floating point numbers on all architectures are only approximate - 1/7 times 7 is seldom exactly equal to 1, in any language, but that doesn't mean floating point math on a computer is useless.
My point was: either answer is obviously wrong. You can't eat infinity apples any more than you can eat undefined apples. It wasn't the real answer to the question you were trying to solve. Haskell (or whatever language) allowed you to enter logic that gave an invalid answer. You used the mathematical and functional operators to blow off your leg. You could probably use a more constrained language dealing specifically in "apples and eating" that would prevent you from getting an answer that wrong. Obviously you'd have trade offs in using that language though.
So let me try again: Haskell isn't any more a broken language because it let's you calculate apples incorrectly than C# is a broken language because you can print things multiple times.
Also, partial functions like `head` or `fromJust` can burn you:
The latter is a legacy behavior that is generally considered undesirable. The former is more debatable; some would say that you should avoid integer division by an unknown value as much as possible.In Haskell, exceptions in non-IO code (other than deliberate hard crashes) are generally considered a wart to be avoided and mitigated as much as possible (because they interfere with functional programming).
b) All programs can be reduced by at least one line of code without affecting their behavior.
Therefore: All programs can be reduced to a one-liner, which doesn't work.
Unless you have wheels : http://www2.math.su.se/reports/2001/11/2001-11.pdf
Example time: I program python, not C++. And in the example with the try-catch I would have wrapped it around the loop anyway. So for me it wasn't quite convincing that there is a problem in the first place. Sure someone who hasn't crashed a few programs due to lazy evaluaton might run into trouble in such kind of situation. But after a few times you understand that being defined and being executed are different things and then you wrap the execution. As far as I can see the whole problem described with the example doesn't exist if you intuitively wrap the foreach loop and not the definition statement for q.
So, would really appreciate if someone could explain it in a more nooby fashion to me. Thanks.
In this example, you've had to internalize a bit of extra knowledge, and have an extra "gotcha" to watch out for. A job that could have been done by a computer, if things were kept pure, now has to be done by you, incurring slight amounts of mental overhead.
I don't program routinely in Haskell (although things are quickly headed in that direction), but I think the sentiment of that community is: 1. Keeping a large system consistent in your head is hard 2. Offloading the constant checking of that consistency, in any amount, to a computer, is a huge win that compounds quickly over time.
Note that the "hardness" of holding a large system in your head, and the "hardness" of Haskell's learning curve have to be qualitatively different for those assumptions to hold. In my experience, they are; essentially, you're making a large upfront cognitive investment in order to save slight amounts of mental overhead every day.
> Offloading the constant checking of that consistency, in any amount, to a computer, is a huge win that compounds quickly over time.
True. And therefore offloading a less amount to the computer (by being mostly but not totally functional) is less of a win, but still a win.
It's very likely that, in reality, you would forget the exception could be thrown until it happened in production.
So the advantages of the Haskell type system and purity guarantees, I think, often come down to the compiler preventing you from making certain errors that don't surface in other languages until you run the program. (Disclaimer: not a Haskell programmer.)
Would it be fair to say that this is an advantage of the Haskell approach of (for example) encapsulating an operation that might fail inside a Maybe? You get to make the failure aspect explicitly part of a monad, and make sure you "know" where it's happening?
The only language that allows you to build things that won't fail even when the hardware breaks.
Erlang.
If radiation hits the CPU, Erlang won't save you.
In short: (some) Erlang systems have backup hardware and support moving running systems from one machine to the other (or one cluster to the other, etc), achieving very high "uptime" values.
With any system on relying on hot switching to backups you must have a mechanism to detect errors. The mechanisms exist in many languages there are plenty of robust C++, Java, php, ruby, and sadly cobol systems out there. No new language gets to claim backups as a core feature of the language, unless cobol does too.
I am moving to my third job that has a cobol legacy system being replaced by something new. All three of these shops have backup systems that the business owners would never consider separating from their legacy cobol apps.
I was completely unaware of it even in a research setting. Yet I was aware of things like ECC RAM, RAID, Chipkill RAM and algorithms for continuous checking of results including some implemented in hardware to isolate faulty CPUs but they all added extra hardware or software and none reused existing commodity cores. Its so simple, of course it should be a thing.
Could you post a link to some reading on this?
The language's module system offers namespacing and then the OTP framework implements standard behaviors that have (in gen_server's case) state calculation, supervision, code change, termination and messaging callbacks all exported.
I presume "surviving hardware failures" was in reference to native Erlang epmd machine distribution, which is intended for fault tolerance and based on heartbeats. It's one step further than most languages go.
Ultimately, there is much more to be considered than state and side effects.
STONITH: https://en.wikipedia.org/wiki/STONITH
Some people even started a fork of GHC aimed at cluster parallelism a few years ago, but I don't think they got it working.
Not necessarily. Functional language compilers are very sophisticated and do not necessarily compile code the way you might think.
If you use an abstraction (your job as a programmer) the innards of the processor should approach inconsequential.
For the record, I prefer purely functional programming, but it's not the only tool in the tool box.
It is, but the code barely resembles the code most imperative programmers would write.
Memory safe languages are ultimately built upon an unsafe foundation.
Why? Do you not know how to perform a specific task within functional code?
In which case, you should probably take that opportunity to find out how to do it.
It's not that I didn't know, it's that the process was imperative at its core. For example, I am working on a rendering engine using OpenGL. The high-level scene graph interface is purely functional, but rendering such a scene graph is an imperative process: binding textures, vertex arrays, shaders, writing to a framebuffer, etc. This low-level rendering code is the functional runtime for the high-level API.
You could do that within a purely functional language (via monads), but given how locked to C and the imperative paradigm OpenGL is, it would make sense to simply write that in C, yes.
Edit: in my comment, oppose pureness (that is put forward in his rant) to laziness (what he unintentionally is arguing against).
Also, it's not an argument against laziness. Laziness is fine in a purely functional language. It's one of the things that makes Haskell so pleasant.
2 - If you really, for some unknown reason, wants to do that, you can pass the logger as an argument to f2, and have it run inside the IO monad somewhere outside of that fragment. But really, read #1 first.
My exhibit would be D. Here is the 'identity' declaration:
It cannot have side effect (do IO,start a thread,etc) because "pure". It cannot throw any exceptions because "nothrow". It cannot do any pointer-magic shenanigans because "@safe". It cannot modify the argument because "const". It even guarantees that no memory will be allocated through the garbage collector because "@nogc".(Yes, pure by default etc would be better, but such a change would break too much code at this point. Yes, Haskell looks cleaner, but this article is not about beauty.)
More importantly, however, D's type system is not as expressive as Haskell's.
Details: http://dlang.org/function.html#pure-functions
And Haskell's type system is not as expressive as D's. E.g. records.
Please, don't go there. D lacks add types and anything that isn't a first order type. Its type derivation also isn't even remotely as powerful.
T identity(T)(T me) { return me; }
http://prog21.dadgum.com/54.html
> Imagine you've implemented a large program in a purely functional way. All the data is properly threaded in and out of functions, and there are no truly destructive updates to speak of. Now pick the two lowest-level and most isolated functions in the entire codebase. They're used all over the place, but are never called from the same modules. Now make these dependent on each other: function A behaves differently depending on the number of times function B has been called and vice-versa.
This is a complaint that the programming language is preventing you from introducing a hidden dependency. This is a strange complaint, given that hidden dependencies are a problem in software maintainability.
I mean, yes, it's convenient now to be able to "just" add global state here and there, but it will come back to bite you later.
> [Single-assignment form] is cleaner in that you know variables won't change. They're not variables at all, but names for values. But writing [single-assignment form] directly can be awkward.
Well, you don't have to write single-assignment form functional code. If you want to modify state within a function in the same way we all know and love from C, you actually can do that. In Haskell you could do this with the state monad, for example. Purely functional programming enables the composing of operations in many different ways, so you actually have a huge amount of freedom in what style you write your code.
> For me, what has worked out is to go down the purely functional path as much as possible, but fall back on imperative techniques when too much code pressure has built up. Some cases of this are well-known and accepted, such as random number generation (where the seed is modified behind the scenes), and most any kind of I/O (where the position in the file is managed for you).
Random number generation doesn't require threading the seed if you really don't want to. In Haskell, for example, you can also:
* Generate an infinite list of random numbers
* Call the IO monad function to get a new random number, which advances the generator behind-the-scenes
While the author might have a point that some things are simpler in imperative code, I don't think their examples really support this. Perhaps they had not delved very deep into functional programming.
- write all functions related to math/geometry/combinatorics in pure functional style, even better with accompanying mathematical proofs to be sure they are principally sound (you won't avoid problems with floating numbers etc. that way)
- think about using functional programming in parallel/distributed tasks if you can manage communication complexity directly (i.e. this is not handled to you by some opaque, un-debuggable monad)
- write everything else in imperative style of your choice
I/O is the elephant in the room of functional programming, monads are "imperative" hacks to make functional programming useful in any real-world-related way, and most monad tutorials will only confuse you about what they are actually.
Also, because it is so similar in a way, I learned a new appreciation for unix pipes and the shell syntax for composing programs.
I will continue to learn FP just to upgrade my programming skills.
Monads are a quite clever solution to control IO: instead of allowing functions to directly perform IO, allow them to compose a sequence of IO operations and return them, such that you can compose a program which the runtime will then execute.
They're not imperative: they represent programs in a purely functional way, and they don't let you opt out of any of the guarantees a purely functional language provides. And they're not a hack, they fit properly into the type system.
Unlike "mostly functional" programming, use of monadic IO in a purely functional language will force you to cleanly separate IO and computation. Not only does this make a more maintainable program (no side effects everywhere), but you get nice bonuses, like lazy computation.
> and most monad tutorials will only confuse you about what they are actually.
Perhaps because many monad tutorials try to hide how they work, despite this really being quite simple.
have `unsafePerformIO` in the language.
Speak for yourself. I don't see any value in not using a tool when it's available and seems to do the job:
http://science.raphael.poss.name/haskell-for-ocaml-programme...
Personally I don't believe in 100% pure FP, it's both a pain in the ass and doesn't really give you more than 85% FP already would. Actually, most of the FP world seems to agree: out of many Lisps, Scala, OCaml, SML, F#, Erlang/Elixir etc. only Haskell is that hell-bent on purity. Even things like PureScript are more pragmatic than Haskell in this regard.
Here's a great talk on getting what's best from FP without also taking all the problems: http://www.infoq.com/presentations/functional-pros-cons
Interesting. In what way?
How so? Having a pure language has some significant benefits, like laziness. Data structures as control flow is awesome.
Are you sure it's pointless? Having to show I/O in the type signature means you can't end up with accidental side-effects.
https://hseeberger.wordpress.com/2010/11/25/introduction-to-...
[0] https://wiki.haskell.org/Typeclassopedia
http://bartoszmilewski.com/2014/10/28/category-theory-for-pr...
Haskell is accessible. Just ignore the monad tutorials.
1) mostly imperative programming has worked wonders for ages with a huge body of work to show for (almost every basic infrastrcuture, all major OSes, almost all popular programs etc are writen in one of C/C++/Pascal/Obj-C/Fortran/Ada/Java). Might have its security and stability warts, but it has shown pragmatic results.
2) "fully functional" might have its security and stability assurances, but it has little of importance historically to show for all its boasting (the AI winter is not something to be really proud of).
I'd call BS, and say that "mostly functional" works fine, thank you very much, in that it takes (1) that already works (with some issues) and makes it even better with a touch of (2).
I did, did you?
>His point was that introducing some functional features to imperative languages do not confer them the safety benefits found in type systems such as Haskell's.
And my point was that I don't want ALL the safety benefits found in type systems such as Haskells. If I would, I would be using Haskell. Or rather, while I might want it, I also want other things more, so I wouldn't sacrifice all of them to get it.
His point was that mostly functional "does not work" which is a BS thing to say, because it's an absolute statement. And his whole article revolves around that absolute, e.g.:
>The idea of "mostly functional programming" is unfeasible. It is impossible to make imperative programming languages safer by only partially removing implicit side effects.
Nope, you can very much make them safer. It might not make them the "safest" it can be, but it can totally make them safer. Even merely substituting for loops with map and fold will eliminate lots of potential bugs.
Heck, you can even make C much safer by just removing pointer arithmetic or just adding a built-in string type. Either of those two things alone would eliminate tons of bugs while the language would remain the imperative thing that it is.
You can have perfect safety, or you can have a workable system delivered in a timely manner.
And there are probably very good reasons why a language like Haskell is not likely to be chosen for this kind of application. Consider predictability: Haskell may make it easy to prove algorithmic correctness, but its laziness makes runtime characteristics more difficult to reason about because they're hidden behind so much abstraction. This could make it very difficult to deal with stuff like subtle bugs related to timing issues that might crop up in an electromechanical system.
Not Haskell and not even Lisp.
The last thing you want from a perfectly safe system is some lazyness smartness in the compiler hitting at the wrong time and affecting execution time, memory use etc, or things like GC pauses.
I do every time I get in a plane.
This one might come down to conflicting definitions of "safe". The author seems to define safety purely in relation to the number of language features with which you can contrive to shoot yourself in the foot. You seem to also take into account the number of language features you can use to make it harder to shoot yourself in the foot.
I think there's some value in both definitions. The author's perspective captures a lot of what worries me about languages like C++. But it also fails to give C++ any credit for all the language improvements that have materially improved the overall quality of most C++ code over the past couple decades.
Yeah, well, for those of us who actually write programs, pragmatism matters a lot. If you're going to tell us how we should do it, but without anything like pragmatism, you're not going to get much of a hearing from the people you think you're trying to talk to.
So you don't have to mix effects with laziness, you can mix effects and other functional constructs (pure functions, immutable values, closures, map, fold, homoiconicity and macros, etc) and you still get a huge benefit.
So the theme of the article "mostly functional can't work" is wrong.
Whether or not I read the article correctly, tome's comment was brilliant. It very succinctly exposed the pragmatic issue at stake.
You're right. It's not really an issue of "functional" at all. It's worth noting that the author is an incorrigible (yet gentle) troll who likes to poke fun at Haskell and non-Haskell languages alike.
You could say that "almost functional" does not really exist, just like "almost secure" does not exist.
And yes, you can still program with a non-functional language just fine he does not refute that.
Yes. And what I say is that "I get enough benefits from going halfway functional, thank you very much".
>You could say that "almost functional" does not really exist, just like "almost secure" does not exist.
You could say that but you would be wrong. Most Lisp coders for example have also used imperative code where they need it, with side effects and all. Nobody cared much about such total "functionalness" and purity as you get today from Haskell programmers (most of them non professional of course since there are far more Haskell programmers than Haskell jobs) and the like...
So, what's a good purely functional language that's easier to learn than Haskell. One that I can learn more by hacking about than trying to keep so many things juggling about in my memory all at once?
Ideally one that runs native on Windows and Linux and is suitable for web and business apps? So I might actually end up with something I could put in (light) production.
You just need a lot of patience. Learning Haskell takes a lot more than a few days. But it's worth it.
Haskell is the only industrial-strength purely functional (i.e. referentially transparent) programming language. The other popular functional languages (SML, OCaml, F#, and various Lisp and Scheme dialects) are all impure--they all have type systems that allow side effects anywhere in your code.
Haskell does run on Windows, Mac and Linux and is suitable for web and business applications. It has several fully-featured web programming frameworks: https://wiki.haskell.org/Web/Frameworks
I am using Yesod and I highly recommend it. It's like Ruby on Rails except the compiler automatically typechecks everything when you make a code change and tells you if you messed up. It is virtually impossible to even have a broken link.
OO has 'Inheritance', 'Subclassing', 'SOLID', 'Liskov', every design pattern name, 'Object', 'Class', 'Overloading', 'Factory', etc, etc.
Haskell has similar words, covering both core concepts, and common patterns of implementation. 'Monoid', 'Monad', 'State', 'Reader', 'Function', 'Lambda', 'Typeclass' are all examples of terminology in Haskell
None of those words are inherently more difficult than the OO ones, you're just not familiar with them.
The best way to learn Haskell is to follow along with a course that has you do exercises, and actually do the exercises. See the https://github.com/bitemyapp/learnhaskell course for some class recommendations.
From my experience, it only really falls in place after you write your first or second monad. To learn that you go in that order:
1 - Get used to use the IO monad in do notation. 2 - Learn a couple more monads. I'd recommend State and one of Parsec or Attoparsec. (As a bonus, use a bit of applicative syntax too.) 3 - Get used to write some somewhat complex one liners without the do notation. 4 - Abuse the List monad for practice. 5 - Write your own.
All the time, look at the types as much as you need, but do not obsess over them.
Normally I've seen mostly functional programming being sold as a pragmatic solution that merely gives developers some tools that are helpful in certain situations. It's deeply uncharitable to suggest that users of multiparadigm languages are so naive as to believe that the mere presence of paradigm A erases the pitfalls of paradigm B.
I'm also deeply suspicious of any language evangelism that makes a whole lot of noise about the pitfalls of other languages and then implies that one's own favored language has no pitfalls whatsoever. This one just comes down to Occam's razor. Which is more likely: That your favorite language just happens to be the first perfect, wart-free language ever invented, and its relative lack of popularity is just because the rest of the world can't recognize its blinding awesomeness? Or that you've become so attached to your favorite language that your perception of it has become deeply biased?
From an optimization perspective, imperative behavior can be introduced if the functional API is not affected. Caching systems fall into this category. The functional benefits and freedom of usage is not impeded; the difficulty of creating and optimizing a caching system is the same as any other project.
Any quality from a project comes from discipline. Even purely functional programs can become unwieldy spaghetti with copy/paste code, memory leaks, etc. It's less about the programming languages, and more about proper experienced perspective of what to do and how to design.
I think that's where the article really comes across as tone deaf. If you're going to write an article with a title like, "Mostly functional programming does not work," then you better be prepared to demonstrate that mostly functional programming fails accomplish the thing it sets out to do. Instead, what he really demonstrates is that he fails to understand that there are desirable things about functional programming aside from purity and laziness. Which is in itself a rather fascinating thing to miss considering that the majority of functional languages are neither lazy nor pure.
If this author's definition of "does not work" means that they can't be used to build operating systems or high level AI programs well...
http://platosallegory.com/TheStory.aspx
Food for thought and all that.