2
\$\begingroup\$

So we're starting to use Typescript for a new project at work. Part of the application allows a user to retrieve their future availabilities (present/absent/possibly present), for planning purposes.

This is the get function of the service right now.

getMyAvailabilities(startDate: Moment, endDate : Moment): Observable<Map<Moment, Availability>>
{
    let params = new URLSearchParams();
    params.set('startDate', startDate.format(this.formatDateString));
    params.set('endDate', endDate.format(this.formatDateString));
    return this.http.get(this.resourceUrl, {search: params})
        .map((res: Response) =>
             {
                 let serializedResult = new Map<Moment, Availability>();
                 let result = res.json();
                 for (let key of Object.keys(result))
                 {
                     let availability = result[key];
                     serializedResult.set(moment(key),
                                          availability);
                 }
                 return serializedResult;
             });
}

You provide a startDate, and an endDate to the API, and the API will give you a map of availabilities with the date as a key.

I wrote a functional variant because it seemed suitable to functional code. We're using a lot of functional-style code in our project, because it's very suited for transforming lists of objects.

getMyAvailabilities(startDate: Moment, endDate: Moment): Observable<Map<Moment, Availability>>
{
    let params = new URLSearchParams();
    params.set('startDate', startDate.format(this.formatDateString));
    params.set('endDate', endDate.format(this.formatDateString));
    return this.http.get(this.resourceUrl, { search: params }).map((res: Response) => {
        let result = res.json();
        return Object.keys(result)
            .reduce((map: Map<Moment, Availability>, key: string) => {
                map.set(moment(key), result[key]);
                return map;
            }, new Map<Moment, Availability>());
    });
}

So, the functional variant is shorter. It's not likely that this function will need to map a large amount of objects - right now, we're estimating about 10-60 objects.

Most of us have a Java background - we're used to programming in Java, we're new or new-ish to the functional style of programming, so when presented with a procedural-style snippet of code and a functional-style snippet of code which do the same, perform the same and are of the same length, we tend to pick the procedural-style code.

But for this new project, making heavy use of procedural code would clash with the functional style used throughout the rest of the project.


Thus, the dilemma:

If we're not sure whether a piece of code is better as functional or procedural, should we go with the procedural code, which is easier to understand and probably more performant, even although we don't need that performance? Or should we go with the functional code, which is harder to understand, but follows the style of the rest of the project and is shorter in LoC?

And for the bonus question:

Is that a proper use of .reduce? Usually reduce gives a single value back. In this case, it returns... a collection. Is there another way by which we can go from Object (which has string keys and Availability values) to Map<Moment, Availability>?

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Another variant of the code

I took your second (more functional) example and polished it a bit.

*) The const keyword is used instead of the let in two places. This prevents accidental/undesired re-assignment of a value to a local variable.

**) the type of the accumulatorMap and key can be inferred.

***) The .reduce() is simplified, since map.set(key, value) already returns you a map object.

getMyAvailabilities(startDate: Moment, endDate: Moment): Observable<Map<Moment, Availability>>
{
  const params = new URLSearchParams();                              // *
  params.set('startDate', startDate.format(this.formatDateString));
  params.set('endDate', endDate.format(this.formatDateString));

  return this.http
    .get(this.resourceUrl, { search: params })
    .map((response: Response) => {
      const result = response.json();                                // *
      return Object.keys(result)
        .reduce(
          (accumulatorMap, key) =>                                   // **
            accumulatorMap.set(moment(key), result[key]),            // ***
          new Map<Moment, Availability>()
        );
    });
}

Functional vs imperative — hopefully not off topic

Disclaimer

I don't think that Codereview.StackExchange is the right place to ask questions of that sort. The answers may get extremely subjective (and result in a "holy war"). The issue is that there's no universally objective criteria which could be used to justify that functional code is by definition better than imperative ...or vice versa.

I prefer functional...

Still, I chose the functional code snippet you provided and improved it, rather than the imperative. Here's the reason why, but please take it as an opinion.

In my experience, functional code is less buggy, so to speak. Now, I do not have any data to back this statement, and my guess is that the scientific studies on that subject are contradictory. Nevertheless, this is what I feel about it.

Reasoning behind "functional is better"

Here are a very few functional programming aspects and their possible effects:

  • Working with Maybe/Option [monad] makes the code more generalized, gets rid of explicit branching —> Simpler code. —> Defect likelihood reduces.
  • Using const instead of let or var prevents accidental object reassignment. No if-guards are needed. —> Defect likelihood reduces.
  • Pure functions do not modify the state, nor alter the input parameters, and always return same results for same inputs. —> Simpler code. —> Defect likelihood reduces.
  • ...
  • I think you get my point but please do not trust me! Search online for more in-depth articles or blog posts. :)

Psychology matters

However, as you said, it takes time to adjust one's mindset, way of thinking, or habits — call it whatever you want — to harness functional programming aspects.

Remember how you touched a declarative (e.g. SQL), logic (e.g. Prolog), functional (e.g. Haskell, Lisp, Scala) or any other fundamental paradigm language for the first time. I bet it was not easy to grasp. For the first several weeks or months everyone in this position will write a lot of imperfect code and it's totally fine (as long as he/she is willing to revisit and fix it).

During this period the code may be more buggy, but this effect is temporary and diminishes with experience and learning time investments. It is important from the psychological standpoint to not make any conclusions at this point about whether the paradigm being adopted is good or bad. For the same reasons, it's extremely important to not judge a dev nor his code too strictly.

Anesthesia?

If you agree that the benefits are sweet, you want to push for functional code when possible. Still, the transition process may be painful. What do you do?

Well, here're the good news. The Divide and Conquer principle can be successfully applied. In fact, this is how I learn functional programming myself. You don't need to learn all the aspects at once (just like the SOLID principles, or design patterns).

  1. Add one tool at a time to your toolkit and start harvesting the fruits.
  2. Polish your skills over time and they become your second nature.
  3. Experiment with what you've learnt.
  4. Keep investing into the tools (in other words, make sure point 1. is repeated).
  5. Retrospect and notice how the practices/aspects can be efficiently combined together. Often the case it happens intuitively without intentional search for it.
  6. Be patient...
  7. PROFIT!

P.S.

Many programming languages that have functional traits don't push a developer very hard towards writing functional code. This is especially true for the languages that acquired these traits as a result of evolution (like C# and latest versions of Java). Developers can still write imperative code even in Scala, the language does not prevent that.

Obviously, there's no silver bullet in programming. There will always be bugs out there, no matter what king of programming language we're talking about. It's the human beings who write the code, and we are not perfect in it. Realistic expectations may help a lot to achieve satisfaction instead of disappointment.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.