Showing posts with label lodash. Show all posts
Showing posts with label lodash. Show all posts

Monday, August 28, 2017

Lodash Logic

If you're using Lodash, you have a handful of tools at your disposal for organizing logic into functions. This is a nice alternative to imperative if statements sprinkled throughout your code. For example, let's say that you only want to execute code if one of several choices is true. You could use the some() function as follows:

const choices = [
  { name: 'choice 1', value: true },
  { name: 'choice 2', value: true },
  { name: 'choice 3', value: false }
];

if (_(choices).map('value').some()) {
  console.log('true');
else {
  console.log('false');
}

The choices array represents the choices that we have available. The name property isn't actually used for anything in this code. The value property is what we're interested in here. We want to run some code if any value is true.

To do so, we're using an if statement. With the help of the map() and some() Lodash functions, we can easily check for this condition. In this case, the statement evaluates to true because there's two true values in the array. We can also check to make sure that every value is true before executing a piece of code:

if (_(choices).map('value').every()) {
  console.log('true');
else {
  console.log('false');
}

In this case, the else path is followed because not every value is true.

The _.some() and _.every() functions are helpful with simplifying the conditions evaluated by if statements. For example, _.some() provides the same result as chaining together a bunch of logical or (||) operators. Likewise, _.every() replaces logical and (&&) operators.

The result is that instead of having to maintain the condition that's evaluated in the if statement, we can simply add new values to the choices collection. Essentially, this is a step toward declarative programming, away from imperative programming.

Let's think about the if statement used above, and what it's actually doing. It's calling console.log() when some condition is true, and it's calling console.log() again when the condition is false. The problem with if statements like this is that they're not very portable. It'd be much easier to call a function with the possible choices as an argument, and the correct behavior is invoked.

Here's what this might look like:

const some = (yes, no) => (...values) =>
  new Map([
    [true, yes],
    [false, no]
  ]).get(_.some(values))();

Let's break this code down:
  • We've created a higher-order function called some() that returns a new function.
  • The returned function accepts an arbitrary number of values. These are tested with Lodash's _.some().
  • The some() function accepts yes() and no() functions to run based on the result of calling _.some(values)
  • A Map is used in place of an if statement to call the appropriate logging function.
With this utility, we can now compose our own functions that values as arguments, and based on those arguments, run the appropriate function. Let's compose a function using some():

const hasSome = some(
  () => console.log('has some'),
  () => console.log('nope')
);

Now we have a hasSome() function will log either "has some" or "nope", depending on what values are passed to it:

hasSome(0, 0, 0, 1, 0);
// -> has some
hasSome(0, 0, 0, 0);
// -> nope

Now any time that you want an if statement that evaluates simple boolean expressions and runs one piece of code or another, depending on the result, you can use some() to compose a new function. You then call this new function with the choices as the arguments.

Let's create an every() function now that works the same way as some() except that it tests that every value is true:

const every = (yes, no) => (...values) =>
  new Map([
    [true, yes],
    [false, no]
  ]).get(_.every(values))();

The only difference between every() and some() is that we're using _.every() instead of _.some(). The approach is identical: supply yes() and no() functions to call depending on result of _.every().

Now we can compose a hasEvery() function, just like we did with the hasSome() function:

const hasEvery = every(
  () => console.log('has every'),
  () => console.log('nope')
);

Once again, we've avoided imperative if statements in favor of functions. Now we can call hasEvery() from anywhere, and pass in some values to check:

hasEvery(1, 1, 1, 1, 1)
// -> has every
hasEvery(1, 1, 1, 1, 0)
// -> nope

Lodash has a _.cond() function that works similarly to our Map approach in some() and every(), only more powerful. Instead of mapping static values, such as true and false, to functions to run, it maps functions to functions. This allows you to compute values to test on-the-fly.

Before we get too fancy, let's rewrite our some() and every() functions using _.cond():

const some = (yes, no) => _.flow(
  _.rest(_.some, 0),
  _.cond([
    [_.partial(_.eq, true), yes],
    [_.stubTrue, no]
  ])
);

Let's break this down:
  • The _.flow() function creates a new function by calling the first function, then passing it's return value to the next function, and so on.
  • The _.rest() function creates a new function that passes argument values as an array to it's wrapped function. We're doing this with _.some() because it expects an array, but we just want to be able to pass it argument values instead.
  • The _.cond() function takes an array of pairs. A pair is a condition function, and a function to call if the condition function returns true. The first pair that evaluates to true is run, and no other pairs are evaluated.
  • The _.partial(_.eq, true) call makes a new function that tests the output of _.some().
  • The _.stubTrue() function will always evaluate to true, unless something above it evaluates to true first. Think of this as the else in an if statement.
We can use this new implementation of some() to compose the same hasSome() function that we created earlier and it will work the same way. Likewise, we can implement the every() function using the same approach.

For something as simple as the some() and every() functions, the _.cond() approach doesn't present any clear advantage over the Map approach. This is because there are exactly two paths. Either the condition evaluates to true, or it doesn't. Often, we're not working with simple yes/no logical conditions. Rather, there are a number of potential paths.

Think of this as a an if-else statement with lots of conditions. Suppose we had the following conditions:

const condition1 = false;
const condition2 = true;
const condition3 = false;

Instead of a simple yes/no question with two potential paths, now we have 3. Later on, we might have 4, and so on. This is how software grows to be complex. Here's how we would evaluate these conditions and execute corresponding code using _.cond():

const doStuff = _.cond([
  [_.constant(condition1), () => console.log('Condition 1')],
  [_.constant(condition2), () => console.log('Condition 2')], 
  [_.constant(condition3), () => console.log('Condition 3')]
]);

doStuff();
// -> Condition 2

For each of the condition constants that we defined above, we're using the _.constant() function in _.cond(). This creates a function that just returns the argument that is passed to it. As you can see, console.log('Condition 2') is called because the function returned by _.constant(condition2) returns true.

It's easy to add new pairs to _.cond() as the need arises. You can have 20 different execution paths, and it's just as easy to maintain as 2 paths.

In this example, we're using static values as our conditions. This means that doStuff() will always follow the same path, which kind of defeats the purpose of this type of code. Instead, we want the path chosen by _.cond() to reflect the current state of the app:

const app = {
  condition1: false,
  condition2: false,
  condition3: true
};

Instead of using _.const(), we'll have to somehow pass the app into each evaluator function in _.cond():

const doStuff = _.cond([
  [_.property('condition1'), () => console.log('Condition 1')], 
  [_.property('condition2'), () => console.log('Condition 2')], 
  [_.property('condition3'), () => console.log('Condition 3')]
]);

The _.property() function creates a new function that returns the given property value of an argument. This is where the _.cond() approach really shines: we can pass arguments to the function that it creates. Here, we want to pass it the app object so that we can process its state:

doStuff(app);
// -> Condition 3

app.condition1 = true;
app.condition3 = false;

doStuff(app);
// -> Condition 1

When doStuff() is called the first time, the console.log('Condition 3') path is executed. Then, we change the state of app so that condition1 is true and condition3 is false. When doStuff() is called again with app as the argument, the console.log('Condition 1') path is executed.

So far, we've been composing functions that use console.log() to print values. If you write smaller functions that return values instead of simply printing them, you can combine them to build more complex logic. Think of this as an alternative to implementing nested if statements.

As an example, suppose we have the following two functions:

const cond1 = _.cond([
  [_.partial(_.eq, 1), _.constant('got 1')],
  [_.partial(_.eq, 2), _.constant('got 2')]
]);

const cond2 = _.cond([
  [_.partial(_.eq, 'one'), _.constant('got one')], 
  [_.partial(_.eq, 'two'), _.constant('got two')]
]);

These functions themselves follow the same implementation approach, using _.cond(). For example, cond1() will return the string 'got 1' or 'got 2', depending on the number supplied as an argument. Likewise, cond2() will return 'got one' or 'got two', depending on the string argument value.

While we can use both of these functions on their own, we can also use them to compose another function. For example, we could write an if statement that would determine which one of these functions to call:

if (_.isFinite(val)) {
  cond1(val);
} else if (_.isString(val)) {
  cond2(val);
}

Remember, this approach isn't very portable. To make it portable, in the sense that we don't have to write the same if statement all over the place, we could wrap the whole thing in a function. Or, we could just use _.cond() to compose it:

const cond3 = _.cond([
  [_.isFinite, cond1],
  [_.isString, cond2]
]);

cond3(1);
// -> "got 1"
cond3(2);
// -> "got 2"
cond3('one');
// -> "got one"
cond3('two');
// -> "got two"

Using _.cond(), you can compose complex logic by reusing existing functions. This means that you can keep using these smaller functions where they're needed, and you can use them as pieces of larger functions.

Friday, July 24, 2015

Efficient counting with lodash

Counting with lodash is easy. A lot of the time, an array is returned, so I just need to read the length property. If I'm working with a chain, I can simply use the size() function. What I like about size() is that it works with anything — strings, arrays, and objects. However, using length or size() isn't always the most efficient answer.

Tuesday, March 10, 2015

Fixed argument counts with lodash ary()

When using functions as callbacks to lodash collection functions, we have to be careful to get the arguments right. This is because functions like map() actually pass more than just the current collection item as the first argument, which is all we need in the majority of cases. It also passes the current index, and the collection itself. Using callback functions that only make use of the first argument is easy. They just work. But if you want to use a function that takes more than just the current item as arguments, then weird things can happen.

Friday, March 6, 2015

Planting values in lodash wrappers

Wrapping values in lodash let's us compose chains of functionality. There's two reasons to do this. The first is that the resulting code is a lot more compact and readable, especially if each chained call is on it's own line. The second reason, now that lazy evaluation is supported in lodash, is efficiency. Some chains won't iterate over the entire collection if it doesn't have to. Now there's a third reason — the plant() function. This function was made available after Lo-Dash Essentials was published, so I'll talk about it here.

Monday, January 26, 2015

Lo-Dash Essentials

I'm pleased to announce my latest book, Lo-Dash Essentials, available now from Packt Publishing. It covers everything you need to get started using Lo-Dash in your projects, as well as some newer features introduced in the 3.0 release.
Here's a rough outline of what you'll find in the book:
  1. Collections and arrays: working with collections and arrays
  2. Objects: working with objects
  3. Functions: functional programming tools
  4. MapReduce: mapping and reducing is fundamental to applicative/functional programming in Lo-Dash
  5. Chained function calls: write elegant, compact code
  6. Application components: glue functions together that realize larger behavior
  7. Other libraries: Lo-Dash working alongside other libraries
  8. Internals and performance: some guiding principles on performance, and how how to get the most of it

Thursday, December 11, 2014

Lo-Dash: Partials and References

The partial() function in Lo-Dash is used to compose higher-order functions, with argument values partially applied. So for example, let's say we have a function that accepts a string argument, like console.log(). We can compose a new function that when called, doesn't need to supply a string argument — it'll use the argument value it was composed with. This is especially powerful in contexts where we don't necessarily have control over how the function is called. As is the case with callback functions. Primitive types, like strings and numbers work well as partial arguments. Things like arrays and objects work fine too, if they're passed to partial() as a literal. If a reference is passed as a partial argument, unexpected things start to happen.

Thursday, December 4, 2014

Lo-Dash: Using the result() function

The result() function is a handy way to look up object properties. If the property happens to be a function, result() will invoke it for us and return the result. Taken at face value, it doesn't seem like there's much to result(). On the contrary, it's a powerful tool. The basic usage looks like this:

var object = { name: 'Lo-Dash' };
_.result(object, 'name');
// → "Lo-Dash"

Tuesday, July 8, 2014

Simpler Lodash Templates

I used to use Lodash template() until I give up due to the verbosity of the interpolation delimiters used. They're a pain, especially when it's a simple string I want to build. Stylistically, I don't want to resort to string concatenation in my code, but, it turned out to be the better option. You can use Lodash for simple string-building scenarios such as these — it just requires a little tweaking.

Friday, July 4, 2014

Caching Autocomplete Responses Using Lodash

I've taken a few approaches to caching responses in the jQuery UI autocomplete widget. A response, in autocomplete parlance, is the data that's displayed when the user is typing in the text input. Each keystroke is a request for data — the response is something the autocomplete must generate by filtering some data source. The filtering operations can be done locally, or remotely. Either way, we stand to benefit from caching these responses.

Thursday, July 3, 2014

Lodash: Descending Sorted Index

The sortedIndex() function available in Lodash performs a binary search on an array to tell you where you can insert the new element in question, and maintain the sort order. Which implies, of course, that the array is already sorted. This function helps you keep it that way. The benefit to keeping and array sorted is that often, you're going to want to present the values of that array in sorted order. So you could, do it just-in-time, by sorting the array when it's about to be displayed. But this has efficiency problems.

Monday, June 30, 2014

Processing Form Data with jQuery and Lodash

I can get most of what I need for processing form data straight out of jQuery. I can listen for the submit event on the form, and I can prevent the form from actually getting submitted, allowing my own behaviour to run. jQuery also helps me collect the form data I need with the serializeArray() function. This function returns an array of form values. Lodash can help me transform this array into something more usable.

Friday, June 27, 2014

The Difference Between Throttle and Debounce

I've always wondered what the difference is between the debounce() and throttle() Lodash functions. It turns out, there is no real difference — throttle() depends on debounce(), which handles all the complex logic of timing function execution. So what does throttle(), do exactly? It sets up the leading and the trailing edge of the timeouts.

Building Collections Using Lodash Invoke

The Lodash invoke() function does more than simply iterate over a given collection, calling the given method on each object. It actually stores the result of each method invocation, effectively building a new collection. This is a powerful idea, especially when it comes to chaining Lodash functions together.

Monday, April 28, 2014

Lodash: Picking Object Properties to Extend

The extend() function is one of those Lodash tools that's so indispensable, you wonder how the core JavaScript language doesn't have something similar built-in. There's another Lodash function I'm developing similar feelings toward — pick(). This function operates on objects, and returns a new object, containing only properties "picked" from the source object. Something like this comes in handy when extending objects.

Friday, April 25, 2014

Lodash: Replacing setTimeout() With delay()

The built-in setTimeout() JavaScript function defers the the execution of a given function till a given number of milliseconds have passed. For the most part, this works perfectly — you pass in a function, and the duration to wait. Things aren't so straightforward when you already have a function defined, and you want to delay it's execution — especially when you want to invoke the function with specific parameters.

Monday, March 31, 2014

Backbone: Combining The where() and toJSON() Collection Methods

The two Backbone collection methods I use most often are probably where(), and toJSON(). However, they're difficult to use together sometimes. For example, I can filter my collection before I actually do anything with it using where(), but this gives me back an array of models. I can't do further Lodash processing on this array, because it has full-fledged models, not objects in it. I can't pass this array to my template context because, again, it's looking for plain objects, and not Backbone model instances.

Finding Keys By Value With Lodash

The Lodash library has a findKey() function that returns the first property name for which the given value is found. The where and pluck style callbacks are supported, as with most lodash functions of this kind. As is passing in our own callback to determine whether the value has been found or not. This is where we can handle some rather subtle use cases. For instance, what happens when one of the properties you're checking against is an array of objects. Meanwhile, one of those objects has the value you're looking for.

Monday, March 17, 2014

Classification With Lodash groupBy()

It's seldom that the API data returned by your application server will have everything you need to render the user interface you're after. Do you ask the API team to extend the data schema to fit your front-end design? Not unless the rewards are huge. And sometimes that's the case. For example, if there's a field that the front-end code needs, and has to jump through hoops in order to get, then you obviously benefit from having the field as part of the API. Especially if it's used everywhere in the UI. What's more common, however, are the edge-cases where we need some piece of data that just isn't part of the API. Extending the API is just way too much effort — much greater than the reward. An example of this type of front-end manipulation is classifying data with the help of lodash.groupBy().

Wednesday, February 26, 2014

Lodash Where Inclusive Or

I really like using the Lodash where() function for filtering arrays. Something about it makes for elegant, condensed code — two very big wins in my opinion. For example, you can pass complex conditions in an object that test for property value equality. Each property is "and"ded together. The where() function loses some of it's power when you want to combine expressions using "and" and "or". Often, the result is me writing a function to do the complex filtering, and passing that to where(). But there's another way to do it.

Thursday, February 13, 2014

Working With JavaScript Object Defaults

The issue is a simple, yet annoying one. You want to access the property of an object, and work with that property value — perhaps calling a method on it, or accessing another property. The issue is that when an object property is undefined, errors happen. For example, if you're expecting a string or an array as a property value, the code would work fine if the property were initialized to an empty string, or an empty array respectively. The trick is, providing sane defaults on objects so that the clients using them don't blow up.