• Functional Mixins
  • Original post by Eric Elliott
  • The Nuggets translation Project
  • Translator: yoyoyohamapi
  • Proofreader: Tina92 REID3290

Smoke Art Cubes to Smoke — MattysFlicks — (CC BY 2.0) )

Note: This is part 7 of the “Writing Software” series on learning functional programming and compositional software techniques from scratch in JavaScript ES6+. About the concept of software composability, see wikipedia on a | < < < return to the first article

Functional Mixins are composable factory functions that are piped together. Each factory function is like an assembly line worker, responsible for adding an additional property or behavior to the original object. Functional mixins don’t rely on an underlying factory function or constructor; we simply plug any object into the Mixin pipeline and get an enhanced version of that object at the pipe exit.

Functional mixins have some characteristics:

  • Data can be private (through closures).
  • Private state can be inherited.
  • Multiple inheritance can be implemented.
  • There is no diamond problem. There is a principle in functional mixins implemented in JavaScript — last in wins.
  • Base classes are not required.

motivation

Today’s software development is all about composition: we divide large, complex problems into smaller, simpler ones, and the solutions to those smaller problems eventually form our applications.

The composition has the following two basic elements:

  • function
  • The data structure

These basic elements make up the application structure. Typically, composite objects are created through class inheritance (when a class inherits many functions from its parent and then enhances itself through extension or overloading). The problem with class inheritance is that it describes an IS-A thinking, for example, “an administrator is also an employee,” which creates a lot of design problems:

  • Tight coupling problem: Because subclasses depend on the implementation of their parent class, class inheritance inevitably leads to the tightest coupling in object-oriented design.
  • Base class vulnerability: Due to tight coupling, changes to a base class can break a large number of subclasses — and potentially even change code managed by third parties. The author may have broken the code without knowing it.
  • Inflexible inheritance hierarchy: Since each class evolved from an ancestor classification, over time, it becomes difficult to determine the category for a new use case. (For example, should the green truck class be inherited from the truck class or from the green class?)
  • Forced replication problems: Due to inflexible inheritance hierarchies, new use cases are often implemented by copying rather than extending, which creates possible ambiguity between similar classes. Once the replication problem arises, it becomes ambiguous which class the new class should inherit from and why.
  • Orangutan and banana Problem: “The problem with object orientation is that you have to build an entire hidden environment to solve the problem. It’s like asking for a banana and getting a banana with a gorilla and the jungle.” ~ Joe Armstrong describes object orientation in his book Coders at Work.

In the “Think of an administrator as an employee” (IS-A) mindset, how do you implement a scenario through class inheritance that hires an external consultant to perform some administrative work on a temporary basis? Class inheritance may work well if you know the requirements ahead of time, but at least I’ve never personally met anyone who knows how to do it. As the size of the application expands, more efficient ways of extending functionality emerge.

Mixins come out of nowhere, providing flexibility that class inheritance can’t.

What is a Mixin?

The quote “Prioritize Object composition over class inheritance” comes from the Work of the Gang of Four (GoF) on Design Patterns: Elements of Reusable Object Oriented Software

A Mixin is a composition of objects in which a component feature is mixed into a composite object so that the features of each Mixin become features of the composite object.

The term “mixins” in object-oriented programming comes from dessert shops that sell self-service flavors of ice cream. In such an ice cream shop, you can’t buy a variety of flavors of ice cream, you can only buy a plain ice cream, and then according to your own taste, add other flavors of sauce.

The Mixin process for objects is similar: You start with an empty object and extend it by constantly mixing in new features. Since JavaScript supports dynamic object extensions Obj. newProp = XXX), and objects don’t depend on classes, so mixins in JavaScript are incredibly simple, making mixins the most common inheritance method in JavaScript. Here’s an example of how to get a multi-flavor ice cream:

const chocolate = {
  hasChocolate: (a)= > true
};

const caramelSwirl = {
  hasCaramelSwirl: (a)= > true
};

const pecans = {
  hasPecans: (a)= > true
};

const iceCream = Object.assign({}, chocolate, caramelSwirl, pecans);

/* // If your environment supports destruct assignment, you can also do this: const iceCream = {... chocolate, ... caramelSwirl, ... pecans}; * /

console.log(`
  hasChocolate: ${ iceCream.hasChocolate() }
  hasCaramelSwirl: ${ iceCream.hasCaramelSwirl() }
  hasPecans: ${ iceCream.hasPecans() }
`);Copy the code

The program output is as follows:

hasChocolate: true
hasCaramelSwirl: true
hasPecans: trueCopy the code

What is functional inheritance?

One of the most important functions is directly applied to an object instance by using Functional Inheritance to increase object features. Functions can implement data privacy through closures, and enhancement functions use dynamic object extensions to add new properties or methods to an object.

Let’s take a look at Douglas Crackford’s example of functional inheritance:

// Base object factory
function base(spec) {
    var that = {}; // Create an empty object
    that.name = spec.name; // Add a "name" attribute to the object
    return that; // This object is returned when production is complete
}

// Construct a child object generated (inherited) from the underlying object factory
function child(spec) {
    // Create objects using the "base" constructor
    var that = base(spec);
    // Dynamically extend objects by enhancing functions
    that.sayHello = function() {
        return 'Hello, I\'m ' + that.name;
    };
    return that; // Return this object
}

// Usage
var result = child({ name: 'a functional object' });
console.log(result.sayHello()); // "Hello, I'm a functional object"Copy the code

Because child() is tightly coupled to base(), we have to face the problems of class inheritance when we create more grandchild objects grandChild () and greateGrandChild().

What are functional mixins?

Extending objects with functional mixins relies on composable functions that can mix new features into a given object. The new property or behavior comes from the specified object. Functional mixins do not rely on the base object construction factory, passing any object and mixing it into an extended object.

We see an example where Flying () will add the ability to fly to an object:

// Flying is a composable function
const flying = o= > {
  let isFlying = false;

  return Object.assign({}, o, {
    fly () {
      isFlying = true;
      return this;
    },

    isFlying: (a)= > isFlying,

    land () {
      isFlying = false;
      return this; }}); };const bird = flying({});
console.log( bird.isFlying() ); // false
console.log( bird.fly().isFlying() ); // trueCopy the code

Notice that when we call the Flying () method, we need to pass in the object to be extended, and functional mixins serve function combinations. We create a yell Mixin. When we pass the yell function quack, quacking() this Mixin adds the ability to yell to the object:

const quacking = quack= > o => Object.assign({}, o, {
  quack: (a)= > quack
});

const quacker = quacking('Quack! ') ({});console.log( quacker.quack() ); // 'Quack! 'Copy the code

Composition of functional mixins

Functional mixins can be composed by a simple composition function. Objects now have the ability to fly and shout:

const createDuck = quack= > quacking(quack)(flying({}));

const duck = createDuck('Quack! ');

console.log(duck.fly().quack());Copy the code

This code may not be easy to read, and it is not easy to debug or change the order of composition.

This is a standard way to compose functions, and as we learned in the previous sections, a more elegant way to compose functions is to compose () or pipe(). If we use the pipe() method to reverse the composition order of functions, the composition can be read as object.assign ({},…). Or {… object, … Spread}, which ensures that mixins are in declarative order. If attribute conflicts occur, they are handled in a backward efficient manner.

const pipe = (. fns) = > x => fns.reduce((y, f) = > f(y), x);
// If you don't want to use custom 'pipe()'
// Pipe from 'lodash/fp/flow' can be imported

const createDuck = quack= > pipe(
  flying,
  quacking(quack)
)({});

const duck = createDuck('Quack! ');

console.log(duck.fly().quack());Copy the code

When to use functional mixins?

You should use the simplest abstraction possible to solve the problem. The first thing you should think about is the simplest pure function. If the object needs to maintain a persistent state, consider using factory functions. If you need to build more complex objects, consider functional mixins.

Here are some examples of functional mixins:

  • Application state management, such as Redux Store.
  • Specific cross-cutting concerns and services, such as a centralized log management.
  • UI components with lifecycle hooks.
  • Composable data types, for example, JavaScriptArrayTypes are implemented through mixinsSemigroup,Functor,FoldableAnd so on.

Some algebraic structures may be derived from other algebraic structures, which means that a particular derivation can be combined into new data types without the need for a new custom implementation.

Pay attention to the

Most problems are solved by pure functions, but functional mixins are not. Like class inheritance, functional mixins have their own problems, and they can even reproduce the problems of class inheritance.

You can avoid this problem by following these tips:

  • When necessary, consider implementations from left to right: pure functions > factory functions > functional mixins > classes.
  • Avoid using “IS-A” relationships to organize objects, mixins, and data types.
  • Avoid implicit dependencies between mixins. In any case, functional mixins should not maintain their own state and do not require other mixins. Implicit dependencies will be explained later.
  • “Functional Mixin” does not mean “functional programming.”

class

Class inheritance is almost (and probably never) the best way to extend functionality in JavaScript, but not everyone thinks that way, so you can’t control the use of classes and class inheritance by some third-party library or framework. In this case, for libraries or frameworks that use the class keyword, do the following:

  1. You are not required (developers who use these libraries or frameworks) to extend your own classes with their classes (you are not required to build a multi-level class hierarchy).
  2. You are not required to use it directlynewKeyword, in other words, the framework takes care of the object instantiation process.

Angular 2+ and React meet these requirements, so as long as you don’t extend your own classes, you can safely use them. React allows you to build components without classes, but your components may lose some of the optimizations provided by some of the base classes in React, and your components may not work as described in the documentation sample. Even so, whenever you use React, you should prioritize building components in functional form.

The performance of the class

In some browsers, classes may bring some JavaScript engine optimizations. However, in the vast majority of scenarios, these optimizations won’t significantly improve your application’s performance. In fact, for years, people didn’t have to worry about the performance difference with class. No matter how you build objects, object creation and property access are fast enough (millions of OPS per second).

Of course, this is not to say that the authors of RxJS and Lodash should not look at the performance benefits of using class to create objects. The point is that unless you run into serious performance bottlenecks in reducing the use of classes, your optimizations should focus on building clean, flexible code rather than worrying about performance without losing classes.

Implicit reliance on

You might be interested in how to create functional mixins and get them to work together. Imagine now that you want to build a configuration manager for your application that generates configurations for the application and warns when code tries to access configurations that don’t exist.

Maybe you’ll do this:

/ / log Mxin
const withLogging = logger= > o => Object.assign({}, o, {
  log (text) {
    logger(text)
  }
});

// There is no explicit reliance on log mixins: withLogging in configuration mixins
const withConfig = config= > (o = {
  log: (text = ' ') = > console.log(text)
}) => Object.assign({}, o, {
  get (key) {
    return config[key] == undefined ?

      // VVV is implicitly dependent on VVV
      this.log(`Missing config key: ${ key }`) :
      // ^^^ ^ implicit dependency ^^^config[key] ; }});// Due to dependency hiding, another module needs to introduce withLogging and withConfig
const createConfig = ({ initialConfig, logger }) = >
  pipe(
    withLogging(logger),
    withConfig(initialConfig)
  )({})
;

// elsewhere...
const initialConfig = {
  host: 'localhost'
};

const logger = console.log.bind(console);

const config = createConfig({initialConfig, logger});

console.log(config.get('host')); // 'localhost'
config.get('notThere'); // 'Missing config key: notThere'Copy the code

In this implementation, the Mixin withConfig relies on the log method of o to add functionality to object O, so you need to ensure that O has the log method.

Or you might do something like this:

import withLogging from './with-logging';

const addConfig = config= > o => Object.assign({}, o, {
  get (key) {
    return config[key] == undefined ?
      this.log(`Missing config key: ${ key }`) : config[key] ; }});const withConfig = ({ initialConfig, logger }) = > o =>
  pipe(

    // VVV explicitly depends on VVV in this combination
    withLogging(logger),
    // ^^^ explicitly depends on ^^^ ^ in this combination

    addConfig(initialConfig)
  )(o)
;

// The configuration factory now only needs to know withConfig
const createConfig = ({ initialConfig, logger }) = >
  withConfig({ initialConfig, logger })({})
;

const initialConfig = {
  host: 'localhost'
};

const logger = console.log.bind(console);

const config = createConfig({initialConfig, logger});

console.log(config.get('host')); // 'localhost'
config.get('notThere'); // 'Missing config key: notThere'Copy the code

In this implementation, withConfig explicitly relies on withLogging. Therefore, o is not guaranteed to have a log method, but withLogging provides log capability for O.

The choice of implementation depends on a number of factors. It is possible to use enhanced data types to make functional mixins work, but if so, API conventions need to be clearly designed in function signatures and API documentation.

This is why the default value for o is set in implicitly dependent versions. Since JavaScript lacks the ability to declare types, we can only use default values to ensure that the types are correct:

const withConfig = config= > (o = {
  log: (text = ' ') = > console.log(text)
}) => Object.assign({}, o, {
  // ...
})Copy the code

If you use TypeScript or Flow, it’s better to declare an explicit interface for object requirements.

Functional mixins and functional programming

The “functional” that permeates functional mixins does not imply that such mixins have the functional purity that functional programming advocates. In fact, functional mixins are often object-oriented and full of side effects. Many functional mixins change the object you pass in, so be aware of this.

On the other hand, some developers may prefer a functional style of programming and, therefore, do not maintain a reference identifier for incoming objects. When writing mixins, assume that the style of code that uses them is not only functional, but also object-oriented, or even a mixture of styles.

This means that if you need to return an object instance, return this instead of the object instance reference in the closure. In the functional coding style, object instance references in closures may reflect something other than an object. In the following code, fly() returns this instead of the o stored in the closure:

const flying = o= > {
  let isFlying = false;

  return Object.assign({}, o, {
    fly () {
      isFlying = true;
      return this;
    },

    isFlying: (a)= > isFlying,

    land () {
      isFlying = false;
      return this; }}); };Copy the code

Also, you need to know that objects are extended by object.assign () or {… object, … Spread} implements this, which means that if your object has non-enumerable attributes, they will not appear on the final object:

const a = Object.defineProperty({}, 'a', {
  enumerable: false.value: 'a'
});

const b = {
  b: 'b'
};

console.log({... a, ... b});// { b: 'b' }Copy the code

If you are using functional mixins and not functional programming, don’t expect them to be pure. Instead, you have to assume that the underlying objects to be extended can be mutable, that mixins are full of side effects, and that there is no guarantee of reference transparency, i.e., that it is generally unsafe to cache factories composed of functional mixins.

conclusion

A functional Mixin is a set of composable factory functions that add properties or behavior to an object, like the sites on an assembly line. Functional mixins express has-A, uses-A, or can-do thinking patterns by helping objects acquire features from multiple sources, compared to the “IS-A” thinking pattern of class inheritance.

It’s important to note that “functional Mixin” doesn’t imply “functional programming,” it just describes “mixins implemented using functions.” Of course, functional mixins can also be written in a functional programming style to help avoid side effects and make references transparent. Functional mixins provided by third-party libraries, however, can be fraught with side effects and uncertainty.

  • Unlike simple object mixins, functional mixins allow for true data privacy through closures, as well as inheritance of private data.
  • Unlike class inheritance with a single ancestor, functional mixins can support multiple ancestors, in which case they act like decorators, traits, or multiple inheritance.
  • Different from multiple inheritance in C++, functional Mixin implemented with JavaScript basically does not have the problem of diamond when facing multiple inheritance. In case of attribute or method conflict, the last Mixin will be considered as the winner and will adopt the features provided by it.
  • Unlike class decorators, characteristics, or multiple inheritance, functional mixins do not require a base class.

Finally, remember not to make things complicated, functional mixins are not necessary, and your solution to a problem should be:

Pure functions > factory functions > functional Mixin > classes

To be continued…

The following

Want to learn more about JavaScript functional programming?

Learn Javacript with Eric Elliott, now or never!

Eric Elliott is the author of “Write JavaScript Apps” (O ‘Reilly) and “Learn JavaScript with Eric Elliott.” He has contributed to many companies and organizations, such as Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN and BBC, and is a top artist for many organizations, Including but not limited to Usher, Frank Ocean and Metallica.

He spent most of his time in the San Francisco Bay Area with the most beautiful women in the world.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. Android, iOS, React, front end, back end, product, design, etc. Keep an eye on the Nuggets Translation project for more quality translations.