Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Guide: Creating Valid Substitutions for Lodash Functions #385

Open
Uzlopak opened this issue Oct 19, 2023 · 1 comment
Open

Guide: Creating Valid Substitutions for Lodash Functions #385

Uzlopak opened this issue Oct 19, 2023 · 1 comment

Comments

@Uzlopak
Copy link
Contributor

Uzlopak commented Oct 19, 2023

Creating Valid Substitutions for Lodash Functions

This document provides a comprehensive guide on creating valid substitutions for Lodash functions, using the _.isBoolean function as an exemplar.

Retrieving the Original Lodash Functionality

Lodash can be complex, with each function residing in its own file. To simplify the process of determining the original functionality, you can utilize per-method packages provided by Lodash. Follow these steps:

  1. Visit the npm page of the function you want to substitute. In our example, we visit the npm page of the lodash.isboolean package.
  2. Click on the code tab to access the source code of the package, which is bundled in a single file named index.js.

Here is the content of the index.js for reference:

/**
 * lodash 3.0.3 (Custom Build) <https://lodash.com/>
 * Build: `lodash modularize exports="npm" -o ./`
 * Copyright 2012-2016 The Dojo Foundation <http://dojofoundation.org/>
 * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
 * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
 * Available under MIT license <https://lodash.com/license>
 */

/** `Object#toString` result references. */
var boolTag = '[object Boolean]';

/** Used for built-in method references. */
var objectProto = Object.prototype;

/**
 * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
 * of values.
 */
var objectToString = objectProto.toString;

/**
 * Checks if `value` is classified as a boolean primitive or object.
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
 * @example
 *
 * _.isBoolean(false);
 * // => true
 *
 * _.isBoolean(null);
 * // => false
 */
function isBoolean(value) {
  return value === true || value === false ||
    (isObjectLike(value) && objectToString.call(value) == boolTag);
}

/**
 * Checks if `value` is object-like. A value is object-like if it's not `null`
 * and has a `typeof` result of "object".
 *
 * @static
 * @memberOf _
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 * @example
 *
 * _.isObjectLike({});
 * // => true
 *
 * _.isObjectLike([1, 2, 3]);
 * // => true
 *
 * _.isObjectLike(_.noop);
 * // => false
 *
 * _.isObjectLike(null);
 * // => false
 */
function isObjectLike(value) {
  return !!value && typeof value == 'object';
}

module.exports = isBoolean;

Creating the Substitution

Template

Begin with the following template for creating the substitution:

'use strict';

const assert = require('assert');
const _ = require('lodash');

function isBoolean() {
  // TODO
}

// Assertions
// TODO

Assertions or tests are essential to ensure the validity of your substitution. You can use the examples provided in the comments of the original function. It is also recommended to add additional tests while implementing the substitution to ensure the function behaves as expected.

In this example, we use Lodash as the reference implementation and do not test it against the expected result.

From the comments in the original function, extract the following assertions:

assert.strictEqual(_.isBoolean(false), isBoolean(false));
assert.strictEqual(_.isBoolean(null), _.isBoolean(null));

Additionally, include the not documented true case:

assert.strictEqual(_.isBoolean(true), isBoolean(true));

Now, let's start with the implementation.

Implementation

A clear understanding of the original function is crucial:

// isBoolean from lodash
function isBoolean(value) {
  return value === true || value === false ||
    (isObjectLike(value) && objectToString.call(value) == boolTag);
}

Lodash's isBoolean explicitly compares against true and false. While the typeof operator might seem sufficient, benchmarking shows that explicitly comparing with true and false is faster.

Lodash's isBoolean uses isObjectLike in combination with objectToString. This check is for "instantiated" Boolean objects.

Keep in mind that using objects of primitive types is not recommended. However, for the sake of creating a valid substitution, it's necessary to implement this check. You can add a comment to the substitution, indicating that the check can be removed if the user is sure that no Boolean objects are used.

isObjectLike essentially performs a null check and a typeof check. objectToString is a reference to Object.prototype.toString. The variable boolTag is simply a string reference to the toString result of a Boolean object. You can remove this variable and use the string directly.

The final implementation is as follows:

// isBoolean substitute
function isBoolean(value) {
  return (
    value === true ||
    value === false ||
    // not necessary if no Boolean objects are used
    (
      value && // null check
      typeof value === 'object' && // check if it is an object
      Object.prototype.toString.call(value) === '[object Boolean]' // check if it is a Boolean object
    )
  )
}

For the Boolean object branch, include assertions/tests:

// Truthy cases
assert.strictEqual(_.isBoolean(Boolean()), isBoolean(Boolean()));
assert.strictEqual(_.isBoolean(Boolean(true)), isBoolean(Boolean(true)));
assert.strictEqual(_.isBoolean(Boolean(false)), isBoolean(Boolean(false)));

// Instantiated Boolean objects
assert.strictEqual(_.isBoolean(new Boolean(true)), isBoolean(new Boolean(true)));
assert.strictEqual(_.isBoolean(new Boolean(false)), isBoolean(new Boolean(false)));

// Falsy cases
assert.strictEqual(_.isBoolean(new String(true)), isBoolean(new String(true)));
assert.strictEqual(_.isBoolean(new String(false)), isBoolean(new String(false)));

Final Result

Here's the final implementation with assertions:

'use strict';

const assert = require('assert');
const _ = require('lodash');

function isBoolean(value) {
  return (
    value === true ||
    value === false ||
    // Not necessary if no Boolean objects are used
    (
      value && // Null check
      typeof value === 'object' && // Check if it is an object
      Object.prototype.toString.call(value) === '[object Boolean]' // Check if it is a Boolean object
    )
  )
}

// Assertions
assert.strictEqual(_.isBoolean(false), isBoolean(false));
assert.strictEqual(_.isBoolean(null), _.isBoolean(null));
assert.strictEqual(_.isBoolean(true), isBoolean(true));

// Truthy cases
assert.strictEqual(_.isBoolean(Boolean()), isBoolean(Boolean()));
assert.strictEqual(_.isBoolean(Boolean(true)), isBoolean(Boolean(true)));
assert.strictEqual(_.isBoolean(Boolean(false)), is

Boolean(Boolean(false)));

// Instantiated Boolean objects
assert.strictEqual(_.isBoolean(new Boolean(true)), isBoolean(new Boolean(true)));
assert.strictEqual(_.isBoolean(new Boolean(false)), isBoolean(new Boolean(false)));

// Falsy cases
assert.strictEqual(_.isBoolean(new String(true)), isBoolean(new String(true)));
assert.strictEqual(_.isBoolean(new String(false)), isBoolean(new String(false));

Running this code in Node will demonstrate that all assertions pass.

Publishing the Substitution

You can now propose your substitution to the You-Dont-Need-Lodash-Underscore project. You can open an issue to discuss the substitution or create a pull request directly. Follow the patterns outlined in the Readme.md and add your substitution to the appropriate section, listed alphabetically. Use the Lodash website to identify the correct section.

Transform the assertions into Mocha tests in the test/unit/all.js file.

To determine browser support, refer to resources such as the caniuse website or the Mozilla Developer Network.

@Uzlopak
Copy link
Contributor Author

Uzlopak commented Oct 19, 2023

@stevemao

Do you think this is something we want to pin or store as a markdown file in the repo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant