• Edd (unregistered)

    Get only the frist comment

  • WTFGuy (unregistered)

    As much as I like the power of C#'s functional code or the "fluent" stuff underlying so called query comprehension, I always wonder about mistakes like this.

    Back in the day they talked about designing the Framework and C# so devs naturally fell into the "pit of success" rather than the all too common pit of mistakes awaiting a dev in C & C++. And for sure they succeeded pretty darn well in the early versions; it was often easier to write good and correct code than bad or incorrect code.

    But these power tools are definitely a two-edged sword. Writing code the old way that performs badly by having a bad design that's O(N^2) or O(n!) by re-doing the same work over and over used to take 2 or 3 screenfuls of typing and half-dozen state variables to keep track of what you were doing. Somewhere along the way most folks would get lazy and said "This is too hard; there's gotta be an easier way." Then realized they could simply iterate over their list once calling e.g. DoStuff(myList[myElementItemNumber]) and be done with it. Or somesuch.

    Now it's less typing and far less thinking to create abominations like today's example.

    My bottom line: It sorta works because the dev sorta thought about solving the problem they sorta understand using tools they sorta know. That way lies endless WTFery. And really encapsulates modern commercial software development in a single sentence if I do say so myself. Whether that's ISV or in-house dev.

    Damn kids these days! Harrumph! In my day we had to make our own punch cards, beating the paper pulp on a rock by the river to dry it out enough to punch.

  • (nodebb)

    This is the first ever escape of fuctional programming approach.

    Addendum 2021-05-27 07:35: Example*

  • Brian (unregistered)

    My coworkers might be surprised to hear me say it (I've sort of gained a reputation as a LINQ wizard), but there are times when a good old foreach is the better choice. This is definitely one of those times.

  • SMQ (unregistered)

    Oh ffs.

    private void HandleProcessPluginChain(ProcessMessage message, List<IProcessPlugin> processPlugins) => processPlugins.ForEach(plugin => plugin.Process(message);

  • (nodebb)

    This seems like yet another case of developers forgetting all of history and reinventing bad wheels. C# got multicast delegates back in 2003 with 1.1.

  • witchdoctor (unregistered)

    I've done something where the tail call is not a callback but instead goes on an event queue. The point was to attach reading incoming elements from a synchronized queue to the GUI thread of an application without blocking the GUI. But that's very much not what's happening here and a really specific special case that merits a pretty long explanation in comments.

    This reads like something someone who's seen something similar without understanding it would do.

  • TheJayMann (github)

    Given the code as presented, I can see really only two advantages over the much simpler design of enumerating the plugins and passing the given message to each one in order.

    First is that it allows an earlier plugin to pass a modified version of the message without altering the original message itself. In this case, from a functional design perspective, it makes more sense for the plugin method itself to then have a return value (rather than being void), and returning the modified message. This then very nicely fits in to the fold functions that most functional languages have, and even C# has in the form of Enumerable.Aggregate. At this point, the entirety of the handling method becomes processPlugins.Aggregate(message, (msg,plugin) => plugin.Process(message)).

    The second is that a plugin can decide to terminate further processing of the message by not calling the continuation. In this case, I would still defer back to making use of the return value rather than passing in a continuation, having the method return a value, such as true and false or possibly even null and not null, to indicate whether the next handler should process the message.

    Assuming neither of these are the case, then use of continuations is quite unnecessary. Functional languages in this case will typically encourage a recursive approach over a continuation approach. Also, this example is the reverse of how continuations are typically developed in functional languages. In the provided example, one source is creating a continuation, with multiple methods accepting the continuation. In typical functional languages, one function will typically accept a continuation, with multiple sources creating the continuation.

    To me, this does not appear to be someone used to functional programming forced to use a different language, but rather as someone who recently took an "f-pill" and is now trying to implement these new concepts wherever possible without fully understanding the consequences.

  • Sole Purpose Of Visit (unregistered) in reply to WTFGuy

    I'm gonna (sort of) defend it, again.

    The crass mistake is to work with List, as though List is the equivalent of what you would expect from an FP list. Obviously in C# you should recurse on an enumerable. (Always!) And equally obviously, any outward facing method should accept an enumerable.

    The "possible" mistake is, as Remy states, to assume tail recursion. As an aside, at least the OP avoids trampolining, which would work but would be ugly.

    However, on the (unverifiable) assumption that there are no more than 255 plug-ins for any given case, nobody is actually going to care. And with a halfway sensible testing system, you're going to notice when this breaks before it goes into production.

    Obviously I wouldn't implement the chain this way. There's no good reason not to iterate through the list/enumerable. But then again (and given the constraints mentioned above), it does its job. And is eminently testable, given the fact that it doesn't introduce side-effects. And if an F# programmer comes on to my team and wants to write methods like this, I'm hardly going to complain -- because I'd rather have a wide base of language expertise on my team than somebody who knows everything there is to know about BrainFck and nothing else.

    I'd suggest that in any given code-base there are going to be a lot more, serious, WTFs than this one.

    • Although to be honest I'd hire that guy just for entertainment value.
  • Sole Purpose Of Visit (unregistered) in reply to SMQ

    In general, avoid ForEach. It's an invitation to side effects. Not relevant in this instance, I know, but still a good rule for us all.

  • Guester (unregistered)

    A Haskell programmer wouldn't write anything like that.

    head and tail (the Haskell equivalents to car and cdr) are huge red flags that call out the location of something being fragile and probably already broken. You should either be pattern matching or using better data types.

    And even then, you don't write things like this in application code. Manual recursion is GOTO. You might use it to implement the occasional higher-order combinator manually, then you use that combinator instead.

    But most common recursion patterns are already in libraries, so you'd just look up what existing thing already does what you want first. And... That's exactly what the code in question didn't do.

  • (nodebb)

    In the late 80's I worked as a sysadmin for a company that created one of the first massively-parallel computers. All the software engineers were Lisp programmers, since AI was the target market. I inherited the Unix in-house tools, and they looked like C written by Lispers -- lots of linked lists, and IIRC macros named CAR and CDR.

  • (nodebb)

    The only car/cdr operation I care about is burning a playlist for a road trip.

  • (nodebb) in reply to Sole Purpose Of Visit

    And with a halfway sensible testing system

    YMBNH.

  • Nel (unregistered) in reply to Sole Purpose Of Visit

    Precisely that's the point of it, it returns void so it either does nothing or has a side effect.

  • erik (unregistered) in reply to Sole Purpose Of Visit

    I am not convinced that recursing on the enumerable is that much better.

    Sending processPlugins.Skip(1) means that you are chaining the Skips together. So it becomes processPlugins.Skip(1).Skip(1), then processPlugins.Skip(1).Skip(1).Skip(1) and so on.

    IIUC then each Skip would set up its own enumerable, with its own enumerator, on every call.

    In each iteration you would navigate past all those skips twice. (Once for the Any call and once for the First call).

    This is better than calling .ToList(), but both approaches is O(n^2). (ToList because it copies the list, and just Skip() because it traverses the skips on each iteration).

    The better approach is to pass the Enumerator.

  • (nodebb)

    "that every call to ToList is visiting every member in the list to construct a new List object every time."... more accurately every call is visiting every reference in the list, there is no access to the actual member objects in the code. Heck, depending on non-shown code, it could be valid (but silly) to have the list contain nothing but 10,000 nulls)

  • (nodebb)

    And, eventually, they return a void. Just a plain, empty, void. Ackshually, very functional...

  • PedanticRobot (unregistered)

    "the head (or first element) of the list, and the tail (the rest of the list)"

    Apparently this idea was invented by a snake that had never seen any other type of animal.

  • Sole Purpose Of Visit (unregistered) in reply to erik

    Looked it up, and you are absolutely correct. (I come from a Stepanov background, so although I work with C#, I expect enumerators to work in Pythonic "just what you expect" terms.)

    In this particular case, I don't think O(n2) matters. You'll be caught by the stack overflow long before it becomes important.

    But in general, yes. And whilst we all agree that the OP approach is frankly cretinous, it would be far less cretinous if you passed the enumerable and a skip value. Which would make it O(n) again.

    Even for cretins, there is a better way of being a cretin.

Leave a comment on “A More Functional Approach”

Log In or post as a guest

Replying to comment #:

« Return to Article