1
\$\begingroup\$

In this article I found this quite elegant looking Elixir code:

conn
|> put_session(:current_user, user)
|> put_resp_header(location, "/")
|> put_resp_content_type("text/html")
|> send_resp(302, "You are being redirected")

It works by using the output of each of the functions as the first argument to the next function. The given args are bound to the second argument and up.

I know two different proposals for the pipe operator are being considered for a future version, but I am wondering if there is a way of writing something visually and functionally similar looking using the Javascript of today?

I am guessing I could use currying in some sense, binding everything but the first argument?

For reference, here are the original function definitions from the article. They all return a conn

  • assign(conn, key, value)
  • clear_session(conn)
  • put_resp_content_type(conn, content_type, charset)
  • put_resp_header(conn, key, value)
  • put_session(conn, key, value)
  • send_resp(conn, status, body)

I tried making some suggestions, but I would like input on how this could be improved/be more readable/look better for the casual programmer coming to modify my code.

A basic attempt using ES5.1 would look like this:

const {bind, flow} = require('lodash');
const _ = bind.placeholder;

flow(
  bind(put_session, null, _, current_user, user),
  bind(put_resp_header, null, _, location, '/'),
  bind(put_resp_content_type, null, _, 'text/html'),
  bind(send_resp, null, _, 302, 'You are being redirected')
)(conn);

It's a little bit too verbose compared to the original, so I tried compacting it down a bit:

// bind all but first argument - what should I call this?
// bindAllButFirstArgument is a tad bit long
const $$ = (...args) => bind(args[0], null, _, args.slice(1));

flow(
  $$(put_session, current_user, user),
  $$(put_resp_header, location, '/'),
  $$(put_resp_content_type, 'text/html'),
  $$(send_resp, 302, 'You are being redirected')
)(conn);

This looks a bit better (except for the mysterious $$ :) ), and with some minor modifications I could try to make the function in itself syntactically stand out by having a line like

  $$(put_session, current_user, user),

look like

  pipe.prepare(put_session)(current_user, user),

where pipe would be the equivalent of flow and pipe.prepare a utility function that "prepared" the function for the pipe flow mentioned above. Better? What do you think of this? Too dense? Needing to much reading of JSDocs? Different ways?

Would look like this

const pipe = (...args) => flow(...args);
pipe.prepare = fn => (...args) => bind(fn, null, _, args);

pipe(
  pipe.prepare(put_session)(current_user, user),
  pipe.prepare(put_resp_header)(location, '/'),
  pipe.prepare(put_resp_content_type)('text/html'),
  pipe.prepare(send_resp)(302, 'You are being redirected')
)(conn);

Runnable example: https://runkit.com/fatso83/piping-with-binding

\$\endgroup\$
1
\$\begingroup\$

It works by using the output of each of the functions as the first argument to the next function. The given args are bound to the second argument and up.

Just running by this description, one could easily create a piping operation with 2 helper functions:

const bind = (fn, ...boundArgs) => callArg => fn(callArg, ...boundArgs)
const pipe = (...fns) => initialArg => fns.reduce((r, fn) => fn(r), initialArg)

pipe(
  bind(put_session, current_user, user),
  bind(put_resp_header, location, '/'),
  bind(put_resp_content_type, 'text/html'),
  bind(send_resp, 302, 'You are being redirected')
)(conn)

bind accepts a function and bound arguments and returns a function that accepts a single argument. When this returned function is called, it calls the bound function with the call argument first, followed by the rest of the bound arguments.

pipe accepts a list of bound functions, and calls them one by one. The first function gets the call argument, while the remaining functions gets the return value of the previously called functions.

This approach effectively copies over the lodash signature, but does not require lodash. It also doesn't deal with this. You get the conciseness of your second example, and less of the bloat on your third example.

\$\endgroup\$
  • \$\begingroup\$ My only issue is naming. A casual reader would have no idea that the bind operator deviated from the standard bind implementations by reserving the first argument. Solution? I tried namespacing. Would you actually write this code? \$\endgroup\$ – oligofren Aug 16 at 12:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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