API design
Starting points in the API design are:
- We want to embrace pure functions over a monolithic, stateful instance.
- We want a layered API: offer ready-made functions out of the box, but also allow using low level factory functions.
- We want the API to be easy to understand and remember.
- We want as little magic as possible, give the user full control.
- We want to be able to pick just the functions that you actually use.
- We want to be able to pick just the data types that you use.
Concepts
The currently proposed API has the following concepts:
A. Loading functions
There are three ways to load a function:
-
Use ready made functions:
import { add, multiply } from 'mathjs' -
Create a function yourself using
createand ready made collections of factory functions, one collection per function.A factory function (like
createAdd) typically has a number of dependencies (liketyped,matrix, ...). To make it easy to load a function and its dependencies, collections of factory functions are available likeaddDependencies. This collection is an object holding the factory function of the function that you want to create and all its dependencies.import { create, divideDependencies, sinDependencies, piDependencies } from 'mathjs' const { divide, sin, pi } = create({ divideDependencies, sinDependencies, piDependencies })A shortcut to create all functions is using
all:import { create, all } from 'mathjs' const math = create(all) -
Create a function yourself using a factory function, providing all dependencies yourself:
import { createHypot } from 'mathjs/factories' const hypot = createHypot({ typed: ..., abs: ..., addScalar: ..., divideScalar: ..., multiplyScalar: ..., sqrt: ..., smaller: ..., isPositive: ... })
B. Configuration
Configuration can be set in two ways:
-
Specify configuration statically when creating functions:
import { create, divideDependencies, sinDependencies, piDependencies } from 'mathjs' const config = { ... } const { divide, sin, pi } = create({ divideDependencies, sinDependencies, piDependencies }, config) -
Create a mathjs instance and change config there dynamically:
import { create, divideDependencies, sinDependencies, piDependencies } from 'mathjs' const mathjs = create({ divideDependencies, sinDependencies, piDependencies }) mathjs.config({ ... }) // use mathjs.divide, mathjs.sin, and mathjs.pi
C. Data types
There are different pre-made versions of all functions. Currently there are "full" functions support all data types (number, BigNumber, Complex, Fraction, Unit, Matrix, etc), and "number" functions just supporting plain numbers. In the future, it is possible to create versions only supporting BigNumbers for example.
-
Load "full" versions of the functions:
import { add, multiply } from 'mathjs' -
Load functions only supporting numbers:
import { add, multiply } from 'mathjs/number' // ES6const { add, multiply } = require('mathjs/es5/number') // ES5
Use cases
To bundle all example use cases and run and explore them (linux, unix), run:
sh bundle.sh
The following use cases are worked out as an example:
-
use a few functions
import { divide, sin, pi } from 'mathjs' console.log(divide(sin(divide(pi, 2)), 3)) // sin(pi / 2) / 3 = // number 0.3333333333333333 -
use a few functions with config
import { create, divideDependencies, sinDependencies, piDependencies } from 'mathjs' const config = { number: 'BigNumber' } const { divide, sin, pi } = create({ divideDependencies, sinDependencies, piDependencies }, config) console.log(divide(sin(divide(pi, 2)), 3).toString()) // sin(pi / 2) / 3 = // BigNumber 0.3333333333333333333333333333333333333333333333333333333333333333 -
use all functions in the expression parser
import { evaluate } from 'mathjs' console.log(evaluate('sin(pi / 2) / 3')) // number 0.3333333333333333 -
use all functions in the expression parser with config
import { create, all } from 'mathjs' const config = { number: 'BigNumber' } const { evaluate } = create(all, config) console.log(evaluate('sin(pi / 2) / 3').toString()) // BigNumber 0.3333333333333333333333333333333333333333333333333333333333333333 -
use a few functions with just number support
import { divide, sin, pi } from 'mathjs/number' console.log(divide(sin(divide(pi, 2)), 3)) // sin(pi / 2) / 3 = // number 0.3333333333333333 -
Use all functions and dynamically change config
import { create, all } from 'mathjs' const mathjs = create(all) console.log(mathjs.divide(mathjs.sin(mathjs.divide(mathjs.pi, 2)), 3)) // sin(pi / 2) / 3 = // number 0.3333333333333333 mathjs.config({ number: 'BigNumber' }) console.log(mathjs.divide(mathjs.sin(mathjs.divide(mathjs.pi, 2)), 3).toString()) // sin(pi / 2) / 3 = // BigNumber 0.3333333333333333333333333333333333333333333333333333333333333333 -
create functions yourself
import { createHypot, createTyped } from 'mathjs/factories' // Create a hypot instance that only works with numbers: const typed = createTyped({}) const hypot = createHypot({ typed, abs: Math.abs, addScalar: (a, b) => a + b, divideScalar: (a, b) => a / b, multiplyScalar: (a, b) => a * b, sqrt: Math.sqrt, smaller: (a, b) => a < b, isPositive: a => a > 0 }) // Use the created function: console.log('hypot(3, 4) =', hypot(3, 4)) // 5
Tree shaking results
To get an idea what the size of the bundles is after tree-shaking:
| Use case | Description | Minified + Gzipped size |
|---|---|---|
| Full library | Just everything in a bundle | 138 KB |
| Use case 1 | Use a few functions | 43 KB |
| Use case 2 | Use a few functions with config | 41 KB |
| Use case 3 | Use all functions in the expression parser | 105 KB |
| Use case 4 | Use all functions in the expression parser with config | 131 KB |
| Use case 5 | Use a few functions with just number support | 12 KB |
| Use case 6 | Use all functions and dynamically change config | 131 KB |
| Use case 7 | Create functions yourself | 6 KB |
Some observations:
- All "full" versions of most functions include support for numbers, BigNumbers, Complex numbers, Fractions, Units, and matrices, and require typed-function support. This causes a high "base" size in the order of 40 KB: the full libraries for data types like BigNumber and Complex.
- The selected "few" functions in use cases 1, 2, and 5 are
sin,divide, andpi. Here,divideis a very large function since the matrix implementation involves calculating the inverse, the inverse requires calculating the determinant, the determinant requires calculating the LU decomposition, etc.