[邂逅范式编程 | 青训营笔记]

83 阅读8分钟

1.介绍

课程背景 :

1.前端的主要编程语言为 JavaScript。

2.JavaScript 做为一种融合了多种编程范式的语言,灵活性非常高

3.前端开发人员需要根据场景在不同编程范式间自如切换。

4.进一步需要创造领域特定语言抽象业务问题。

2.编程语言

为什么需要编程语言

从人与人间流的语言类比推出人与机器的语言

机器语言

不同的机器有不同的格式

例如8086指令格式分操作码与操作数

image.png

汇编语言

image.png

高级语言

为适应人的思维

接着出现编译器,将高级语言编译成汇编语言,再转变成机器语言

结果就是很小的源码编译出很大的机器语言

C/C++

C语言:面向过程的突出代表

  • 可对位,字节,地址直接操作

  • 代码和数据分离倡导结构化编程

  • 功能齐全:数据类型和控制逻辑多样化

  • 可移植能力强

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

  • C with Classes

  • 继承

  • 权限控制

  • 虚函数

  • 多态

//下面为一个包括全知识点的C++程序
#include<iostream>
#include<string>
using namespace std;//std空间声明
class Shape//抽象类
{
public:
	virtual float area() const { return 0.0; };
	virtual float volume() const { return  0.0; };
	virtual void shapeName() const = 0;//纯虚函数
};

class Point :public Shape//公有继承
{
public:
	Point(float a= 0, float b= 0)//构造函数
	{
		x = a, y = b;
	}
	void setPoint(float a, float b)
	{
		x = a, y = b;
	}
	float getX() const { return x; }
	float getY() const { return y; }
	virtual void shapeName() const { cout << "Point:"; }
	friend ostream& operator<<(ostream& output, const Point& p);//声明、友元函数实现运算符重载
protected:
	float x, y;
};
ostream& operator<<(ostream& output, const Point& p)//类外实现功能
{
	output << "[" << p.x << "," << p.y << "]" << endl;
	return output;
}

class Circle:public Point//继承
{
public:
	Circle(float x = 0, float y = 0, float r = 0) :Point(x, y), radius(r){ }
	void setRadius(float a) { radius = a; }//参数列表构造
	float getRadius() const { return radius; }
	virtual float area() const { return 3.1415926 * radius * radius; }//返回面积
	virtual void shapeName() const { cout << "Circle:"; }
	friend ostream& operator<< (ostream&, const Circle&);//运算符重载
protected:
	float radius;//自己的私有成员
};
ostream& operator<< (ostream& output, const Circle& c)
{
	output << "[" << c.x << "," << c.y << "],r=" <<c.radius<< endl;
	return output;
}

class Cylinder:public Circle//继承
{
public:
	Cylinder(float x = 0, float y = 0, float r = 0, float h = 0) :Circle(x, y, r), hight(h) { }
	void setHight(float h) { hight = h; }
	virtual float area() const { return 2 * Circle::area() + 2 * 3.1415926 * radius * hight; }
	virtual float volume() const { return Circle::area() * hight; }
	virtual void shapeName() const { cout << "Cylinder:"; }
	friend ostream& operator<< (ostream&, const Cylinder&);
protected:
	float hight;
};
ostream& operator<< (ostream& output, const Cylinder& cy)
{
	output << "[" << cy.x << "," << cy.y << "],r=" <<cy.radius<<",h="<<cy.hight<< endl;
	return output;
}

int main()
{
	Point point(3.2, 4.5);
	Circle circle(2.4, 1.2, 5.6);
	Cylinder cylinder(3.5, 6.4, 5.2, 10.5);
	point.shapeName();
	cout << point;
	circle.shapeName();
	cout << circle;
	cylinder.shapeName();
	cout << cylinder;
	cout << endl;
	Shape* p;//一个基类指针,能指向派生类
	p = &point;
	p->shapeName();
	cout << "x=" << point.getX() << ",y=" << point.getY() << "\narea="
		<< point.area() << "\nvolume=" << p->volume() << endl<< endl;
	p = &circle;
	p->shapeName();
	cout << "x=" << circle.getX() << ",y=" << circle.getY() << "\narea="
		<< circle.area() << "\nvolume=" << p->volume() << endl<<endl;
	p = &cylinder;
	p->shapeName();
	cout << "x=" << cylinder.getX() << ",y=" << cylinder.getY() << "\narea="
		<< cylinder.area() << "\nvolume=" << p->volume() <<endl<< endl;
	return 0;
}

Lisp:函数式语言代表

  • 与机器无关

  • 列表:代码即数据

  • 闭包

(set nums `(1 2 3 4));数据列表

(setq add `+);加操作

(defun run(op exp) (eval (cons op exp)));将数据构建成代码列表

(run add nums);运行

JavaScript

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

  • 过程式

  • 面向对象

  • 函数式

  • 响应式

此外,还有

  • 弱类型:JavaScript是一种弱类型的语言,不需要事先声明变量的类型,变量的类型会在运行时自动推断(在TS中变成强类型)。

  • 解释性:JavaScript是一种解释性的语言,不需要编译成可执行文件,可以直接在浏览器中执行(经典的例如V8引擎会进行处理)。

  • 高阶函数:JavaScript中的函数可以作为参数传递给其他函数,也可以作为返回值返回给其他函数,这种函数称为高阶函数。

  • 闭包:JavaScript中的函数可以形成闭包,即在函数内部定义的变量可以在函数外部访问,这种特性可以实现私有变量和函数的封装。

image.png

3.编程范式

什么是编程范式

image.png

image.png

命令式侧重程序员如何操作机器改变状态,

过程式是把操作用过程分组

面向对象是根据操作和它所面向的对象来调整

过程式编程

  • 自顶向下

  • 结构化编程--替代传统goto结构

  • 顺序结构

image.png

  • 选择结构

image.png

  • 循环结构

image.png

JS中的面向过程

export var car = {
   meter:100,
   speed:10,
};#数据

export function advanceCar(meter){
  while (car < meter){
     car.meter +=car.speed;
      }#算法
}

import {car,advanceCar }from './car';

function main(){
  console.log('before',car);
  
  advanceCar(1000);
  
  console.log('after',car);
}

面向过程问题

  • 数据与算法关联弱
  • 不利于修改和扩充
  • 不利于代码重用

也就是不利于拓展和维护、代码重用

封装

  • 关联数据与算法
class Car {

meter = 100
speed = 100
advance(meter){#数据与算法封装到一个类中去
 while(this.meter<meter){
    this.meter+=this.speed;
  }
}

getSpec(){
  return `meter:${this.meter};speed:${this.speed};`;
 }
}

function main() {
 var car = new Car();
 car.advance(1000);
}

继承

class FlyCar extends Car{
height = 100  #新数据、新算法,可在在原数据上直接扩充
fly(height){
  while (this.height < height){
     this.height += this.speed
   }
 }
}

多态

不同的结构可以进行接口共享、进而达到函数复用

#反复使用接口,公共的类可以使用相同的接口
getSpec(){
  return `meter:${this.meter};speed:${this.speed};`;
 }
}

依赖注入

去除代码耦合

class Car{
engine = new Engine()
wheel =new Wheel()//耦合特定实现
run(){
   this.engine.strat();
   this.wheel.run();
 }
}

function main(){
  var car = new Car();
  car.run();
}

面向对象编程_五大原则

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

面向对象编程有什么缺点?为什么我们推荐函数式编程? image.png

函数式编程

  • 函数是"一等公民"
  • 纯函数/无副作用
  • 高阶函数跟闭包

image.png

优势

  1. 可缓存
  2. 可移植
  3. 可测试
  4. 可推理
  5. 可并行
const retireAge = 60
function retirePerson(p){
  if(p.age > retireAge){
    p.status = "retired"
  }
}
//优化
function retirePerson(p){
  const retireAge = 60
    if(p.age > retireAge){
     return {
       ...p,
       status = "retired"
     }
  }
  return p
}

Currying

  • 基本概念

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

下面我们通过一些实例,拆解和说明一下柯理化的具体含义。

这是一个普通的三元函数,执行运算得出三个参数的和。

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

add(1,2,3) // 6

按照上面的定义,add函数的的柯理化转换过程应该是add(a,b,c) => curriedAdd(a)(b)(c)。实现代码如下:

function curriedAdd(a){
    return function(b){
        return function(c){
            return a + b + c
        }
    }
}

执行验证

add(1,2,3) // 6
curriedAdd(1)(2)(3) // 6

上面是基于极简场景的实现

Currying的实现也是利用闭包和高阶函数,把之前的参数暂存起来,同时返回一个新的函数。

Composition

  • 在计算机科学中,函数组合是将多个简单的函数,组合成一个更复杂的函数的行为或机制。每个函数的执行结果,作为参数传递给下一个函数,最后一个函数的执行结果就是整个函数的结果。

鼓励组合

可以手动组合,但灵活性差

目标用途

  • 避免洋葱嵌套代码,提高代码的可读性
  • 组合多个单一功能函数生成特定功能的新函数
  • 把功能复杂的函数拆解成功能相对单一的函数,便于维护和复用

结合律(Associativity)

在数学中,意指在一个包含有二个以上的可结合运算子的表示式,只要算子的位置没有改变,其运算的顺序就不会对运算出来的值有影响。

加法和乘法都是可结合的:

(x * y) * z === x * (y * z)
(x + y) + z === x + (y + z)

函数组合遵循结合律,意指只要函数的位置没有发生改变,任意组合优先执行,都不影响执行结果。

compose(f,g,h) == compose(compose(f, g), h) == compose(f, compose(g, h))

Functor

  • 可以当做容器的类型,类型支持对容器内元素进行操作
  • 常见的:functor:Array(Iterable).map,Promise.then
a.b != null ? (a.b.c != null ?(a.b.c.d !== a.b.c.d.e :null) : null) :null
new Maybe(a).map(Prop('b')).map(prop('c')).map(prop('d')).map(prop('e'));

Monad

Monad 由以下三个部分组成:

  • 一个类型构造函数(M),可以构建出一元类型 M<T>
  • 一个类型转换函数(return or unit),能够把一个原始值装进 M 中。
    • unit(x) : T -> M T
  • 一个组合函数 bind,能够把 M 实例中的值取出来,放入一个函数中去执行,最终得到一个新的 M 实例。
    • M<T> 执行 T-> M<U> 生成 M<U>

除此之外,它还遵守一些规则:

  • 单位元规则,通常由 unit 函数去实现。
  • 结合律规则,通常由 bind 函数去实现。

单位元:是集合里的一种特别的元素,与该集合里的二元运算有关。当单位元和其他元素结合时,并不会改变那些元素。
乘法的单位元就是 1,任何数 x 1 = 任何数本身、1 x 任何数 = 任何数本身。
加法的单位元就是 0,任何数 + 0 = 任何数本身、0 + 任何数 = 任何数本身。

Applicative

直接对两个容器操作

  • 函子的值可以是函数,我们把value值是函数的函子称之为可应用的函子。但是这样理解不完整,因为我们看不到它的”可应用性“体现在哪里?函数(function)可以被应用,除了可以直接调用,它还有一个apply方法,可以把函数应用于其它对象。
[].map.apply([1,2,3],[n=>n*2]) // [2,4,6]
Identity: Maybe(id).ap(v) === v;
Homomorphism: Maybe(f).ap(Maybe(x)) === Maybe(f(x));
Interchange: v.ap(Maybe(x)) === Maybe(f => f(x)).ap(v);
Composition: Maybe(compose).ap(u).ap(v).ap(w) === u.ap(v.ap(w));

响应式编程

  • 异步/离散的函数式编程
    • 数据流
    • 操作符
      • 过滤
      • 合并
      • 转化
      • 高阶

Observable

  • 观察者模式

image.png

  • 迭代器模式
  • Promise和EventTraget超集*

操作符

响应式编程的"compose"

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

image.png

Monad

  • 去除嵌套的Observable

image.png

image.png

领域特定语言

什么是领域特定语言

  • 领域特定语言 (英语:domain-specific language、DSL)指的是专注于某个 应用程序 领域的 计算机语言 。

    • HTML
    • SQL
  • 与之对应的是通用语言

    • C/C++
    • Javascript
    • ...

特定语言需要由通用语言实现,通用语言无法由特定语言实现

语言运行

image.png

lexer

SQL Token分类

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

Parser_语法规则

上下文无关语法规则

<selectStatement> ::= SELECT <selectList> FROM <tableName>
<selectList> ::= <selectField> [ ,<selectList> ]
<tableName> ::= <tableName> [ ,<tableList> ]

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

Parser_LL

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

function selectStatement(node) {
  node.children.push(matchToken( "select'));
  const selectListNode = { type: 'selectList', children: []};
  node.children.push(selectListNode);
  selectList(selectListNode);
  node.children.push(matchToken( 'from"));
  node.children.push(tableNasme());
}
function selectList({ children }) {
  let node;
  while (node = selectField()) {
     children.push(node);
    }
    
function selectField() {
  if (currentToken().match(/\w+/)) {
     return { type: 'field", token: nextToken() };
   }
}

Parser_LR

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

LL(K) > LR(1) > LL(1),括号里的内容构建语法树需要向下看的数量

image.png