Basics of Functional Programming
Functional programming is a programming paradigm that emphasizes building programs using pure functions, immutable data, and function composition. It avoids state mutations and side effects, making code easier to test and maintain. In JavaScript, functional programming features such as higher-order functions, closures, and currying are widely used.
Pure Functions and Side Effects
A pure function is one that always returns the same output for the same input and produces no side effects. Side effects include modifying external variables, making network requests, or manipulating the DOM. Pure functions are easier to reason about and test.
// Pure function
function add(a, b) {
return a + b;
}
// Impure function (has side effects)
let counter = 0;
function increment() {
counter++;
}
Immutable Data
Immutable data refers to data that cannot be modified after creation. In JavaScript, primitive types (e.g., strings, numbers) are immutable, but objects and arrays are mutable. Functional programming encourages using immutable data to avoid unintended modifications.
// Mutable operation
const arr = [1, 2, 3];
arr.push(4); // Modifies the original array
// Immutable operation
const newArr = [...arr, 4]; // Creates a new array
Higher-Order Functions
Higher-order functions are functions that take other functions as arguments or return functions. Examples in JavaScript include map
, filter
, and reduce
.
const numbers = [1, 2, 3, 4];
// map
const doubled = numbers.map(n => n * 2);
// filter
const evens = numbers.filter(n => n % 2 === 0);
// reduce
const sum = numbers.reduce((acc, n) => acc + n, 0);
Function Composition
Function composition is the process of combining multiple functions into a new function. This can be achieved using compose
or pipe
functions.
const compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
const add1 = x => x + 1;
const multiply2 = x => x * 2;
const addThenMultiply = pipe(add1, multiply2);
console.log(addThenMultiply(3)); // 8
Currying
Currying is a technique for transforming a multi-argument function into a sequence of single-argument functions. Curried functions are easier to reuse and compose.
const curry = fn => {
const arity = fn.length;
return function curried(...args) {
if (args.length >= arity) return fn(...args);
return (...moreArgs) => curried(...args, ...moreArgs);
};
};
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
Recursion
Recursion is a technique where a function calls itself. In functional programming, recursion is often used as an alternative to loops.
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
console.log(factorial(5)); // 120
Lazy Evaluation
Lazy evaluation delays the computation of an expression until its value is actually needed. Lazy sequences can be implemented using generator functions.
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
Functors and Monads
A functor is a type that implements the map
method, and a monad is a functor that implements chain
or flatMap
. They are used to handle side effects and asynchronous operations.
// Maybe functor
class Maybe {
constructor(value) {
this.value = value;
}
static of(value) {
return new Maybe(value);
}
map(fn) {
return this.value == null ? Maybe.of(null) : Maybe.of(fn(this.value));
}
}
const maybeNum = Maybe.of(5).map(x => x * 2);
console.log(maybeNum.value); // 10
Pattern Matching
Pattern matching is a technique for selecting different processing logic based on the structure of data. While not natively supported in JavaScript, it can be simulated using libraries or functions.
const match = (value, patterns) => {
for (const [predicate, handler] of patterns) {
if (predicate(value)) return handler(value);
}
throw new Error('No pattern matched');
};
const result = match(3, [
[x => x === 0, () => 'zero'],
[x => x > 0, x => `positive ${x}`],
[x => x < 0, x => `negative ${x}`]
]);
console.log(result); // "positive 3"
Persistent Data Structures
Persistent data structures retain previous versions when modified, enabling structural sharing. This can be implemented using libraries like Immutable.js.
import { List } from 'immutable';
const list1 = List([1, 2, 3]);
const list2 = list1.push(4);
console.log(list1.toJS()); // [1, 2, 3]
console.log(list2.toJS()); // [1, 2, 3, 4]
Type Systems and Functional Programming
Type systems like TypeScript can help catch errors in functional programming, such as incorrect function composition or type mismatches.
type AddFn = (a: number, b: number) => number;
const add: AddFn = (a, b) => a + b;
type ComposeFn = <A, B, C>(f: (b: B) => C, g: (a: A) => B) => (a: A) => C;
const compose: ComposeFn = (f, g) => x => f(g(x));
Performance Considerations
Functional programming may introduce performance overhead, such as creating new objects and arrays. Trade-offs are necessary in performance-sensitive scenarios.
// Less performant immutable operation
const largeArray = Array(1000000).fill(0);
const newArray = largeArray.map(x => x + 1); // Creates a new array
// More performant mutable operation (when necessary)
for (let i = 0; i < largeArray.length; i++) {
largeArray[i]++;
}
Functional Programming Libraries
The JavaScript ecosystem includes many functional programming libraries, such as Ramda, Lodash/fp, and Folktale.
import R from 'ramda';
const addThenMultiply = R.pipe(
R.add(1),
R.multiply(2)
);
console.log(addThenMultiply(3)); // 8
Asynchronous Programming and Functional
Promises and async/await can be combined with functional programming to handle asynchronous operations.
const fetchData = url => fetch(url).then(res => res.json());
const processData = R.pipe(
fetchData,
R.then(R.map(R.prop('name'))),
R.then(R.filter(R.startsWith('A')))
);
processData('https://api.example.com/users')
.then(console.log);
Testing and Debugging
Pure functions and immutable data make testing and debugging easier because the behavior of the code is more predictable.
// Pure functions are easy to test
function testAdd() {
console.assert(add(1, 2) === 3, 'add should work');
}
// Impure functions are harder to test
function testIncrement() {
counter = 0;
increment();
console.assert(counter === 1, 'increment should work');
}
Functional Reactive Programming
Functional reactive programming (FRP) combines functional programming with reactive programming to handle event streams.
import { fromEvent } from 'rxjs';
import { map, filter } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const positions = clicks.pipe(
filter(evt => evt.clientX > 100),
map(evt => ({ x: evt.clientX, y: evt.clientY }))
);
positions.subscribe(pos => console.log(pos));
Domain Modeling
Functional programming can be used for domain modeling, expressing business logic using algebraic data types and pattern matching.
class Result {
static ok(value) {
return new Ok(value);
}
static error(message) {
return new Error(message);
}
}
class Ok extends Result {
constructor(value) {
super();
this.value = value;
}
map(fn) {
return Result.ok(fn(this.value));
}
}
class Error extends Result {
constructor(message) {
super();
this.message = message;
}
map() {
return this;
}
}
const divide = (a, b) =>
b === 0 ? Result.error('Division by zero') : Result.ok(a / b);
divide(10, 2).map(x => x * 3); // Ok(15)
divide(10, 0).map(x => x * 3); // Error("Division by zero")
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn