Real World Uses of Tacit Programming: Part 1 of 2

Introduction

Tacit Programming, also called point-free style, is a way to write functions without specifying the arguments. While functional programming languages have more abilities to leverage this style, there is still two key things you can use point-free style to help with in JavaScript, Python, and Lua that I wanted to cover today. Specifically reducing the amount of arguments for functions, and aiding in composition by writing less code.

The later isn’t as powerful in JavaScript as in one where you can define your own operators, such as PureScript or Haskell, but that’s ok, it’s still helpful. Less code === less to remember & keep track of.

Part 2 has more examples.

Example of Point Free Styles

Passing Values

As Dr Boolean explains in Chapter 2 of his book, functions in JavaScript, and other languages, are also treated as values. You can set them to variables, and pass them around like variables. In currying, you return functions from functions all day, everyday. As regular values.

However, it can be easy to forget this value concept when passing functions as values vs. defining them especially when dealing with Promises in JavaScript.

getPersonFromServer()
.then(person => getStreet(person))

Instead, just give it the function:

getPersonFromServer()
.then(getStreet)

They both do the same thing, the first one just uses an extra, un-needed function often for readability’s sake. Point free refers to defining of functions, not using them like the above, but I found that Promise example resonates a lot with JavaScript developers.

With Currying

The following function attempts to read the street address from an Object using Lodash/fp’s get function.

const getStreet = person => get('address.street', person)

So does this function:

const getStreet = get('address.street')

The functions “do the same thing” and “produce the same result”, but the first defines its argument, and the 2nd does not. The first requires an extra wrapper function, and the 2nd does not. The 2nd is written in the point-free style.

Note if you don’t understand curried functions, partial applications, or pure functions, the above will be harder to grok. Read and understand those first. Or keep going, I love your attitude either way.

All In On Curry

You decide to go all in on curried functions using a library like Lodash, Ramda, or Sanctuary, or making your own. You like how your unit tests only use stubs and have no mocks. You are digging the purity. You like how they are more flexible to compose with others.

… but you start noticing a problem. Or perhaps it’s just a new requirement of you creating pure functions that must declare their dependencies in their arguments?

Your functions… they have A LOT of arguments.

Sure, your low-level private functions like the predicates, validators, or simple parsing ones are fine:

/* predicate with 1 parameter */
const firstNameIsLegitString = firstName => firstName.length > 0 && isString(firstName)

But the ones higher up the module chain, the ones that form the basis of the library or API you’re building. They either need a lot of parameters to get going, or they require copious partial applications of their own, or both.

/* login database API with 4 parameters */
const login = curry((bycryptModule, dbClient, username, password) => ...

I have one at work that uses 15.

This is the cost of pure functions. The more different types of functions you compose together, the more arguments they require until it eventually becomes quite exponential. Stupid math being all exponential.

WAT DO!?

Point Free To The Rescue

Let’s take the login function above and fix him using point free style. First, let’s show you a bit about how his module is defined.

import curry from 'lodash/fp/curry'
export const login = curry((bycryptModule, dbClient, username, password) => ...)

It exports just the login function for unit testing and “public access” purposes. Notice it doesn’t even import the bycrypt module, nor the Postgres for Node. It assumes, generally correctly, that whoever up the call stack is responsible for supplying the concrete implementations.

However, let’s be pragmatic here about 2 things:
1. You’re making it harder on those “up the call” stack. Let’s not. We wrote the code, we know those 2 dependencies are NEVER GOING TO CHANGE EXCEPT IN UNIT TESTS.
2. Only we’re testing our code by exposing those methods publicly. Let’s expose a concrete (a function that actually supplies the REAL Bycrypt and the REAL Postgres for Node) as well.

import curry from 'lodash/fp/curry'
const bcrypt = require('bcrypt')
const { Pool } = require('pg')
export const login = curry((bycryptModule, dbClient, username, password) => ...)
export const loginConcrete = login(bcrypt, new Pool())

VoilĂ ! We now expose the loginConcrete for consumers to utilize that only requires 2 parameters, and the only 2 parameters they care about anyway. Since you manually unit tested the private function login, code coverage via istanbul considers the public function loginConcrete tested as well.

import { loginConcrete } from './login'
loginConcrete('user', 'pass').then(...)

Conclusions

Tacit programming, also called point-free style, is when you write functions without parameters. Those parameters can be referred to the “point” or “points” your function is operating on. This can sometimes reduce the size of your code, decrease the amount of extra & unneeded functions, and help alleviate API level public functions from having too many parameters by supplying the commonly known concrete implementations.

It can also make the code unreadable, unintentionally hide intent, or result in MORE functions than needed as seen in Part 2. Use with abandon in list comprehensions like map, filter and reduce as well as functions used in your Promise chains, but be careful everywhere else, especially if you’re working with a team that has no familiarity, nor care about function currying.

Leave a Reply

Your email address will not be published. Required fields are marked *