26

There are many good articles about the theory of functional programming in JavaScript. Some even contain code examples showing the difference between imperative/object-oriented programming and declarative/functional programming. But I have found none that show, with simple JavaScript code examples, how to handle side-effects in a web app. No real world application can entirely avoid side-effects (database calls, logging to console, saving to a file, drawing to the screen etc.) and I have a hard time figuring out how it is done in practice.

There are blog articles and S/O answers (like this one: How to perform side-effects in pure functional programming?) that touch on the subject of handling side-effects in the real world but they are usually far from simple, don't include code example or include code example in other languages (Haskell, Scala, etc.). I haven't found one for Node/JavaScript.

So... given the following very simple example Node/Express app with MongoDB database, what code changes must be implemented so that this piece of code fully reflect current JavaScript functional programming best practices. Especially when it comes to the routes/functions handling database calls. I'm hoping your answers will help me, and others, better understand the practical application of the 'avoiding side-effects' concept of Functional Programming in real-world JavaScript.

/*app.js*/

const express = require('express')
const app = express()
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/test');

var greetingSchema = mongoose.Schema({
    greeting: String
});

var Greeting = mongoose.model('Greeting', greetingSchema);

app.get('/', function (req, res) {
  Greeting.find({greeting: 'Hello World!'}, function (err, greeting){
    res.send(greeting);
  });  
});

app.post('/', function (req, res) {
  Greeting.create({greeting: 'Wasssssssssssuuuuppppp'}, function (err, greeting){
  res.send(greeting);
  });      
});

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})
snowfrogdev
  • 5,963
  • 3
  • 31
  • 58
  • 2
    This is an ill-posed problem. As you said, "what would the code look like once fully converted to functional programming" - the question you asked - is meaningless because "No real world application can entirely avoid side-effects". The code you have above is full of side-effects (logging to console, sending greetings, connecting to a database). You already avoid loops and use .find and lambda functions, so the parts which are not inherently stateful are already functional. It's not clear what you want from an answer. (I didn't downvote this question, but this might be why someone did). – anandsun Jul 19 '17 at 14:13
  • @anandsun thanks for the feedback. I will gladly edit the question to make it better. Maybe you can help me do that. I was under the impression that the code example from my question did not reflect JavaScript Functional Programming best practices. Everything I read about FP makes it sound like you have to use streams and monads and all these other complicated things to handle side-effects and state changes in your application. I'm wondering what that actually looks like in JavaScript. Are you saying that the code I supplied is as good as it gets when it comes to FP in JavaScript? – snowfrogdev Jul 19 '17 at 14:47
  • 1
    re: "you have to use streams and monads and all these other complicated things." Eww, no! This is a common misconception that pushes people away from FP because they think it is too complicated. I love FP and use Scala at work, but I don't agree that you HAVE to use monads and streams. See for example [fp-is-not-the-answer](http://degoes.net/articles/fp-is-not-the-answer). Monads > nulls. Streams are nice. But to start, push statefulness to the edges of your code, split your logic up into small functions, use standard library functions, avoid side effect in functions, etc. – anandsun Jul 19 '17 at 15:02
  • Another thing is a question like this might have more success on StackExchange or elsewhere, because you're not asking for how to get something working, but advice on style, which is often discouraged on StackOverflow because it's subjective. – anandsun Jul 19 '17 at 15:03
  • @anandsun "push statefulness to the edges of your code"... I have seen that statement a few times in my readings on FP but I have to admit I don't really understand what is meant by it. Could you expand on that a little bit? – snowfrogdev Jul 19 '17 at 15:13
  • To incorporate time-varying values and events into functional programming you need an appropriate FRP library. In Javascript libs such as Bacon or Highland realize parts of the funcitonal reactive paradigm. They are pretty demanding and you must first go through a long learning curve though. –  Jul 19 '17 at 15:36
  • 2
    @neoflash Essentially it means that you defer impure computations with thunks (functions without arguments), build your pure function compositions around them and leave it to the caller to actually execute them. By doing so you defer the effects, or more allegorical, you push it to the edges of your application. –  Jul 19 '17 at 15:51

1 Answers1

36

You will not be able to avoid side effects entirely but you can make some effort to maximally abstract them away where possible.

For example the Express framework is inherently imperative. You run functions like res.send() entirely for their side effects (you don't even care about its return value most of the time).

What you could do (in addition to using const for all your declarations, using Immutable.js data structures, Ramda, writing all functions as const fun = arg => expression; instead of const fun = (arg) => { statement; statement; }; etc.) would be to make a little abstraction on how Express usually works.

For example you could create functions that take req as parameter and return an object that contains response status, headers and a stream to be piped as body. Those functions could be pure functions in a sense that their return value depend only on their argument (the request object) but you would still need some wrapper to actually send the response using the inherently imperative API of Express. It may not be trivial but it can be done.

As an example consider this function that takes body as an object to send as json:

const wrap = f => (req, res) => {
  const { status = 200, headers = {}, body = {} } = f(req);
  res.status(status).set(headers).json(body);
};

It could be used to create route handlers like this:

app.get('/sum/:x/:y', wrap(req => ({
  headers: { 'Foo': 'Bar' },
  body: { result: +req.params.x + +req.params.y },
})));

using a function that returns a single expression with no side effects.

Complete example:

const app = require('express')();

const wrap = f => (req, res) => {
  const { status = 200, headers = {}, body = {} } = f(req);
  res.status(status).set(headers).json(body);
};

app.get('/sum/:x/:y', wrap(req => ({
  headers: { 'Foo': 'Bar' },
  body: { result: +req.params.x + +req.params.y },
})));

app.listen(4444);

Testing the response:

$ curl localhost:4444/sum/2/4 -v
* Hostname was NOT found in DNS cache
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 4444 (#0)
> GET /sum/2/4 HTTP/1.1
> User-Agent: curl/7.35.0
> Host: localhost:4444
> Accept: */*
> 
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Foo: Bar
< Content-Type: application/json; charset=utf-8
< Content-Length: 12
< ETag: W/"c-Up02vIPchuYz06aaEYNjufz5tpQ"
< Date: Wed, 19 Jul 2017 15:14:37 GMT
< Connection: keep-alive
< 
* Connection #0 to host localhost left intact
{"result":6}

Of course this is just a basic idea. You could make the wrap() function accept promises for the return value of the functions for async oprations, but then it will arguably not be so side-effect free:

const wrap = f => async (req, res) => {
  const { status = 200, headers = {}, body = {} } = await f(req);
  res.status(status).set(headers).json(body);
};

and a handler:

const delay = (t, v) => new Promise(resolve => setTimeout(() => resolve(v), t));

app.get('/sum/:x/:y', wrap(req =>
  delay(1000, +req.params.x + +req.params.y).then(result => ({
    headers: { 'Foo': 'Bar' },
    body: { result },
  }))));

I used .then() instead of async/await in the handler itself to make it look more functional, but it can be written as:

app.get('/sum/:x/:y', wrap(async req => ({
  headers: { 'Foo': 'Bar' },
  body: { result: await delay(1000, +req.params.x + +req.params.y) },
})));

It could be made even more universal if the function that is an argument to wrap would be a generator that instead of yielding only promises to resolve (like the generator-based coroutines usually do) it would yield either promises to resolve or chucks to stream, with some wrapping to distinguish the two. This is just a basic idea but it can be extended much further.

rsp
  • 107,747
  • 29
  • 201
  • 177
  • 1
    This answer doesn't solve my specific problem but it gets an upvote for being an awesome answer! – Eric Mar 13 '18 at 19:53
  • I used this approach for writing API handlers serving PATCH requests and translating them into corresponding mongoose (sub)document updates. All the handlers are purely functional; they operate under a tiny framework which: * converts mongoose subdocument into POJO * calls the corresponding handler * gets required changes * applies the changes to mongoose subdocument * and saves it This way the only non-FP part is a tiny framework; all the various handlers are purely FP. – ivosh Nov 05 '18 at 10:30