阿里云主机折上折
  • 微信号
Current Site:Index > Basics of Functional Programming

Basics of Functional Programming

Author:Chuan Chen 阅读数:19028人阅读 分类: JavaScript

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

上一篇:内存泄漏问题

下一篇:设计模式实现

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.