How to stop functional programming
brianmckenna.org
brianmckenna.org
1. Write:
function add (a, b) { return a + b; }
2. Refactor to object: class AccumulatingIntegerWidget {
constructor (...numbers) {
this.accumulation = 0;
this.accumulate(...numbers);
}
accumulate (...numbers) {
for (const n of numbers) {
this.accumulation = this.accumulation + n;
}
}
getAccumulation () {
return this.accumulation;
}
}
Presto! It’s stateful and object-oriented and handles future cases like adding three numbers, even though WeArentGonnaNeedIt!If this does not satisfy, I progress to making my classes observe each other and update themselves automatically. “We’re doing two-way binding!” I exclaim proudly, while leaving debugging as an exercise for my colleagues.
Only in the presence of a powerful gravitational force, like the edge of a corporate programming style guide, will one of the pair be sucked into the maw, leaving the other to accumulate cruft.
For example, the second can incorporate two-way binding as noted, logging, synchronization via Operational Transforms, and so much more...
I'll be using this in every code review from now on.
A.) IObjFactoryFactory
B.) IIObjFactory
C.) IIObjFactoryFactory
D.) An abomination upon mankind.
E.) IObjFactoryIFactory
or something.
OOP is sometimes the right tool, but most times it definitely is not. It's important to be able to know your tools and choose the right one. The current situation is disappointing though.
Run away. Run far, far away. You don't want to see what "design patterns" and urban legends these people layer on top of Haskell to make it intellectually manageable.
For the final project, we had a problem that I didn't understand well, but he couldn't use the language effectively. We ended up having him work out the problem incorrectly in a shared notepad and I would refactor his broken code into something that worked. It was a mess. I'm pretty sure I've seen worst things you can possibly build in haskell -- and they are bad.
Do share examples if you have them.
But I'm not really disagreeing. It's just that those people will spend months and months on some stupid repetitive concrete code, while the smart way would take a week, and the OOP way would take 3 weeks in Java.
Now, I'm not advocating taking a 3 days to do this. But stop with the "couple hours" crap.
When these groups hear the words "Agile Development" which was a common buzzword in recently past years - they take it literally - "moving quickly."
I completely agree, and it is often the cause of absolutely unnecessary bugs, stress, inefficient code, and developers often precipitate this bad obsession. But it comes from, and is ingrained from, somewhere else.
But there is plenty of time to endlessly maintain crap code that should simply be rewritten. Plenty of hours to endlessly debug hard to debug code, for that time is not an issue. But to rewrite something that really needs to be rewritten, there is no budget for that.
In the end it takes much more time to maintain bad code than to rewrite it, the users have bad user experience and there are endless bugs.
Software factories are agile only in the view of agile consultants. This much process where developers are basically managed like assembly line workers is the antithesis of agile: agile should be about collaborating together to solve a problem with very little process: "Individuals and interactions over processes and tools"
In Java-land, this seems to be the kind of thought process referred to as "engineering". Read the code in any random Spring project, for example.
Personally, I prefer writing simple code for the job at hand, and, yes, refactoring when I find two or more places where the same code can be re-used. And even though I develop mostly in Java, the most "functional" solution is generally the one with the least complexity, and easiest to debug.
One major problem I find with OOP is that it is often requiring refactoring. Every time you shove something into an object you actually take away from it.
One thing that greatly inspires me from Haskell is that it does have design patterns, but they are based on category theory. Math is the ultimate tool. I don't think we can accidentally "create" something as richly descriptive for describing the benefits and problems for using a certain pattern.
When I refactor, it's almost always to eliminate repetition and make the code more concise. But doesn't the same thing happen in a functional program? If you find the same code twice, refactor into a single function.
It's amazing what you can do if, instead of assuming an arrogant attitude, you take your time to teach other programmers and review their code to explain how to avoid pitfalls, and accept that some people will remain unconvinced about some aspects of FP that you cannot adequately justify (a good reason to seek a better understanding of those justifications!). And it has the added benefit that this situation will never reach your manager, so he/she doesn't have to get involved :)
map and filter have become natural parts of my thinking, fold too is getting there, and avoidance of side-effects even gives pleasant tingles to the math-guy in me, but I've never understood what pattern matching brings to the table. In my admitted limited understanding, they seem to be a syntactic sugar for if-elseif branching that ends up scattering the logic of a function to multiple blocks of code, while bringing no obvious benefit compared to them.
Since you mentioned your success explaining the justifications for these things, I thought I'd take advantage of that here. :) Could you explain what I'm missing with pattern matching?
data Thing
= Foo String
| Bar Int Double
| Baz String String
case thing of
Foo s -> putStrLn s
Bar i f -> -- ...
Baz s1 s2 -> -- ...
If you later add another variant to Thing, the compiler will check that all pattern matches against it account for the new case. (Wildcard matches alleviate the need always to handle every case.) An if-elseif chain, however, would still compile after adding the new variant, so you must track down the usages manually.And indeed, when you look at a language like Nim and Swift, that's exactly what happened. Rust, too, although Rust is arguably a functional language.
[Edit: I see that the link from jnbiche is probably going to answer my question, though it's going to take me a while to digest it...]
Personally, I think Haskell and C++ are the two biggest influences I feel when using Rust: it has a lot of Haskell's philosophy around data types and polymorphism, and a lot of C++'s philosophy around the life cycle of resources, and the cost of abstractions.
Rust may not be a functional language, but it draws heavy inspiration from them.
http://science.raphael.poss.name/rust-for-functional-program...
I personally think Rust isn't functional, but rather multi-paradigm with a strong functional influence. But if you were to add TCO and a few other features, I think Rust would be a full-blown functional language, so it may get there yet.
In any case, it's useless arguing over (should have known better than say anything), so this will be my last word on the issue.
And I guess it's going to come down to "whose definition of FP are we using?" If FP means "I can write in a functional style", there are many more functional languages than if FP means "I can only write in a functional style". That debate - about whose definition of FP is correct - is one that I can't bother to care very much about.
foo(bar) -> baz;
foo(X) -> bang.
The X is an explicit variable that will match anything not already matched in previous patterns. It can also be used. (NB: Erlang pattern matching is the same or essentially the same for this discussion at each possible level, whether in a function declaration as above or a case statement or when receiving messages.)Here, we don't care about the thing we've named with an underscore:
tail([_|Tail]) -> Tail.
In Erlang, `[1|[2,3]]` is equivalent to the list `[1,2,3]`. So `[H|T]` as a pattern would have `H = 1` and `T = [2,3]`. Since we don't care about the head of the list, we name it with _. Optionally, in Erlang, you can give it a fuller name: tail([_Head|Tail]) -> Tail.
The compiler knows that _Head is supposed to be unused and won't complain (warning about unused variables). tail([Head|Tail]) -> Tail.
Would give a warning.That's not really required for pattern matching. Certainly makes it more useful, and most implementations have it, however.
obj match {
case Foo(x) => ...
case Bar(y, z) => ...
// no Baz here
} map f [] = []
map f (x:xs) = f x : map f xs
To a Racket one made without using (match...): (define (map f lst)
(cond
[(empty? lst) '()]
[else (cons (f (car lst))
(map f (cdr lst)))]))
The former is far more terse, and also more directly resembles a definition of what map actually does. The latter is wordy, because all kinds of things have to be stated in explicit terms, and operations need to be performed manually instead of with simple destructuring binds.This is a mild example too, it gets far, far more useful when you're dealing with type/struct fields and the like.
map f xs =
if empty xs
then []
else f (head xs) : map f (tail xs)
(I like pattern matching too.)Anyway, this is the analogous `map` in racket:
(define (my-map f lst)
(match lst
['() '()]
[(cons hd tl) (cons (f hd) (my-map f tl))]))
It's a 1-to-1 correspondence, and is arguably the idiomatic way to define `map` in Racket.If you really want an ugly rendition I could try and replicate one of the awful JavaScript versions of "map" that uses a for loop, but I thought that might obscure the comparison of operations too much, and the semantics wouldn't be equivalent (linked lists vs. JS arrays).
"/" ++ X = HttpRequest:get(path).
will crash if PATH_INFO doesn't include leading slash. Writing just the sunny path and getting asserts "for free" (without any clutter) is a fun way to program.I also find that statement very readable.
BTW, here's some Java code that does the same thing:
String Path;
if (HttpRequest.getPathInfo().startsWith("/")) {
Path = HttpRequest.getPathInfo().substring[1:];
} else {
String emsg = "no match of right hand side value \"%s \"";
throw new IllegalStateException(emsg.format(HttpRequest.getPathInfo());
}What I have seen is amazingly intelligent programmers with FP backgrounds (who I pushed to hire) try to write in languages that are sort of FP (ie Java and Javascript). They end up writing code that is not canonical (which I don't mind) and using academic/haskell variable names (ie single letter variable names which I do mind heavily) and then fail to write any documentation (anecdotal observations).
I'm not saying this always the case but I have seen it quite a few times.
But IMO there is problem when (assuming Scala here) you include a library like Scalaz on the codebase, and then start using more abstract functional concepts. While I think these concepts are amazing, I usually don't use then on my day job as I know that my team don't have the knowledge nor the timing to quickly grasp such concepts without impacting the project's pace.
OP, two pieces of advice:
- Ask yourself why your coworker went to your boss instead of talking to you directly
- Work on your communication skills with your coworkers. I'm guessing you have an attitude that's rubbing them the wrong way (I've seen a lot of this coming from people who are trying to impose advanced techniques upon a team that's not ready for those).
It's startling how so many bright programmers possess so little social awareness.
Regardless, what's the practical solution to managing complexity in a team "that's not ready"? At some point, most projects get complex enough that you're going to have to do _something_ that someone is going to find confusing. It could be functional, OO, whatever.
If you've got complexity to manage and can't use reasonable tools to manage it, you may have bigger problems. Pandering to the hypothetical lowest common denominator developer isn't going to help much, because they're probably not going to "get it" anyway and will likely make a mess in any case.
And now we've come full circle
In my experience many functional programmers, myself included, do generalize where they don't need to (e.g. writing a function to accept any F[_]: Foldable when it's only ever called with List). At which point asking them to justify the abstraction is entirely justified. Conversely in cases where fancy functional techniques really are improving maintainability it's usually pretty easy to demonstrate this by comparing two implementations side by side.
Code reviews are not just to make sure the code that goes in is sound, they also help getting multiple team members familiar with the entire code base.
I take it you never heard of Brian McKenna before?
I've also found that quite often I actually am at fault because it is reasonable to want to break up dense code into smaller understandable blocks, and while you _can_ write an entire function (or application) as one statement in an FP style, your job isn't to show how cool you are, it's to write code that your peers can maintain.
_(Like that last paragraph. It probably should have been more than once sentence. We live and learn.)_
It's kind of like when my kids are arguing without being able to solve it on their own, and I hear it from my office, and have to go over there to potentially solve the problem for them. What if they're arguing over a dispute over Pokeyman cards? I don't know anything about Pokeyman, and I can't learn well enough to give helpful advice or make firm rules on the spot.
It seems like the best solution in both cases is to trust the person who's most "senior", either the senior developer or the oldest kid or whoever, to make a judgment call to know what to do here. Probably the other programmer should be brought up to speed on how that code works so when they encounter it next time, it's not confusing.
Is that always practical or even possible? Probably not in a large corporate setting. But in smaller companies, I'm confident it probably always is. Of course, so long as the code isn't intentionally clever. As long as it's at least trying to be clear what it does, that should be good enough. But intentionally clever code should almost always be rewritten, because it's usually so clever that even the author can't understand what it does in 6 months' time.
MOV edx, 1
MOV adx, 2
ADD eax, edxI'm a Java programmer by day, but i have definitely learned a lot by writing some "functional" (i.e. in a "functional" language) code as well. But this is just concepts. You can also take those concepts and write clean (and not too bloated) OO code. I really don't get what all this exaggerated "functional programming" vs. "bad object-oriented enterprise code" ranting is good for
The solution is to simply drop the use of flatmap (or any other function which eliminates loops). In my experience, that level of abstraction is the kind of thing which confuse new developers who have not (yet) been exposed to the terminology introduced by functional programming. It comes across as "clever" and "obfuscated". And it really is quite clever - you turn a 4 line for loop with accumulators into a single line of code. But it's also legitimately obfuscated as well, until you grok how flatmap works.
Ultimately, a for loop can be understood by almost any programmer you will encounter. There's more code, it does the exact same thing as a flatmap, but (as the OP points out) the function is still pure.
Even if it is being written and currently maintained by one of our sharper folks, people move on or change roles, and projects shift to other staff for long term maintenance. Who's going to be maintaining this project in 2-4 years? This project needs to be written with them in mind.
List<AClass> original; // perhaps as a parameter to the function
List<AClass> filtered = original.Where(x => x.Field > 10);
Arguing that I should instead have just used for loops (showed him foreach and he agreed that that was better, at least) like: List<AClass> filtered = new List<AClass>();
foreach(x in original)
if(x.Field > 10) filtered.Add(x);
Ok, so it's only two lines longer (three if you split the if and its effect, 5 if you also add in braces, 7 if you put opening braces on their own lines). The `Where` version is not that complicated, and only requires understanding one (potentially) new concept for programmers, namely anonymous functions.Wouldn't the briefer, but still clear, code be preferable for maintenance?
The "rule" I apply is "code should be written primarily for people to read and only incidentally for machines to execute" which, although very subjective, provides a good foundation for everyone to start from.
And dear heavens, achieving more with less code is the last thing any respectable developer would want.
The problem is, clarity for who? For the developer? For another, equally skilled developer? Or for the developer's less skilled coworkers? For the least skillful coworker? To whom does the code need to be clear?
If you have a coworker who doesn't understand it, pair them with someone more senior until they get up to speed. If they are simply cognitively incapable of understanding the concept of mapping a function, in my opinion they are not capable of becoming a good developer.
Yes, there are times when reducing lines of code reduces clarity. But in my experience they are rare and exceptional. Far more real world code lacks clarity due to verbosity than from being overly terse.
Sure, they could be taught. But they're busy being taught other things - Android, say, or the latest JS framework. So there's an opportunity cost to teaching them - it takes away time and attention from teaching them other things, things that may move us closer to our business goals. So in practice, yes we can teach them, and no we won't.
And now that one line of code can decrease clarity for the team.
In my opinion and experience, Resharper is the easiest way to learn LINQ, before you even start looking at all the other dumb, inconsistent and error-prone stuff that people will do because it compiles, blissfully unaware of all the foot-guns it contains.
Your logic about an abstraction being "legitimately obfuscated" "until you grok" it could apply to nearly every advancement in programming, and needs to be qualified with some notion of why you've decided to apply it to the layer of abstraction that you have.
If the final order of the output is important (and it frequently is), you have to understand not only "map, then flatten", but the order in which the flatten occurs. And that appears to be implementation dependent.
List(List(1,2), List(3,4), List(5,6)).flatten -> List(1, 2, 3, 4, 5, 6)
Set(Set(1,2), Set(3,4), Set(5,6)).flatten -> Set(5, 1, 6, 2, 3, 4)http://reactivex.io/documentation/operators/flatmap.html
Granted, this refers to a higher level abstraction on a sequence of realtime events, but as we delve more into green threads and lazy evaluation, these kinds of solutions will become more common.
I really don't believe that there is no obvious way to guarantee the ordering of a given flatten command without looking into its implementation. Of course, I'm admittedly partial to Python's solution - use an explicit list comprehension:
>>> g = lambda i: (i, -i)
>>> [x for y in [1, 2, 3] for x in g(y)]
[1, -1, 2, -2, 3, -3] var foo = things.SelectMany(thing=>thing.SubThings).OrderBy(subthing=>subthing.PropertyICareAbout)
Particularly if things is some interface that I don't know the details of, like IEnumerable, ICollection or IDictionary.Because at some point that programmer learned how a for loop works. Programmers are smart people; they should learn how flatMap or any other abstraction works. Abstractions are the tools of the trade. Imagine a mechanic that didn't use power tools because he/she didn't understand them.
In his example, I suspect he is conflating that one-liner chained function call with functional programming? Sort of like how coders like to use ternary one-liners especially when they first learnt them (and end up with unreadable code). But his first snippet is obviously simpler, compared to the OOP-cruft of the second and third snippets.
As more Java programmers migrate to coding in Python, they are bringing that toxic culture in to Python too - where boilerplate and sugar were relatively less.
Its like everyone is doing rocket science, which is despicable, given we are mostly coding CRUDs.
Any language is susceptible to abuse, its also a human trait to de-simplify something to gain a fake intellectual satisfaction.
var coworkers = departments.selectMany(department => department.employees);
Nearly equivalent to the Scala code, but uses more approachable keywords like `select`, `selectMany`, and `where` instead of `map`, `flatMap`, and `filter`.Although that LINQ expression syntax can go die in a fire. It's a great way to end up with people using borked up joins in Linq-to-SQL and EF that don't work the way they think they do, rather than the actual foreign-key relationships.
k x y = x
b f g x = f (g x)
c f g x = f x g
y f = f (y f)
cond p f g x = if p x then f x else g x
fac = y (b (cond ((==) 0) (k 1)) (b (s (*)) (c b pred)))
fac n = result (for init next done) where init = (0,1) next (i,m) = (i+1, m * (i+1)) done (i,_) = i==n result (_,m) = m
for i n d = until d n i
Versus for-each-entry-in-list-do-this.
Funny you should mention that for loop as it's been removed from Swift 2.2 ;-)
But this isn't the way to point that out. Should we be surprised that a modern multi-paradigmatic programming language can be used to code in a functional style?