编程范式 | 青训营笔记

127 阅读8分钟

写在前面

编程范式是一种编程风格或方法,它指导程序员如何组织和结构化代码。常见的编程范式包括面向对象编程(OOP)函数式编程(FP)过程式编程(PP)等。作为一枚程序猿,在选择编程范式时,程序员应该考虑程序的需求和目标,以及他们自己的编程风格和偏好。不同的编程范式适用于不同的情况和问题。

编程语言

为什么需要编程语言?编程语言的存在是为了让程序员更容易地编写和理解计算机程序。编程语言提供了一种结构化的方式来表达计算机程序,使程序员能够更轻松地编写和维护代码。

为什么需要编程语言

机器语言

机器语言是一种计算机语言,它由二进制代码组成,用于直接控制计算机硬件。尽管机器语言很难编写和理解,但它是计算机能够理解和执行的唯一语言。程序员通常使用更高级别的编程语言来编写程序,并使用编译器或解释器将其转换为机器语言,以便更轻松地编写和维护代码。

机器语言

汇编语言

汇编语言是一种更高级别的机器语言,它使用助记符来代替二进制代码,使程序员更容易地编写和理解代码。尽管汇编语言比机器语言更容易编写和理解,但它仍然比高级编程语言更难以理解和维护。因此,程序员通常使用高级编程语言来编写程序,并使用编译器将其转换为汇编语言或机器语言。这种转换过程使程序员能够更轻松地编写和维护代码,并且使程序更易于移植到不同的计算机系统上

汇编语言

高级语言

高级编程语言是一种相对于机器语言和汇编语言而言更高级别的编程语言。高级编程语言使用更接近自然语言的语法和结构,使程序员更容易编写和理解代码。高级编程语言通常具有更强大的抽象能力和更丰富的库,使程序员能够更轻松地编写和维护复杂的程序。

高级语言编译

C/C++

C:“中级语言”过程式语言代表

  • 可对位、字节、地址直接操作
  • 代码和数据分离倡导结构化编程
  • 功能齐全:数据类型和控制逻辑多样化
  • 可移植能力强

C++:面向对象语言代表

  • C with Classes
  • 继承
  • 权限控制
  • 虚函数
  • 多态

Lisp

函数式语言代表

  • 与机器无关
  • 列表:代码即数据
  • 闭包

JavaScript

基于原型和头等函数的多范式语言

  • 过程式
  • 面向对象
  • 函数式
  • 响应式

编程范式

什么是编程范式?从程序语言特性上看,它包括是否允许副作用操作的执行顺序代码组织状态管理以及语法和词法

常见的编程范式

编程范式包含命令式以及声明式,具体如下:

常见编程范式

过程式编程

  • 自顶向下

自顶向下

  • 结构化编程

结构化编程

JS中的面向过程

// 定义一个函数,它接受两个数字并返回它们的和
function add(a, b) {
  return a + b;
}

// 定义一个函数,它接受一个数字并返回它的平方
function square(x) {
  return x * x;
}

// 定义一个函数,它接受一个数字并返回它的倒数
function reciprocal(x) {
  return 1 / x;
}

// 定义一个函数,它接受一个数字并返回它的平方根
function squareRoot(x) {
  return Math.sqrt(x);
}

// 使用这些函数来计算以下表达式的值:(3 + 4)² + (1 / 2) + √9
const result = square(add(3, 4)) + reciprocal(2) + squareRoot(9);

console.log(result); // 输出 26.5

思考:面向过程式编程有什么缺点?为什么后面会出现面向对象?

面向过程式编程的缺点包括代码重用性差可维护性差可扩展性差可重用性差。这是因为在过程式编程中,代码通常是以线性方式组织的,而不是以对象或模块的方式组织的。这使得代码难以重用、维护和扩展。此外,过程式编程通常需要使用全局变量和共享状态,这会导致代码的可读性和可靠性降低。面向对象编程(OOP)被广泛认为是一种更好的编程范式,因为它提供了更好的代码组织、重用和维护。在OOP中,代码被组织成对象,每个对象都有自己的状态和行为。这使得代码更易于理解、扩展和维护

面向对象编程

面向对象有几个重要的概念,分别是:封装继承多态以及依赖注入

  • 封装:关联数据与算法
  • 继承:无需重写的情况下进行功能扩充
  • 多态:不同的结构可以进行接口共享,进而达到函数复用
  • 依赖注入:去除代码耦合

面向对象有五大原则,分别是:

  • 单一职责原则SRP(Single Responsibility Principle)
  • 开放封闭原则OCP(Open-Close Principle)
  • 里式替换原则LSP(theLiskovSubstitution Principle LSP)
  • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
  • 接口分离原则ISP(the Interface Segregation Principle ISP)

思考:面向对象编程有什么缺点?为什么我们推荐函数式编程?

面向对象编程(OOP)的缺点包括复杂性高可测试性差可扩展性差可重用性差。这是因为在OOP中,代码通常是以对象的方式组织的,每个对象都有自己的状态和行为。这使得代码更加复杂,因为对象之间的交互和依赖关系可能会变得非常复杂。此外,OOP通常需要使用继承和多态等概念,这些概念可能会导致代码的可测试性、可扩展性和可重用性降低。函数式编程(FP)被广泛认为是一种更好的编程范式,因为它提供了更好的代码组织、重用和维护。在FP中,函数是一等公民,它们可以像变量一样传递和操作。这使得代码更加简单、可读性更高、可测试性更好、可扩展性更好和可重用性更好

函数式编程

函数式编程将函数作为“第一等公民”,它引入了无副作用的纯函数以及高阶函数闭包

函数式编程

一级函数

在函数式编程中,聚合是指将多个值合并为一个值的过程,而转发是指将一个值转换为另一个值的过程。

function map(array, transform) {
  var result = [];
  for (var i = 0; i < array.length; i++) {
    result.push(transform(array[i]));
  }
  return result;
}

function filter(array, test) {
  var result = [];
  for (var i = 0; i < array.length; i++) {
    if (test(array[i])) {
      result.push(array[i]);
    }
  }
  return result;
}

function reduce(array, combine, start) {
  var current = start;
  for (var i = 0; i < array.length; i++) {
    current = combine(current, array[i]);
  }
  return current;
}

var numbers = [1, 2, 3, 4, 5];

var doubled = map(numbers, function(n) {
  return n * 2;
});

var evens = filter(numbers, function(n) {
  return n % 2 === 0;
});

var sum = reduce(numbers, function(a, b) {
  return a + b;
}, 0);

console.log(doubled); // 输出 [2, 4, 6, 8, 10]
console.log(evens); // 输出 [2, 4]
console.log(sum); // 输出 15
纯函数

纯函数是指没有副作用并且对于相同的输入始终返回相同的输出的函数。纯函数的优势有:可缓存可移植可测试可推理可并行

function add(a, b) {
  return a + b;
}
函数柯里化

函数柯里化是一种将接受多个参数的函数转换为一系列接受单个参数的函数的技术。

function curry(func) {
  return function curried(...args) {
    if (args.length >= func.length) {
      return func.apply(this, args);
    } else {
      return function(...args2) {
        return curried.apply(this, args.concat(args2));
      }
    }
  };
}

function add(a, b, c) {
  return a + b + c;
}

var curriedAdd = curry(add);

console.log(curriedAdd(1)(2)(3)); // 输出 6
console.log(curriedAdd(1, 2)(3)); // 输出 6
console.log(curriedAdd(1)(2, 3)); // 输出 6
函数组合

函数组合是一种将多个函数组合成一个新函数的技术。这种技术可以帮助我们编写更加灵活和可重用的代码。

function compose(...funcs) {
  return function composed(result) {
    return funcs.reduceRight(function(acc, func) {
      return func(acc);
    }, result);
  };
}

function add1(x) {
  return x + 1;
}

function double(x) {
  return x * 2;
}

var add1ThenDouble = compose(double, add1);

console.log(add1ThenDouble(1)); // 输出 4
Functor

可以当做容器的类型,类型支持对容器内元素进行操作

常见的functor:Array (Iterable).map,Promise.then。

Functor

Monad

可以去除嵌套容器的容器类型。常见monad:Array.flatMapPromise.then。

Monad

Applicative

直接对两个容器直接操作

Applicative

响应式编程

异步/离散的函数式编程

  • 数据流
  • 操作符
    • 过滤
    • 合并
    • 转化
    • 高阶
Observable
  • 观察者模式
  • 迭代器模式
  • Promise/EventTarget超集

observable

操作符

响应式编程的“compose”

  • 合并
  • 过滤
  • 转化
  • 异常处理
  • 多播

操作符

Monad

去除嵌套的Observable

Monad

领域特定语言

什么是领域特定语言?领域特定语言(Domain-Specific Language,DSL)是一种专门用于解决特定领域问题的编程语言。与通用编程语言不同,DSL通常具有更高的抽象级别和更严格的语法限制,以便更好地适应特定领域的需求。

function createPerson(name, age) {
  return {
    name: name,
    age: age,
    greet: function() {
      console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
  };
}

var john = createPerson('John', 30);
john.greet(); // 输出 "Hello, my name is John and I am 30 years old."

语言运行

语言运行

lexer

SQL Token分类

  • 注释
  • 关键字
  • 操作符
  • 空格
  • 字符串
  • 变量

lexer

Parser

语法规则为上下文无关语法规则。

  • 推导式:表示非终结符到(非终结符或终结符)的关系。
  • 终结符:构成句子的实际内容。可以简单理解为词法分析中的token。
  • 非终结符:符号或变量的有限集合。它们表示在句子中不同类型的短语或子句。

LL:从左到右检查,从左到右构建语法树。

LR:从左到右检查,从右到左构建语法树。