Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
How to stop functional programming (brianmckenna.org)
225 points by zeckalpha on Feb 23, 2016 | hide | past | favorite | 122 comments


My methodology for avoiding this confusing “functional” programming:

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.


I love this so hard. I've been a dev for over a decade and just recently discovered functional programming (in a now undergraduate academic sense) and it has changed all my code. Things are so much simpler, easier to follow, and far far less code as I focus on data structure as state, and functions as things that operate on the data structures. Everything is just so much simpler now.


Won't pass code review till you can use dependency injection to pass a + operator mock for testing.


It doesn't even implement IIntegerOperation!


Ugh. Relies on mathematical assumptions that may not be correct in other realities.


-1; please add a logging/tracing call in your add method. Since that introduces impurity, make an algebra for effects you can do with logging, construct the program as a free monad in that algebra and evaluate it.


With great comedy, you never know if they're being serious...


This is one reason I hate our industry. No one engineers anything. They all just abuse OOP so that code is packaged together in some arbitrary heirarchy and then refactor it every single time a user story is added.


Not only that but there's this obsession(?) with doing things as quickly as possible. No matter what task it is on a story it is always, "should only take a couple hours". NO! Stop! You're training your managers to think that every task can be done in two hours! Give yourself some time to stop and reason about the code you're writing AND integrating into the larger system. Be patient. Be thoughtful. Be careful.

Now, I'm not advocating taking a 3 days to do this. But stop with the "couple hours" crap.


Do note that in a corporate setting, this obsession is primarily due to Project managers, Implementations, and even Sales who don't have a hand in the actual development expecting projects to be finished quickly, and to "just work."

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.


I think it's easy to blame others, but programmers are optimistic by nature and bad at estimating. Also, I think all of us have had the opposite scenario where a request comes in from a business-minded person that they think will take forever and it turns out to be trivial. It feels great to do a lot of good very quickly, independent of what the project schedule says.


This is so painfully true. Sprints are organized to have very little time for technical debt and refactoring. From time to time there is a technical debt sprint that addresses all the wrong issues.

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"


If we forced everyone to switch to Haskell, do you think this would really be any different? People would still do the bare minimum to implement the latest user stories, because that is literally what the boss is paying for.


We force everyone to switch to Haskell. OK. Even the fair-to-mediocre programmers? Yes. Who tend to program by copypasta and change stuff until it gets the right answer? Yes.

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.


I remember being a sophmore in college and I understood the basics of haskell (not really monads yet) and we worked in groups. My partner was very intelligent, but didn't have a grasp on the language at all.

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.


> You don't want to see what "design patterns" and urban legends these people layer on top of Haskell to make it intellectually manageable.

Do share examples if you have them.


I think the point is to not force any single paradigm. OOP is what is being forced today, but it would also be bad if the industry forced functional programming. Large programs typically call for multiple styles of programming. One piece may suit an imperative style, another functional, another logic/relational, another object-oriented, etc. In general, I think OOP is nearly always the wrong tool, though.


I'm in university right now and there is an object-oriented programming course (which is required by most other advanced courses), but no functional programming course. That shows how skewed the industry is. It's a pretty forward-thinking university as well.

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.


In Haskell? I'm afraid the bare minimum is the most abstract possible engine with business rules added on as data or high level function application.

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.


Are you saying you prefer example 2, in the comment you replied to?

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.


I definitely hate solution number 2.

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.


I'm having trouble understanding your definition of "refactoring", then.

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.


I'm getting at the idea of having to rearrange the code inside classes because your original assumptions have changed. Sometimes this involves moving stuff between classes. This is especially messy if the class has any member variables or relies on private functions.


I think you need to pass it a NumberFactory instead of this magic dot-dot-dot syntax, I can't read it.


Your first example is actually procedural. Needs more lambdas.


It never lasts long enough to get complex, it appears in a virtual functional/anti-functional pair and then they annihilate each other.

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


> Needs more lambdas.

I'll be using this in every code review from now on.


I'll need an interface for that so I don't have to read into the code to see what it does.


Your design lacks a `Factory` I think. You don't use design patterns enough.


It’s factories all the way down.


So if you have some object named Obj, and you have some interface for it, and you have a factory that creates objects matching that interface, and that factory has an interface, then what do you call the factory that builds implementations of that interface?

A.) IObjFactoryFactory

B.) IIObjFactory

C.) IIObjFactoryFactory

D.) An abomination upon mankind.

E.) IObjFactoryIFactory


JavaProxyEnterpriseBeanConnectorAbstractInterfaceFactoryImplementationMockup

or something.


Like "functional" programming can't be abstracted so it becomes way too generic.


But where's your specs?


I've had some success explaining, mainly through pair programming, code reviews and an educational use of the console, the basics of FP-ish constructs such as map, fold, filter, pattern matching, control of side-effects, etc.

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 :)


> such as map, fold, filter, pattern matching, control of side-effects, etc.

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?


Pattern matching is not just sugar for an if-elseif chain, since compilers will check for exhaustiveness. This is especially important when used with sum types (which are the primary motivator for this feature):

    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.


Aha, thanks for that bit, I didn't realize that. I thought Swift, which has a switch/case statement that does not have automatic fallthrough and which has to be exhaustive, was special in that regard. (Although in Swift you can in most cases just do a 'default' case too, not sure how that works with pattern matching)


This is why functional programmers get so grouchy - it's great that Swift has picked up this kind of functionality, but ML family languages have had it for 40 years now :/. Worse is when a feature gets dismissed as "incomprehensible" for years until it gets picked up by some fashionable language and then suddenly everyone's excited about it.


Pattern matching is awesome, and ML definitely gets the credit here for putting it into practice, but matching is arguably not even part of the functional paradigm. Lots of non-functional languages could greatly benefit from pattern matching and sum types.

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.


Rust is arguably a functional language? I'd love to hear the argument that says that it is...


> I'd love to hear the argument that says that it is...

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.


I wasn't actually looking to argue; I was looking to understand.

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.


The term "functional" today more-or-less means "a language like Haskell and the ML family" (I know this is not the original meaning), which Rust is.


I'm still trying to understand (not argue). How is Rust like Haskell and the ML family? My impression of Rust is that it's like C++ but with clear tracking of who owns what memory.

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


It's immutable by default, with mutation only when managed (most famously a Haskell thing). It has a rich type system with ADTs and generics but without subtyping (very much the ML/Haskell tradition). It distinguishes between validation-like failures and system-level errors, handling the former with values and the latter with panics (i.e. noncaught exceptions) - Erlang is the most famous example of this style, but it's common in Haskell and ML too. It tries to push functionality into libraries rather than the core language - it seems to care about having sensible, consistent semantics rather than ad-hoc.


It really depends on what you think are the features of Haskell that typify its "family". If you think it's typified by things like type inference, sum types, and typeclasses, then Rust will feel very Haskell-y, but if you think it's typified by higher kinded types, laziness, and functional purity, then it won't feel very Haskell-y at all.

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.


I think the life cycle of resources issue was a big reason why I was surprised to hear people say that Rust was Haskell-like.


I'm in the "no" camp, personally, but we _did_ inherit a lot of things from OCaml. The initial compiler was written in it, a lot of the syntax and features are inspired by it, and it just _feels_ more ML than Haskell.

Rust may not be a functional language, but it draws heavy inspiration from them.


In other languages with pattern matching you often have some sort of wildcard that you can match against. Either an explicitly named variable or, commonly, an underscore (`_`) if the value is unused. Example in erlang:

  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.


True in Haskell, but not e.g. in Scala. In Scala, pattern matching is done using partial functions that are defined only for certain patterns. E.g. it's totally legal to write:

  obj match {
    case Foo(x) => ...
    case Bar(y, z) => ...
    // no Baz here
  }


In Scala you get the compiler to check for exhaustive pattern matching if you use case classes and sealed classes. So if you want exhaustiveness checks, you can have them.


Not entirely true. You would get a warning if its not exhaustive matching for a sealed trait and you can make warnings into compiler errors with a compiler flag.


> since compilers will check for exhaustiveness.

That's not really required for pattern matching. Certainly makes it more useful, and most implementations have it, however.


Pattern matching isn't just conditional matching. It's also binding, and even some common operations. Compare a naive Haskell map:

  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.


Just curious why you would use that imperative example with Racket, and not an imperative language like Python, or even Haskell? You parenthetically mention (match) (ha!) but to someone who doesn't know the languages, give the impression that Racket can't do pattern matching.

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.


I was largely using Racket as a common enough Scheme that I have installed my computer. I wanted an example from another functional language but in an older classical style (I was shooting for the Little Schemer here). I'm quite familiar with Racket's match, somewhere around my blog I even have a "proof" of sorts of using Racket's match to do the famous Haskell quicksort in just as few lines of code.

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


That wasn't quite a fair comparison.

    map f xs =
      if empty xs
      then []
      else f (head xs) : map f (tail xs)
(I like pattern matching too.)


The point isn't to be a "fair" comparison, but rather to compare the number of explicit operations involved. I'd note that your Haskell example there is largely equivalent to the Scheme one.


Yes, it's equivalent on purpose. Yet in Haskell it's only a couple tokens longer than the pattern-matching original which you called "far more terse". To fairly test a claim, one should change only the variables the claim is about.


The benefit I see is that (at least in Erlang) pattern matching combines pre-condition asserts with programming the sunny path. For example,

   "/" ++ 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());
  }


Right, but how do you do that if your coworkers are just as against communication, and decide to go straight to the manager to complain without asking how the code works first?


That's a pretty bad situation, agreed. I think it exceeds the scope of what programming constructs/paradigms to use, and is instead a way more serious problem. With this kind of coworkers, I predict everything is going to be a problem.


I have never seen a manager complain about the use of functional programming.

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.


FWIW I have had a manager complain about it.


Isn't that a bit like hiring a sniper and then sending them into battle armed with a nerf gun? ;-)


Probably more like sending them in with an AK-47. Each is great at what it does, but you will fail if you assign a person trained with a different weapon to the wrong task, and ask them to use an AK47 like a sniper rifle, or vice versa.


Well, if the functional code people are complaining is on the same level as the one the author used on the post, then I agree that a quick pair session is enough to "solve the conflict".

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.


We need more reasonable people like you :-)


Sounds like the author is sharing a personal story here. I'm guessing he wrote some FP code and one his coworkers complained about it.

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.


It's amazing how you can tell so much about a person just by reading one short article. To me, your advice comes across as condescending. Maybe you need to work on your communication skills too?

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.


Brian McKenna is a public figure who people could easily have encountered in other ways.

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.


An advantage of using the most general signature possible is it restricts the things you can actually do with the values involved, which can make it easier to provide correct implementations.


Exactly. More generally, this is what code reviews are for. On top of the personal issues that might be occurring there, it looks like that team doesn't do code reviews.

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.


> To me, your advice comes across as condescending. Maybe you need to work on your communication skills too?

And now we've come full circle


> It's amazing how you can tell so much about a person just by reading one short article.

I take it you never heard of Brian McKenna before?


I've found usually if you cleanly explain what you're doing and go through some examples people are willing to learn. Not sure if I've been lucky in that regard, but I've never had the problem this guy clearly has before, with integrating functional ideas into code.

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


This satire is more sad than funny, because it's true. A manager's job isn't to understand good code, it's to understand business and to know how to effectively get rid of road-blocks for programmers on the team. But how can they make decisions about how to make progress easier/smoother for every programmer, if they don't understand programming?

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.


There is an even better approach than trusting the most senior that will have a longer term payoff. The dispute is usually caused by a lack of people skills in one or both of the parties. If you can instead take this as an opportunity not to resolve a pokemon dispute but instead to teach dispute resolution skills to both then you don't need to know enough pokemon to resolve it. And the next time a dispute comes up around something you don't have enough information to be authoritative about the involved parties might be able to resolve it themselves.


Adding two numbers together without being pure? Isn't that how we always do it?

    MOV edx, 1
    MOV adx, 2
    ADD eax, edx


Does that actually work?


Welp. The code would add the user himself as his own coworker in both versions.

I'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


I sense a little bit of a condescending attitude in such posts, and that does not help 'non-fp' progs to want to join the super world of fp elite programming. Personnally I've started learning Haskell and I love it. But having a denigrating attitude towards OOP-ish programming is simply polarizing and does not help the 'cause' ...


I realize this is mostly frustrated satire - but it is an actual issue when working with coders of different skill levels.

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.


It is counterproductive to cripple the programmers on your team who can handle abstraction to avoid confusing the ones who can't. Why turn 10x coders into 9x coders or worse just to retain your 1x coders? Or work with the ones having trouble with a pattern until they get it?


That's the management decision in the article. And I make the same decision. We're not an organization that attracts nor hires rockstars (or, as you say, 10x coders). We have a couple really sharp folks but are predominately staffed by "1x" coders. Therefore, it is important and the responsible thing to do to write all code so that it can be understood and maintained by a 1x coder.

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.


I have to ask, where do you draw the line? I had a team lead that disliked me writing things like (in C#, been a bit so forgive errors):

  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?


Yes absolutely. I don't say "no functional constructs" nor does anyone else here that I know of. However, composite LINQ statements, especially involving aggregation can and do become confusing. Obviously it's a judgment call but in many cases, breaking out a complex expression into incremental, imperative steps can and does make it clearer.

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.


Why would avoiding "map" suddenly decrease the ability of your coders? It's not like writing out the equivalent loop takes that much time.


It's easier for a bug to slip in, like an off-by-one error, when one chooses to write a loop rather than invoke a well-tested abstraction that hides the loop, leading to more time spent later in the development process debugging and fixing the problem.


Yeah, code that anyone can read is so overrated. PS: Sarcasm.


> a for loop can be understood by almost any programmer you will encounter

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.


I would agree with you if we were talking about more complicated features like monad transformers or monoids or something like that, but list operations like this are very high-leverage, not that complicated under the hood, and becoming part of the standard required knowledge for most programming ideologies. (Eg. Java has recently embraced them in Java 8, Python has them, etc)

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.


What order do you expect the output to be in when you run flatmap? In doing research on it, I've seen at least three distinct orders. One, results are interleaved. Two, results are depth first. Three, results are breadth first.

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.


Hmm I would expect the results to be based on the data structure holding the values you're flattening. An in-order depth first traversal if the data structure you're applying it to has an inherent order (e.g a List) and in an undefined order if it's something unordered like a Set.

  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)


I would agree, but have a look at this:

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]


If I cared what the order was, I'd expect that I'd have to order it myself, e.g.:

  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.


"you turn a 4 line for loop with accumulators into a single line of code"

And dear heavens, achieving more with less code is the last thing any respectable developer would want.


If it's at the price of clarity, yes, that is exactly 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?


I believe "turning a 4 line for loop with accumulators into a single line of code" increases clarity for all of the above. Do you disagree?

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.


In the real world, I've got a team that, while all of them could learn to read that one line of code, I don't think any of them can read it now.

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.


Get a decent static analysis tool for their IDE, that points out places where they can replace a gommy, ugly loop with a higher-order function.

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.


If they're new enough to not understand that map, et al are using loops internally, then they're probably too new to be working an actual job. They might never have been exposed to it, in which case an explanation is warranted, but if they still don't get it, maybe they're not cut out to be a programmer.


I think a lot of the abuse happens in Java, and java-derived languages. There is a culture,and the more lines of code you have , and the more classes you have , the better. The language environment and frameworks only re-inforce that stuff. Scala is (I guess ) functional - particularly when contrasted to Java.

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.


This is where I think LINQ does a good job.

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


It makes a lot of sense in .NET land, because it maps to SQL, which people tend to have some kind of grasp on.

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.


Or people could just learn the correct terminology.


s f g x = f x (g x)

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


You don't need if-then-else, you can emulate it by applying k and (s k) (which can represent true and false).


Wait, am I missing something? Isn't the "non-functional" code here wrong? There seems to be no filtering for only those departments that belong to the user's business, unlike the functional example where it uses "user.departments"


What language does the article use for examples? I don't recognize it.


Scala


Oh no problem.

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


One way to solve this problem is to show them this article. I'm bookmarking this in case the situation ever pops up.


I can understand that concepts like flatMap, merge, reduce and zip can be intimidating at first. It would help to explain what they are and how they work with smaller examples outside of project code base.

https://jsbin.com/bemasodemi/edit?js,console


One argument I'd like to use at one point is to explain a for-loop and what it does - intimidating? How about initializing a numerical variable, then creating a loop that checks the value of said variable, increments (or decrements), and in case of array operations, attempts to get an entry in that array in each loop.

Versus for-each-entry-in-list-do-this.


Be pragmatic, there's a higher chance that a developer has used said concept than the FRP mentioned above.

Funny you should mention that for loop as it's been removed from Swift 2.2 ;-)


Apparently this submission is flagged and sinking. In addition to being bureaucrats, the OO hordes are apparently without humor, too. Or perhaps one goes with the other.


It's not really that funny.


Worse, there doesn't seem to be a point. It sounds like a Haskeller's rant over how OOPers don't understand basic concepts. And, to be fair, a lot of them don't, can't count how many times people have mistaken static typing for object-orientation.

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?


Over 200 points and on the ninth page, when older stuff with less points is on the front page. Hmm...


I feel like there's potential for a "How to stop being a smartass" rebuttal article here.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: