Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Real-World Uses of Tacit Programming: Part 2 of 2

DZone's Guide to

Real-World Uses of Tacit Programming: Part 2 of 2

In this post, we'll show some helpful ways to use tacit programming in data validation and composing functions together synchronously.

· Web Dev Zone ·
Free Resource

Deploy code to production now. Release to users when ready. Learn how to separate code deployment from user-facing feature releases with LaunchDarkly.

Introduction

In our last post, we talked about what Tacit Programming is, how it can help reduce argument count of public API functions using known concrete implementations, and how it can help shrink code size and function count for Array comprehensions and Promise chaining.

In this post, we'll show some helpful ways to use tacit programming in data validation and composing functions together synchronously as well as an example of taking things way too far.

Data Validation

If you're validating data that is large Objects, you'll probably want to check a lot of properties. Let's create some predicates to check those properties of a Person Object that should have a name and address.

const person = {
    name: 'Jesse Warden',
    address: {
        street: '123 Cow Ville',
        phone: [
            '123-555-1234',
            '999-555-8234'
        ]
    }
}

And the predicates:

const {has, every} = require('lodash/fp')
const legitPerson = person => every(
    hasResult => hasResult === true, [
        has('name', person),
        has('address.street', person),
        has('address.phone[0]', person)
    ]
)
/*  use like legitPerson({name: 'sup'}) which is false, no address */

... however, it's a bit verbose. Let' use point free style to whittle it down:

const legitPerson = person => every(
    predicate => predicate(person), [
        has('name'),
        has('address.street'),
        has('address.phone[0]')
    ]
)

Instead of looping through an Array of [true, true, true], we instead loop through an Array of functions, can call 'em on our data. Same result, less code.

Smaller Composing

There are two places in JavaScript, less so in Lua/ Python, where you can use the point free style for less code, and thus less stuff to remember and maintain.

Synchronous Composing

The most common used, and demoed, is data parsing. Let's parse this string below from a JSON string, convert to person Objects, filter out the humans, and clean up their names.

const peopleFields = `[
["jesse warden", "123 Cow Ville", ["123-555-1234", "999-555-8234"], "human"],
["brandy fortune", "123 Cow Ville", ["123-867-5309"], "human"],
["albus dumbledog", "92 Dog Down", ["123-555-1234"], "dawg"]
]`

To parse that, we'll use four functions, in order.

/* take in Arrays, convert to a Person Object */
const listToPeople = people =>
    map(list =>
        ({
            name: nth(0, list),
            address: {
                street: nth(1, list),
                phone: nth(2, list)
            },
            type: nth(3, list)
        }),
        people
    )
/* keep only humans in the Array */
const filterHumans = people =>
    filter(
        person => getOr('unknown', 'type', person) === 'human',
        people
    )
/* convert jesse warden to Jesse Warden */
const formatNames = people =>
    map(
        list => set('name', startCase(get('name', list)), list),
        people
    )
/* put 'em all together */
const parseHumans = jsonString =>
    formatNames(
        filterHumans(
            listToPeople(
                JSON.parse(jsonString)
            )
        )
    )

When you pass peopleFields to parseHumans you get:

[{
        name: 'Jesse Warden',
        address: {
            street: '123 Cow Ville',
            phone: [Array]
        },
        type: 'human'
    },
    {
        name: 'Brandy Fortune',
        address: {
            street: '123 Cow Ville',
            phone: [Array]
        },
        type: 'human'
    }
]

It works, but... wow. Even with pure functions and not a function block {} to be seen, it's still verbose. Let's use point-free style to shrink it.

map and nth vs. operators

The listToPeople uses map which takes two parameters: a function that takes in an item of the Array, and whatever it returns will be put into a new Array at the some position, and the Array you want to convert.

const listToPeople = people =>
    map(list =>
        ({
            name: nth(0, list),
            address: {
                street: nth(1, list),
                phone: nth(2, list)
            },
            type: nth(3, list)
        }),
        people
    )

However, it's curried by default in lodash/fp. That means there's no need to copy people twice, we already provide the first parameter, flow will supply the second later. Simply remove people, and no need to create a new Arrow function:

const listToPeople =
    map(list =>
        ({
            name: nth(0, list),
            address: {
                street: nth(1, list),
                phone: nth(2, list)
            },
            type: nth(3, list)
        })
    )

We'll cover the destructuring way after, but I want to show the function way first. If you're willing to get a bit imperative for demo sake, we can shrink it even more. The nth function in Lodash/fp is curried and takes two parameters: The index and the Array. It's like list[0] except you write nth(0, list). It, too, is curried by default. But... it's the wrong way. The dynamic part is SUPPOSED to be to the right, but in our case, the dynamic part is on the right. There's a variety of solutions, so let's just do manual for now to get our ideas down:

const listToPeople =
    map(list => {
        const lst = index => nth(index, list)
        // or, if you want point-free
        // const lst = curry(flip(nth))
        return {
            name: lst(0),
            address: {
                street: lst(1),
                phone: lst(2)
            },
            type: lst(3)
        }
    })

Sometimes, point-free isn't needed because operators that the language has can do the job.

const listToPeople =
    map(list =>
        ({
            name: nth(0, list),
            address: {
                street: nth(1, list),
                phone: nth(2, list)
            },
            type: nth(3, list)
        })
    )

We can simply replace nth with the actual value:

const listToPeople =
    map(([name, address, street, phone, type]) =>
        ({
            name,
            address: {
                street,
                phone,
            },
            type
        })
    )

Yes, functions are powerful, but remember operators are as well. Why not always operators, then? Operators can throw Errors, pure functions like nth do not. Using point-free style assumes they are pure functions and no side-effects. However, I get it, it's hard not to be seduced by their beauty.

filter and getOr

The filter function's first parameter is the function that returns true for keeping the item in the array, false for not. The second parameter is the Array.

const filterHumans = people =>
    filter(
        person => getOr('unknown', 'type', person) === 'human',
        people
    )

Like map, it's curried, so we can remove the second parameter and let "someone else" fill that in later. That someone else is when it's used in flow as you'll see at the bottom. We can also remove the Array function, and just set the return value since it's a partial application (a function with one argument already supplied, we're just waiting for the second argument to actually invoke the function):

const filterHumans =
    filter(
        person => getOr('unknown', 'type', person) === 'human'
    )

The getOr is like get, except, if it detects undefined or null, it'll use whatever default value we give it instead. It's like a Maybe'sgetOrElse. While the getOr inline is readable and small, this IS an article about tacit programming, so let's refactor it as well:

const getTypeOrUnknown = getOr('unknown', 'type')
const filterHumans =
    filter(
        person => getTypeOrUnknown(person) === 'human'
    )

Now getTypeOrUnknown is just expecting the actual Object to check. We're close... but I still see an Arrow function. Let's FINISH HIM Mortal Kombat style:

const getTypeOrUnknown = getOr('unknown', 'type')
const equalsHuman = isEqual('human')
const isHuman = flow([getTypeOrUnknown, equalsHuman])
const filterHumans = filter(isHuman)

TACITALITY!!!11oneone

If you missed it:

  • getTypeOrUnknown takes advantage of getOr requiring three parameters: default value, the path, and the value. We're waiting on the person value, so we just supply the default value and the path.

  • equalsHuman is a way to write equalsHuman = o => o === 'human' without creating an Arrow function and staying point-free. More too it than that, but that's the simple version.

  • isHuman is just isHuman = person => equalsHuman(getTypeOrUnknown(person)), but point-free; no parameter, no arrow function.

  • filterHumans not just takes the function, and is waiting on the second parameter; the Array of humans and non-humans to filter against.

Taking Point-Free Too Far

We're not going to get into profuctor optics or lenses, but the core engine of them, get and set, we are are going to shrink them in formatNames. Side note: If you've mastered get and set and want new lens-like toys, check out Ramda's lens functions.

We've talked about get in the past on this blog. It's a safe way to do a dot access without worry that you'll do dot access on undefined and cause an error. Additionally, you can dot access a path, even using Arrays like get('some.deep.property[2].man') without using try/catch, the if(thing && thing.dot && thing.dot.cow) insanity.

The set function is about the same, except it can set a value AND will return a brand new Object. It's like Object.assign or Object destructor copying like {...cow, name: 'new cow name'}. It's signature in lodash/fp is the path, value, and Object.

const formatNames = people =>
    map(
        list => set('name', startCase(get('name', list)), list),
        people
    )

Let's do the easy one first, and remove the second parameter of map, and then the Arrow function:

const formatNames =
    map(
        list => set('name', startCase(get('name', list)), list)
    )

Cool, now let's untangle that get/set mess:

const getName = flow([get('name'), startCase])
const setName = set('name')
const formatNames =
    map(
        list => setName(getName(list), list)
    )

Not bad, but I see list three times and an arrow function, ack! For this one, we'll have to borrow two functions from Ramda and build up our compose chain. Let's get cray:

const getName = get('name')
const setName = set('name')
const getFixedName = flow([getName, startCase])
const nameAndPerson = [{f: getFixedName}, {f: identity}]
const callF = invoker(1, 'f')
const flipMap = curry(rearg([1, 0], map))
const mapNameAndPerson = flipMap(nameAndPerson)
const getFixedNameAndPerson = flow([callF, mapNameAndPerson])
const setNameWithPersonNameAndPerson = apply(setName)
const mapFormatNames = flow([getFixedNameAndPerson, setNameWithPersonNameAndPerson])
const formatNames = map(mapFormatNames)

Insanity. BUT, not a parameter, nor arrow function in sight.

While I'm proud I figured this out in three days, this is an example of taking point-free way too far. When people say point-free style results in smaller, more readable code, you can respond "not always", heh. While all the functions are pure and point-free, it's a ton of functions... a ton of code which originally fit in one unformatted line and is now 11. You just got Arrow functions in ES6; are they really that bad? It's not exactly clear that each one isn't supposed to be a standalone function, but rather a tool for the greater whole.

Feel free to skip to Data Validation. For you nerds who want the down low on what those functions do, read on.

  • getName: the get function in lodash takes a path and an Object and is curried by default. If you just supply the path, you pass it an Object, and it'll return to you that path. The get('name', {name: 'cow'}) will return cow. You can also write getName({name: 'cow'}) and it does the same thing.

  • setName: like get, except it returns a deep copy of the Object with the property you set changed. Think Object.assign or object destructuring copying, i.e. const newVersion = {...person, name: 'new name'}. Calling const result = setName('new name', {name: 'cow'}) will end up having result equal {name: 'new name'} and be a different Object than you one you passed in to ensure immutability.

  • getFixedName: This uses flow to take the argument you send to function, send it first to getName, then take whatever getName spits out, and capitalize it. Passing in {name: 'jesse warden'} will spit out the String 'Jesse Warden.' Think of it as a choo-choo train of functions, or simple pipes connected together. Every function inside takes the input from the left, and whatever it spits out will go to the function to the right, on down the "flow train."

  • nameAndPerson: The set function needs two parameters. The problem is, set's second parameter needs to the result of getName. We can't use flow because it'd spit out a String, and we need the original person Object. The easiest way to handle multiple things is to put 'em into an Array since Array's are bread and butter for array comprehension libraries like Lodash/Ramda, etc. However, things like invokeMap don't allow parameters without creating your own. An easier way is to use invoker from Ramda, and then throw that function in a map. That way we can "call a bunch of functions in an Array, and get their results." Once you have those results, you can feed them later to the set function via Ramda's apply which works like Function.prototype.apply; call a function with an Array of parameters. The identity function, gives you back what you gave it. It is there so when we give it a person, it'll return the person so our arguments Array we end up with has the person ready to go for the second parameter to set.

  • callF: Using Ramda's invoker, we can create a function that will "call a function on an Object." Like a get, but instead of getting the value, it gets the value that is assumed to be a function, calls it, then gives you the result. For invoker, you supply the path, and parameter(s), and you get a function back ready to go. Up in the nameAndPerson Array, we created Objects with an "f" property that has the function we want to call. The callF will get that Object, find the "f"'s value, then call that function with whatever parameter we pass. Think of it like const callF = (arg, person) => person.f(arg), except curried.

  • flipMap: The lodash map function takes a function and an Array. However, we know the set function we want to run, but we don't know the Array, or results of running getName and identity, so we have to reverse it. Now, you could use flip(map), but that breaks currying. Meaning, if you call flipMap(array, func), you're fine, but if you call flipMap(array)(func), say putting it as a partial application into flow for example, boom. So, we have to do surgery manually using rearg. Same thing, different function, and we curry it again. I supposed we could just curry(flip(map)), but after losing three hours to flip, 3 stars would not flip in a curry again. Remember kids, blame the library and its author(s), not your own inexperience/inability.

  • mapNameAndPerson: We know the two functions we want to run, we're just waiting on the function that'll have the person Object we need for map. So... just create a partial application for now.

  • getFixedNameAndPerson: We compose the callF and mapNameAndPerson together. First, take the person Object you pass, and load it into the callF. When he's run through the mapNameAndPerson function, that callF will have the person ready to go. He'll invoke and pass it to getName first, and identity will just send back the person, resulting in ['Jesse Warden', person]. Dope, the two arguments we need for set!

  • setNameWithPersonNameAndPerson: We take our Array of two ready to go values above, and "spread 'em like buttah" using function.apply to setName. The set is path, value, and object. setName already has the path, "name". Our Array has value and object. The apply function will call that function, and give it those two arguments, in order.

  • mapFormatNames: We wire the 2 pipes together. Passing a person Object to this function will first "fix it" via getFixedNameAndPerson, and then call the setNameWithPersonNameAndPerson with those two values, and we'll get new personobject out with the correctly capitalized name.

  • formatNames: The mapFormatNames function only works with one person. We pre-populate a map function so it'll work with an Array of person objects.

Composing

The last function that wires the whole shebang together, parseHumans, is a nest of functions. While nicely formatted, it's just a prettier nested if statement. The real problem, though, is it doesn't really read from top to bottom like a Promise chain does. It's actually inside out.

const parseHumans = jsonString =>
    formatNames(
        filterHumans(
            listToPeople(
                JSON.parse(jsonString)
            )
        )
    )

Using flow, we can say "run these functions in order." Pass the value to the first, and whatever that function spits out, pass the the right function... and on down the line. Like the old game telephone, but even more inappropriate.

const parseHumans = jsonString => flow([
    JSON.parse,
    listToPeople,
    filterHumans,
    formatNames
])(jsonString)

A lot more clear and smaller. Pass this jsonString to the JSON.parse, and the Object he parses out, give to the listToPeople, and on down the chain. One problem, it's not point-free because of the jsonString. Let's fix.

const parseHumans = flow([
    JSON.parse,
    listToPeople,
    filterHumans,
    formatNames
])

And we're done! Not an Arrow function, nor parameter in 47 lines of code.

Imports

By the way, if you're curious about the functions used in the above for the imports, check it:

const { map, nth, filter, getOr, set, startCase, get, flow, isEqual, rearg, identity, curry } = require('lodash/fp')
const R = require('ramda')
const { invoker, apply } = R

Conclusions

As you can see, for data parsing where you need to operate many times on the same data, point-free style can help reduce how much code you have to write. For data parsing, you can see how even a little bit can go a long way in what ends up often being a lot of some of the most important code you write. Remember, if you even you use a typed language, and have runtime type enforcement, one wrong data parsing, and boom, errors. Creating fixtures (fake, known data) to unit test your parsing code can be painful to create and maintain as the data/API changes. Anything you can do to make it into small, re-usable pieces that are easier to test in isolation can go a long way.

You've seen how using various operators provided in the language can reduce code size as well, but unlike pure functions, they can also blow up (throw exceptions).

Finally, the formatNames has shown you how you can take things too far. Sometimes writing simple one line arrow functions vs 11 point-free functions is a lot easier to read, follow, and maintain. Lua, Python, and JavaScript in ES6 can be beautiful in a non-point-free style. Only use where you've made things redundant.

Deploy code to production now. Release to users when ready. Learn how to separate code deployment from user-facing feature releases with LaunchDarkly.

Topics:
web dev ,tacit programming ,javascript ,data validation ,javascript functions

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}