DEV Community

JavaScript Joel
JavaScript Joel

Posted on • Edited on

Functional Programming: Alternatives to the IF #Functional #JavaScript #Functors

A couple of times I have been asked "How would you do X in functional programming?" I absolutely love these types of questions.

I do my best to answer every question, but I think there are many questions interesting enough to warrant their own articles.

So for this article I would like to demonstrate how I could recreate this imperative function in a more functional manner.

This function has an if statement without an else. So while a ternary operator could work, it is not ideal.

// A basic redux-thunk action that only dispatches when value exists
const someAction = value => dispatch => {
  const item = getItem(value)
  if (item != null) {
    dispatch({ type: 'ACTION', item })
  }
}
Enter fullscreen mode Exit fullscreen mode

Here we need to run dispatch only when we have a value, otherwise we do nothing.

One option is to use a short circuit operator:

// short circuit
const someAction = value => dispatch => {
  const item = getItem(value)
  item && dispatch({ type: 'ACTION', item })
}

// ternary
const someAction = value => dispatch => {
  const item = getItem(value)
  item ? dispatch({ type: 'ACTION', item }) : null
}
Enter fullscreen mode Exit fullscreen mode

Short circuit and ternary and both ways you can solve this problem, but let's take a look at a couple more.

Solution A: ifVal Helper Function

To do this in a more functional way, I will create a helper function. I'll write this helper old school first then boil it down piece by piece so everyone can understand it easily.

// 1: old school
function ifVal (x, f) {
  if (x == null) {
    return null
  } else {
    return f(x)
  }
}

// 2: convert to arrow function
const ifVal = (x, f) => {
  if (x == null) {
    return null
  } else {
    return f(x)
  }
}

// 3: convert if/else to a ternary operator
const ifVal = (x, f) => {
  return x == null ? null : f(x)
}

// 4: voilĂ !
const ifVal = (x, f) => x == null ? null : f(x)
Enter fullscreen mode Exit fullscreen mode

Now we can modify our someAction function to use ifVal instead of the classic if block.

// functional alternative
const someAction = value => dispatch =>
  ifVal(getItem(value), item => dispatch({ type: 'ACTION', item }))
Enter fullscreen mode Exit fullscreen mode

Here's the comparison for a quick and easy twitter screenshot:

/**
 * execute the function if the value is not null or undefined
 * @param {Object} val - the value to test
 * @param {Function} fn - the function to execute.
 * @returns {Object} - null or the value of the executed function.
 */
const ifVal = (val, fn) => val == null ? null : fn(val)

// imperative example
const someAction = value => dispatch => {
  const item = getItem(value)
  if (item!= null) {
    dispatch({ type: 'ACTION', item })
  }
}

// functional example
const someAction = value => dispatch =>
  ifVal(getItem(value), item => dispatch({ type: 'ACTION', item }))
Enter fullscreen mode Exit fullscreen mode

Further reading

Take a look at Ramda's when, unless, and ifelse for other useful and similar functions.

Solution B: Functors

We could also use the Maybe Type. The Maybe Type consists of either a Just type which holds a value or a Nothing type which is exactly what is says.

For this example, I'll use the Maybe Type from the Sanctuary library.

It looks a little like this:

/* Examples of Sanctuary's Maybe */

toMaybe(null) //=> Nothing
toMaybe(undefined) //=> Nothing
toMaybe(0) //=> Just(0)
toMaybe(false) //=> Just(false)
toMaybe(123) //=> Just(123)
toMaybe({ name: 'joel' }) //=> Just({ name: 'joel' })
Enter fullscreen mode Exit fullscreen mode

The Maybe Type is pretty simple and the reason why I want to use it is because map and Maybe work together so well, it was been given a special name, a Functor.

Some libraries like ramda-fantasy have a fluent syntax, but this article is using Sanctuary. Though, it is good to know that these two are equivalent.

const double = x => x * 2

// ramda-fantasy
Maybe.toMaybe(333).map(double) //=> Just(666)

// Sanctuary
map(double, toMaybe(333)) //=> Just(666)
Enter fullscreen mode Exit fullscreen mode

Examples:

const double = x => x * 2

// map is ignored
map(double, toMaybe(null)) //=> Nothing

// no more null exceptions!
map(val => val.something(), toMaybe(null)) //=> Nothing

// map is mapped
map(double, toMaybe(333)) //=> Just(666)
Enter fullscreen mode Exit fullscreen mode

All together doSomething with the Maybe will look like this:

import { toMaybe, map } from 'sanctuary'

const someAction = value => dispatch => {
  const item = getItem(value)
  const maybeValue = toMaybe(item)
  map(item => dispatch({ type: 'ACTION', item }))(maybeValue)
}
Enter fullscreen mode Exit fullscreen mode

Here we have three functions, getItem, toMaybe and map that can be composed together into a new single function. We can pass value into that new function and it will flow through first into getItem, then toMaybe and finally that value will flow into our map.

const someAction = value => dispatch => pipe([
  getItem,
  toMaybe,
  map(value => dispatch({ type: 'ACTION', value })),
], value)
Enter fullscreen mode Exit fullscreen mode

And for one last picture perfect Tweet:

import { toMaybe, map, pipe } from 'sanctuary'

// imperative example
const someAction = value => dispatch => {
  const item = getItem(value)
  if (item != null) {
    dispatch({ type: 'ACTION', item })
  }
}

// maybe functor example
const someAction = value => dispatch => pipe([
  getItem,
  toMaybe,
  map(value => dispatch({ type: 'ACTION', value })),
], value)

someAction(666)(dispatch) // Action is dispatched!
someAction(null)(dispatch) // Nothing happens
Enter fullscreen mode Exit fullscreen mode

End

I hope you enjoyed this small functional journey. Let me know in the comments if you did, if you got lost, if you have any questions, or if you want more articles like this one. If you really want to motivate me, follow me here or Twitter and if you loved this article, pass it around!

Cheers!

Top comments (11)

Collapse
 
idanarye profile image
Idan Arye

The functional paradigm is not about abolishing all syntax (if, const) and being directly mappable to lambda calculus. The functional paradigm is about immutability and about avoiding side-effects. And your code, essentially, is all about side-effects - you don't use the return value of foo.action(), you call it for it's side-effect.

As long as you don't amend foo.action() to return something instead of doing a side-effect - you are not really doing functional programming. No matter how much lipstick you put on it - it's still a pig.

Collapse
 
joelnet profile image
JavaScript Joel

What you saying is correct, but what you are missing here is that this is a contrived example. And this is the problem with all contrived examples.

The purpose of a contrived example is to demonstrate a technique. So you fabricate an example to demonstrate the technique.

The major problem with contrived examples as you have pointed out is there is always a better way to solve the example provided.

So I could have come with up a better example such as a redux-thunk:

// redux-thunk contrived example
const maybeDoSomething = value => dispatch => {
  const next = double(value)
  if (next < 100) {
    dispatch({ type: 'something', value: next })
  }
}

Though, I attempted to contrive the example from real questions I have been asked.

And without fail someone will still point out a better way to solve this newer example too.

Unfortunately, recommended changes to the initial example will typically remove any demonstration of the techniques.

So I completely agree with you that the side effects are bad. Though this article is not about managing side effects.

Clearly one of my weak points is creating contrived examples and if anyone has a better example that can demonstrate these techniques, I will promptly update this article.

Until then, Cheers!

Collapse
 
idanarye profile image
Idan Arye

My point is that the imperative nature of the task you are trying to perform is making you use things like the comma operator that should have no place in functional programming.

It's easy to make the example fit functional programming by making foo.action return something, and making doSomething return the result of foo.action(item) if the item exists and null if it doesn't:

// imperative function
const doSomething = (foo, id) => {
  const item = getItem(id)
  if (item) {
    return foo.action(item)
  } else {
    return null;
  }
}

// functional example
const ifVal = (x, f) => x == null ? null : f(x)
const doSomething = (foo, id) => ifVal(getItem(id), foo.action)

// monad example
const doSomething = (foo, id) => S.pipe([
  getItem,
  S.toMaybe,
  S.map(foo.action)
], id)
Thread Thread
 
joelnet profile image
JavaScript Joel • Edited

Yes that is definitely a better example to start with. And again, I am in agreement with you.

I had come up with that initial imperative example based on the questions I was asked on another article I had written:

There seemed to be a theme forming so I attempted to come with with an example that held true to their original questions.

While that other article was simply to demonstrate how a ternary operator could replace all instances of if/else, I also didn't provide any functional alternatives.

So I guess you can see how this awkward example was born.

Cheers!

Thread Thread
 
idanarye profile image
Idan Arye

Javascript is an imperative language, with some features that allow functional programming. Quite a few libraries (like Sanctuary that you have mentioned) were written to levitate these features into a functional ecosystem - but it still looks forced compared to languages like Haskell that were created for FP, or even languages like Rust that were created with FP support in mind.

Notice that in the first two responses you have linked to (I'll touch the third one later) the if statement was used to do side-effects - mutate a variable or call a function without using it's return value. Side-effects are imperative programming - functional languages usually try to avoid them - or at least discourage them or work around them.

So, what you are trying to do is do an imperative task using a functional API that was forced on an imperative language's syntax. I would be surprised if it didn't turn out awkward...

Now, let us look at the examples from the responses to your article:

Example One

var i = 0
if (Math.random() > 0.5) i++

(purely functional random number generation is a bit complex, so let's ignore that part for a moment)

If I were to rewrite this into functional programming using the ternary operator, I would do this:

const j = 0
const i = (Math.random() > 0.5) ? j + 1 : j

We are doing FP - the point is not to mutate i, the point is to bind i to the result we want. Since JS does not support shadowing, I renamed the original value to something else.

Example Two

if(false) {
    doSomething();
}

In this case, I don't know what doSomething() does so I can't make it functional - but you can be sure it involves rewriting doSomething() to return a value instead of doing a side-effect. Asking what's the functional way to do a side-effect is like asking what's the vegan way to slaughter a cow - the answer is "you simply don't".

Example Three

if (!variable) { return null; }

This, of course, needs to be put into context. What are you returning from? What would you have done had you not returned here? What would be returned otherwise?

In your reply you have given such context - but that context is a side-effect, and as I've been arguing repeatedly - side-effects don't fit well into the functional style!

Now, if the context was a more pure function:

function foo(variable) {
    if (!variable) {
        return null
    }
    return variable + 15
}

We could have used a ternary operator:

const foo = variable =>
    !variable ? null         // put this first to retain the original order
              : variable + 15
}

Simple enough. But as someone commented - what if you have multiple statements?

For example:

function foo(variable) {
    if (!variable) {
        return null
    }
    variable = variable + 15
    variable = variable * 17
    variable = variable - 19
    return variable
}

Well, in functional programming you don't have "statements" - only declarations and expressions. Proper functional syntax allows you do declarations inside expressions. For example, in Haskell:

foo variable =
    -- `if` in Haskell is an expression, so there is no ternary operator
    if variable == 0
        -- Haskell is statically typed, and I don't want to introduce pattern
        -- matching here, so I return 0 instead of null
        then 0
        else let a = variable + 15
                 b = a * 17
                 c = b - 19
             in c

Javascript is not a functional language so you can't do a declaration in the middle of a statement (well, you can if you open a function just to call it, but let's not get into this...). So let's utilize that Sanctuary library:

const foo = variable =>
    !variable ? null
              : S.pipe([
                      v => v + 15,
                      v => v * 17,
                      v => v - 19
                  ], variable)

See what I did there? I've rewritten a bunch of statements that work by mutating a variable into a series of expressions where each expression's value gets fed into the next expression as an argument. This is not just forcing imperative flow into functional syntax - I've actually converted the flow itself to be functional. And this is why I could fit it into a ternary operator.

Thread Thread
 
joelnet profile image
JavaScript Joel

These are all excellent solutions!

Based on your feedback I have reworked this article. I have come up with a (slightly) better example and eliminated the comma operator as well as tap.

Thank you for your feedback!

Cheers!

Thread Thread
 
idanarye profile image
Idan Arye

I would have kept the getItem part. Without it, it's unclear why you can't just do this:

const someAction = value => dispatch => {
  value != null ? dispatch({ type: 'ACTION', value }) : null
Thread Thread
 
joelnet profile image
JavaScript Joel

Examples updated!

Collapse
 
vlatri profile image
Vlad Trishch

I tend to use function I call safe:
const safe = f => x => x && f(x)
for these purposes. I just wrap all my functions that don't expect null/undefined into it.

Collapse
 
rafaelsorto profile image
Rafael Sorto

Thank you for sharing! Very nice read!

Collapse
 
joelnet profile image
JavaScript Joel

Glad you enjoyed this! Cheers 🍻