>> navigating all of the offerings, examining their trade-offs
the amount of time you're afforded for that exercise better fit neatly inside a very small window of implementation (in other words you're probably not going to do it, or at least do it justice)
>> figuring out which ones fit best to the system being built in terms of constraints
constraints will always get ya. It always ends up to being what is the tool/paradigm that you (and your project manager) are most comfortable with because you'll most likely use it in leu of anything else (especially given you are not the only one that has to be convinced -- engineers don't operate in a vacuum, and they love predictability, ie what they already know)
>> You won't get a very optimal solution by ...
YAGNI. Premature Optimization. KISS.
I am not saying that ^^^ is true, I'm just introducing you to your new conversation buddies for the foreseeable future. People always bring'em along for the conversation.
>>> trying to talk you into a religion
advocacy among unbelievers is always gonna come off like this, especially when the evangelists have dealt with so many naysayers and the apathetic majority. And this is probably the crux of the entire issue. Students' first languages in school are generally imperative languages. They are taught in their youth a path away from functional programming. Which is funny to me because my school was always promoting that their studies were there to help you grow one's intellect and not necessarily fit into some cog of the industry. But, I don't recall much playtime given to functional languages (not as much as they deserved at least).
My point is that it would be nice if FP was the first tool that was given to students that pour out of universities into developer teams. It would be nice if the easy button for a group was FP, not the other way around. Then it would be much easier to beat the other drums (like the testing drum).
I am sorry, but you sound like you know for a fact that functional programming is better, but you have trouble making others recognize that fact.
Imho, FP has many tangible weaknesses, just a few off the top of my head:
- Immutability is non-intuitive: If I were to ask someone to make an algorithm that lists all the occurrences of a word on a page, they wouldn't intuitively come up with an algorithm that takes a slice of text and a list of occurrences, then returns an extended list, and an linked list with one more occurrence.
- Immutability can cause more problems than solve: If I were to create a graph of friendships between people, chances are that if I were to add a link between A and B, then not only A and B would need to be updated, but everyone who transitively knows A and B (which, since no man is an island would probably mean, everyone). This is highly complex, and probably just as bad as living with mutability.
- FP is not performant: FP code tends to be full of pointers and non-performant data structures like linked lists that have very little memory locality. It also makes optimizations such as updating things in a certain order to avoid recalculation impossible.
- FP has to deal with side effects: The real world has side effects, and your FP code is probably a bit of business logic that responds to a HTTP request, and fires of some other request in turn. These things have unavoidable side effects.
FP isn’t just limited to Haskell while your criticisms seem aimed only at that one language.
Immutability may not be intuitive, but neither are pointers, hash tables, structured loops, OOP, etc. In any case, maintaining immutable code is certainly more humane than trying to reason about 10th order side effects. Finally, the majority of functional languages are immutable by default with optional mutation when needed.
Your immutable argument is a red herring. It’s the wrong algorithm. Bubble sort is much more intuitive than heap or quick sort, but you certainly wouldn’t recommend it.
You are flatly wrong about performance. Ocaml and StandardML are just as fast as languages like Go or Java. This is incredible when you realize These FP languages are maintained by a handful of people with basically no budget and are keeping pace with languages that have tens out hundreds of millions of dollars pushed into them.
FP doesn’t mean no side effects (though it does tend to encourage limiting and separating them). Outside of Haskell and Miranda, basically all the other FP languages have side effects and even Haskell has an escape hatch available.
Haskell has to have an escape hatch in order to work, though fortunately it is hidden away.
Every program has effects. A program that does not have any effects (IO) would not be useful, as you can't get anything into or out of it.
In FP we manage those effects, in order to help ensure correctness, with the additional benefit that good effect management makes the program easier to comprehend (nee reason about).
Contrast a procedural program with effects littered throughout the codebase, with a program wherein effects are pushed to the edges. In the latter, you and your team know exactly where all of the effects occur. Everything else is pure code: easier to test, easier to comprehend, easier to compose.
Category theory is not required for good effect management. It just so happens that monads like IO fit the problem space nicely; although the same could be achieved with a lazily evaluated definition of computation (i.e. a function).
On this friendship example, I think already FP is showing you something useful about the way you are designing your model.
A friendship is really an edge in a graph. if you manage friendships by recording a collection of friend PersonId's on each person, you are modelling a graph by recording the same edge in two different places, and then hoping the programmer is diligent enough to never make a mistake and update the edge in one place but not the other. If you model the friendship graph as a collection of id pairs, not encapsulated inside a specific person structure, this invalid state is no longer possible.
I'm not sure what you are on about regarding making transitive updates - that idea is obviously not going to work on any moderately sized network. Imagine if Facebook tried to record all your transitive friendships on your account!
Sure immutability is non-intuitive, but it's really helpful! Like borrow checking in Rust, immutability helps eliminate an entire class of bugs. And immutable code is often much easier to reason about, you need to keep significantly less of the context in your head.
I think you haven’t read much of the literature and don’t have any real experience with it on a real project.
There is no intuition when it comes to programming. Everyone has to learn it. You’re appealing to familiarity of concepts you’ve already learned and assume everyone else thinks the same way.
There are highly efficient graph algorithms using pure immutable data structures. You don’t seem to be aware of how much sharing is enabled by immutability nor the algorithms for efficiently updating graphs in a pure functional way. Try reading this book [0].
Again there is a whole zoo of optimizations available exclusively to pure functional languages that you don’t seem to be aware of. Try reading Counting Immutable Beans. Some of the optimizations GHC can do are impressive. It’s not all bad performance.
Want to talk about cache misses? Check out an average C++ code base. It has a dedicated cache-miss operator.
Side effects are everywhere. And yes, in a purely reverentially transparent language, it becomes a pain being forced to be explicit about every mutation, network call, and file handle operation when you’re used to throwing caution to the wind and flying free with them. Improper handling of side effects include common use-after-free errors, etc. After years of dealing with this mud all of software, at least for me, explicit handling of effects becomes exactly what you want in order to keep your sanity intact.
> There is no intuition when it comes to programming. Everyone has to learn it.
There is no intuition in anything aside from the very few things we are born with. Everything else is learned from experiences.
And to that end, chances are someone has learned to follow procedure-like steps like a recipe at a very early age - indeed, in some schoolbooks recipes and other "procedural" works are among the first things someone does.
Children learn things like "to do X, first do Y and then Z" from a much earlier age, often before even learning how to read and write (e.g. via simple papercrafting in kindergarden) than something like "X = Y*Z". Giving and taking commands (i.e. "imperative" programming) as a concept is something people meet in their lives much sooner and are more exposed to than abstract problems.
I think you're completely missing an entire world of FP languages which are not pure like Haskell, ocaml, elm, etc... There are languages with side effects as an easily accessible escape hatch but don't do things which make debugging a pain like data encapsulation and inheritance.
Also, there are FP langs out there that have better data/heap locality with indirected pointer data structures, because they don't share memory and so the pointers aren't tied to anywhere except where they are directly running.
I completely disagree with immutability being non-intuitive. Try explaining to a junior/bootcamp JavaScript or python developer why when you pass an integer into a function it doesn't reflect changes you made in the called function when you jump back into your calling frame... But if you do the same with a object/dictionary...
For junior programmers immutable passing is absolutely the default assumption and so I think it's reasonable to claim it's the intuitive choice.
There's even crazymaking shit where in python if you have a default array parameter you can seriously fuck up your model of what's going on if you mutate that array anywhere.
>> I am sorry, but you sound like you know for a fact that functional programming is better
I can see the room for misunderstanding. My only argument is that FP is worthy of more playtime than it currently enjoys in undergraduate academia. I'd wager that the current percentage of FP/Imperative playtime is 95/5.
Ah I see. I've been out of school for a while so I don't know how students are taught - but if you take a modern language such as Kotlin, you'll see it's a mishmash of concepts from various paradigms, including FP.
> - Immutability is non-intuitive: If I were to ask someone to make an algorithm that lists all the occurrences of a word on a page, they wouldn't intuitively come up with an algorithm that takes a slice of text and a list of occurrences, then returns an extended list, and an linked list with one more occurrence.
What do you base this on? Did you run a study of people who had never programmed before and then asked them to invent a pseudo-programming language to solve this problem? Or are you talking about the "intuition" of people that have already learned to program, mostly likely in an imperative programming language, in which case your claims to "intuition" don't mean anything.
> - Immutability can cause more problems than solve: If I were to create a graph of friendships between people, chances are that if I were to add a link between A and B, then not only A and B would need to be updated, but everyone who transitively knows A and B (which, since no man is an island would probably mean, everyone). This is highly complex, and probably just as bad as living with mutability.
Firstly, that's not true, it depends entirely on what sort of data structure is used. For instance, a trivial model is a simple list of graph modifications, ie. type Action = AddFriend(a,b) | RemoveFriend(a,b), type FriendGraph = List(Action). Any change to the graph thus requires allocating only a single node.
Secondly, if you were to model this mutably, a node removal means you would lose information about the fact that two people were friends at one time.
> - FP is not performant: FP code tends to be full of pointers and non-performant data structures like linked lists that have very little memory locality. It also makes optimizations such as updating things in a certain order to avoid recalculation impossible.
This argument is based on assumptions about the functional programming idioms, the compiler, the machine it's running on and more. I could just as easily say that imperative programming doesn't scale because you can't easily parallelize it across cores, and since single-core performance is now a dead end, even if everything you just said were true it's basically irrelevant for the question of performance.
> - FP has to deal with side effects: The real world has side effects, and your FP code is probably a bit of business logic that responds to a HTTP request, and fires of some other request in turn. These things have unavoidable side effects.
I'm not sure who denies this. The question is not whether useful software has side-effects, it's what sort of side-effects you actually need for any given program and how those side-effects should be modeled so you can properly reason about them and achieve the program properties you need.
If your programming language has pervasive side-effects then you have to deal with them everywhere, and the failure modes multiply. If your programming language does not support pervasive side-effects, then the side-effects are pushed to the edges of your program where they are more amenable to reasoning. Questions like "how did we get into this state?" are considerably simpler to backtrace as a result.
1. Immutability is non-intuitive. If I were to hand a regular person a book, and ask him how he would list all the page's numbers where the word cheese appears, he would probably say something like this: 'I would read through the book, keeping a scrap of paper at hand. Whenever I saw the word cheese, I would write down the page number' This is an imperative algorithm. I could give more examples, but I hope you get the point.
2. I just wrote the 'friends' example to make a point - common, complex programs often have huge graphs of interconnected objects, whether one would like it or not. Your solution is to build a journal of friendships and breakups - it's not a typical solution for object relations - seeing if 2 people are friends is a linear operation. Keeping the history around might not be necessary or useful.
3. Your FP code runs on a modern OoO CPU whether you like it or not - so it's safe to make that assumption. And multithreading might not be a silver bullet. FP does nothing to break long dependency chains. As for sharing values between CPU cores, considering the latencies involved, it might be cheaper to compute the value locally, but multithreaded optimization is a black art - it's not always obvious if sharing is better.
Another example is when I made a small toy Excel-like spreadsheet that supported formulas - while Excel itself is FP, I realized that the best way to update cell values, is to topologically sort all dependent expressions, and evaluate them one after the other - an imperative concept.
4. I was just making the point is it's easy to write a function in e.g. Java that is imperative on the inside, but is pure when called from the outside. Other languages will even allow the compiler to enforce this, while still being imperative. So both 'regular' and FP languages can avoid some side effects to some extent, but have to deal with others.
> 1. Immutability is non-intuitive. If I were to hand a regular person a book, and ask him how he would list all the page's numbers where the word cheese appears, he would probably say something like this: 'I would read through the book, keeping a scrap of paper at hand. Whenever I saw the word cheese, I would write down the page number' This is an imperative algorithm.
No, that's a purely functional algorithm. Note how he's just adding numbers to the end of a list and emphatically not mutating the existing items in any way. Tell me what you see as the functional real-world solution to this problem. Do you consider an accounting general ledger to also be imperative? Because it clearly isn't, it's an append-only log where all previous entries are immutable, which is exactly the same thing as the list noting the pages containing the word "cheese".
> 2. I just wrote the 'friends' example to make a point - common, complex programs often have huge graphs of interconnected objects, whether one would like it or not. Your solution is to build a journal of friendships and breakups - it's not a typical solution for object relations - seeing if 2 people are friends is a linear operation. Keeping the history around might not be necessary or useful.
The example doesn't make a point. In either imperative or functional programming, the operations you need and their time and space complexity will dictate the data structures and algorithms to use. Saying that programs have "huge graphs of interconnected objects" is not evidence of anything specific.
> 3. Your FP code runs on a modern OoO CPU whether you like it or not - so it's safe to make that assumption.
Make what assumption exactly? Microcontrollers are in-order. GPUs are in-order. You also made claims that FP programs are full of pointers and non-performant data structures. I have no idea what out of order execution has to do with this claim. Certainly early FP languages were pointer heavy, but early imperative programs were goto heavy, so I'm not sure what exactly you think this says about FP in general.
> And multithreading might not be a silver bullet. FP does nothing to break long dependency chains.
FP encourages and often forces immutability which does help significantly with parallelism and concurrency.
> Another example is when I made a small toy Excel-like spreadsheet that supported formulas - while Excel itself is FP, I realized that the best way to update cell values, is to topologically sort all dependent expressions, and evaluate them one after the other - an imperative concept.
Prove that that's the best way, and not merely the best way you know of, or the best way available to your programming language of choice.
> No, that's a purely functional algorithm. Note how he's just adding numbers to the end of a list and emphatically not mutating the existing items in any way.
They are clearly mutating the piece of paper to add new numbers to it. There is no linked list in sight. They are also flipping the pages of the book in order, another imperative paradigm.
The closest you could come to a pure FP algorithm expressed in physical terms would have been "Say there are still pages I haven't looked at, and say I have a stack of post-it notes on the current page; then, I would check if the word cheese appears on this page, and if it does, I would produce a post-it note with the page number on it, adding it over the stack of post-it notes; I would then flip one page; on the other hand, if I am looking at the last page of the book, I would add another post-it note to the stack if needed, and return the whole stack of post-it notes to you otherwise".
Imperative:
foreach page in book
if 'cheese' in page
write(paper, page.number)
Of course, we could write this easier with a map and filter, but I don't think there is any good way to express those in physical terms (not with a book - filters have nice physical interpretations in other domains though).
Later edit:
A version of the imperative algorithm closer to what was described in prose:
repeat:
word = read_word(book)
if word == 'cheese':
write(paper, current_page_number(book))
if no_more_words(book):
return paper
> They are clearly mutating the piece of paper to add new numbers to it.
No, they are simply adding a new record for the word occurrence. If the paper runs out, they grab another sheet and continue. This is clearly an append-only ledger, just like that used in accounting. These are both immutable abstractions.
The fact that this is happening on a single sheet of paper is merely an optimization, it's not a property of the underlying algorithm they're employing. The post-it equivalent you describe is simply a less space efficient version of exactly the same algorithm. You're basically saying that tail call elimination makes FP no longer FP.
> There is no linked list in sight.
What do linked lists have to do with anything? You don't think that FP or immutable programming have to use lists do you?
> They are also flipping the pages of the book in order, another imperative paradigm.
Traversing an immutable sequence in order is now an imperative algorithm? Since when?
What's really happening here is that you've already assumed that physical reality and people's intuitions are imperative, regardless of the contortions required.
This example of counting words and the general ledger are perfect examples: it's absolutely crystal clear that all of the recorded entries are immutable and that this log of entries is append-only, which is a characteristic property of FP and immutable abstractions, and yet you have to contort your thinking into looking at the paper itself as some mutable state that's essential to the process in order to call this an imperative process.
> The fact that this is happening on a single sheet of paper is merely an optimization, it's not a property of the underlying algorithm they're employing.
The person is describing the abstraction, not any optimization. If you were to translate their words directly into code, you would have to write code that modifies the piece of paper, because this is what they described.
In contrast, when using a persistent data structure, the abstraction says that I create a new structure that is the old one + some change, but, as an optimization, the computer actually modifies it in place. The implementation is imperative, but the abstraction is functional.
> You're basically saying that tail call elimination makes FP no longer FP.
No, I'm saying that there is a difference between writing an algorithm using tail-call recursion, and writing it using iteration; even if the compiler produces the same code. The tail-call recursive version is FP. The iterative version is imperative. How they actually get executed is irrelevant to whether the algorithm as described is FP or imperative.
In contrast, you're basically claiming that any algorithm that could be abstracted as FP is in fact FP. So, `for (int i = 0; i < n; i++) { sum += arr[i]; }` is an FP algorithm, as it is merely the optimized form of `sum x:xs total = sum xs x+current; sum [] current = total` after tail-call elimination.
> Traversing an immutable sequence in order is now an imperative algorithm? Since when?
Immutability and FP are orthogonal. Append-only ledgers are a data structure, not an algorithm; and it's algorithms that can be functional or imperative.
On the other hand, yes - traversing a data structure in order is an imperative idiom. In pure FP, the basic operation is recursive function application, not traversal. For loops and tail-call recursion obtain the same thing in different ways.
> This counting words and the general ledger are perfect examples: it's absolutely crystal clear that all of the recorded entries are immutable and that this log of entries is append-only, which is a characteristic property of FP and immutable abstractions, and yet you have to contort your thinking into looking at the paper itself as some mutable state in order to call this an imperative process.
By your definition, I understand this is an FP algorithm for producing a list of the first 100 natural numbers, since it's append-only:
xs := []int{}
for (i := 0; i < 100; i++) {
xs = append(xs, i)
}
You can certainly define FP = append-only, but that is emphatically not what others mean by the term. Instead, most people take FP to mean "expressing the problem in terms of function applications (and abstractions based on them), where function == pure function in the mathematical sense".
> What's really happening here is that you've already assumed that physical reality and people's intuitions are imperative
I've actually shown what I think is actually an FP algorithm represented in physical terms, and how that translates 1:1 to FP code (and the alternative imperative algorithm). I don't think it's fair to accuse me of assuming that physical reality is imperative - at least not without showing your 1:1 mapping of the description to FP pseudo-code.
Edit to ads:
Perhaps a fairer representation of the imperative algorithm should have been:
repeat:
word = read_word(book)
if word == 'cheese':
write(paper, current_page_number(book))
if no_more_words(book):
return paper
This is even closer to the prose version, and makes it clearer that it was describing an imperative traversal.
>>> navigating all of the offerings, examining their trade-offs
>> the amount of time you're afforded for that exercise better fit neatly inside a very small window of implementation (in other words you're probably not going to do it, or at least do it justice)
Every project I have worked that has done a gap analysis has executed faster and smoother than those that didn't. A gap analysis being a more rigorous approach to an analysis of tradeoffs, solution capabilities vs requirements/existing software, and sometimes scoring/ranking (often with three recommended options depending on client's choices in tradeoffs).
>> the advocacy usually feels like someone trying to talk you into a religion
> advocacy among unbelievers is always gonna come off like this, especially when the evangelists have dealt with so many naysayers and the apathetic majority.
If an advocate sees unbelievers and naysayers around it is more a reflection of the advocate tactics. No offense intended. Most developers will gladly hear an overview of how a new technology can make doing X easier, better or faster. Even when there is a steep learning curve. They may not switch to it quickly or at all, but they are likely to form positive impression of it.
But if an advocate starts with "your tools are bad", "stop doing Y", "you need to unlearn Z" he is usually digging a hole for his technology, not advancing its adoption. If an advocate cannot produce powerful, convincing examples showing the goodness of his technology (without bashing existing tech) he should stop advocating and start learning. My 2c.
For the most part, agreed. But the arguments for FP are old and like you’re mentioning could be sold easily by the right orator (those teachers/evangelists exist btw in droves on YouTube and elsewhere). I’m only saying that given what academia and collective professional experience has proven around FP, it would be nice if FP or FP patterns were the first option(s) most stressed to budding SWEngineers/Developers and that imperative architecture was the afterthought (not the other way around as probably it is in our current state).
In other words, there would be little need to evangelize for FP if it was the base of understanding.
And while there are a lot that want to improve the field, I’d wager that most 9-5’s are tired, wanna finish a good days work and rest.
Let's say I run a CS department. And let's say that I've accepted that what my department is really doing is jobs training for software engineers. Well, what are their jobs going to be, imperative or functional? For most of them, for most of their careers, the jobs are going to be imperative. Why should we start with FP?
You could say "FP is better", but then we're back at the OP's point. FP is better for some things, and worse for others.
For you to say that FP should be the starting point, you need to show that FP is better for the majority of programming situations, or else to show that FP is the better way to learn programming for the majority of students. I have never seen anyone seriously attempt to show either one. (Some argue "it fits how we think better", but what they mean is that it better fits how they personally think, ignoring that different people think differently.)
It’s about rope. Imperative languages generally give you a lot of flexibility (for x or y) and, therefore, rope to hang yourself. I don’t believe that starting students here, from a pedagogical perspective, is a good strategy. FP languages/paradigms, on the other hand, are all about restrictions(immutability, side effects, etc), and thus less rope. Less places to hang yourself, so to speak.
Also, even though, as you’ve stated sw engineers tend to work in an imperative environment (which I’m arguing is an artifact of their formative years), junior sw engineers should at least start with a bit of trepidation to use that rope (if only in their heads).
Plus, utilizing a functional style (and understanding the whys of functional style, where pragmatic) would improve many aspects of industry (e.g reducing the friction of adding tests - did I mention that I love tests??)
This rope only matters for production oriented systems. Most programmers are doing quotidian processing tasks. Manipulating CSVs, processing data to get statistics on, plotting points on maps, maybe writing a simple automation. Almost every software engineering class I read about when I was a graduate student teaching undergrad classes spent time discussing the pitfalls of the "rope of mutability" and explicitly discussed the idea of immutability to make this kind of programming safer. I agree with another poster that it's just much easier to teach general programming skills and thinking procedurally. I do think that programmers have to unlearn some of this when writing production-grade software, but most programmers will never write anything like that.
the amount of time you're afforded for that exercise better fit neatly inside a very small window of implementation (in other words you're probably not going to do it, or at least do it justice)
>> figuring out which ones fit best to the system being built in terms of constraints
constraints will always get ya. It always ends up to being what is the tool/paradigm that you (and your project manager) are most comfortable with because you'll most likely use it in leu of anything else (especially given you are not the only one that has to be convinced -- engineers don't operate in a vacuum, and they love predictability, ie what they already know)
>> You won't get a very optimal solution by ...
YAGNI. Premature Optimization. KISS.
I am not saying that ^^^ is true, I'm just introducing you to your new conversation buddies for the foreseeable future. People always bring'em along for the conversation.
>>> trying to talk you into a religion
advocacy among unbelievers is always gonna come off like this, especially when the evangelists have dealt with so many naysayers and the apathetic majority. And this is probably the crux of the entire issue. Students' first languages in school are generally imperative languages. They are taught in their youth a path away from functional programming. Which is funny to me because my school was always promoting that their studies were there to help you grow one's intellect and not necessarily fit into some cog of the industry. But, I don't recall much playtime given to functional languages (not as much as they deserved at least).
My point is that it would be nice if FP was the first tool that was given to students that pour out of universities into developer teams. It would be nice if the easy button for a group was FP, not the other way around. Then it would be much easier to beat the other drums (like the testing drum).