highlight: a11y-dark
通过文章可以学习到-
-
- 了解不同编程范式的起源和适用场景。
-
- 掌握 JavaScript 在不同的编程范式特别是函数式编程范式的使用。
-
- 掌握创建领域特定语言的相关工具和模式。
一、编程语言
(一)为什么需要编程语言?
编程语言允许我们以一种计算机能够理解的方式来表达我们的想法和指令。编程语言的发展经历了几个阶段,从最初的机器语言(0和1代码),到汇编语言,再到高级语言。
不同的编程语言适用于不同的应用领域,具有不同的编译效率、代码质量和执行速度要求。此外,它们还可以提供不同的体验度和友好性。
(二)高级语言
高级语言的表达方式和人类语言的表达方式很接近,高级语言的种类有很多,如C、C++、Python、Java等等。每种高级语言都至少有一个编译器,编译器就是把对应的语言转换成机器语言。
C语言
1、面向过程的、抽象化的通用程序设计语言。
2、可对位、字节、地址直接进行操作。
3、代码和数据分离,倡导结构化编程。
4、功能齐全:数据类型和控制逻辑多样化
5、可移植能力强
额外补充一些其他特点:
6、C语言简洁、紧凑,使用方便、灵活。C语言一共只有37个关键字、9种控制语句,程序书写形式自由,主要用小写字母表示,压缩了一切不必要的成分。
7、具有丰富的运算符和数据类型。
8、通过指针类型更可对内存直接寻址以及对硬件进行直接操作。
示例1:操作指针的例子
#include <stdio.h>
int main()
{
int a = 10;
int *p = &a; // 定义一个指针变量p,指向变量a的地址
*p = 100; // 通过指针p来修改变量a的值
printf("a = %d\n", a); // 输出结果为:a = 100
return 0;
}
定义了一个指针变量p,并将它初始化为指向变量a的地址。然后,通过指针p来修改变量a的值。最后,输出变量a的值,可以看到它已经被修改为10了。
示例2:调用C语言标准函数库中的printf()和strlen()函数:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Hello, world!";
printf("%s\n", str); // 输出字符串
printf("The length of the string is %lu\n", strlen(str)); // 输出字符串的长度
return 0;
}
这个例子中包含了stdio.h和string.h两个头文件,它们分别定义了printf()和strlen()两个函数。然后,在main()函数中,调用了这两个函数来输出字符串和计算字符串的长度。
C++(面向对象语言代表)
1、C + Classes 在C的基础上发展而来的。
2、封装: 封装是指将数据和操作数据的方法捆绑在一起,形成一个类。这样可以隐藏类的内部实现细节,只向外界暴露必要的接口,从而保护类中的数据安全。
3、继承: 继承是指一个类(派生类)可以继承另一个类(基类)的成员变量和成员函数。这样可以实现代码复用,减少重复代码。
4、多态: 多态是指同一个函数名在不同的对象中有不同的实现。这样可以提高程序的可扩展性和可维护性。
5、虚函数: 虚函数是指在基类中声明为virtual的成员函数。派生类可以重写虚函数,实现多态。
6、权限控制: C++提供了public、protected和private三种访问控制符来控制类成员的访问权限。这样可以保护类中的数据安全,防止外界对类内部数据的非法访问。
举例:在一个函数中使用封装、继承、多态、虚函数和权限控制这些特点:
ps:基类(也称为父类或超类), 派生类(也称为子类)
#include <iostream>
using namespace std;
// 基类
class Shape {
protected:
int width, height;
public:
Shape(int a = 0, int b = 0) {
width = a;
height = b;
}
virtual int area() {
cout << "Parent class area :" << endl;
return 0;
}
};
// 派生类
class Rectangle: public Shape {
public:
Rectangle(int a = 0, int b = 0): Shape(a, b) { }
int area() {
cout << "Rectangle class area :" << endl;
return (width * height);
}
};
// 派生类
class Triangle: public Shape {
public:
Triangle(int a = 0, int b = 0): Shape(a, b) { }
int area() {
cout << "Triangle class area :" << endl;
return (width * height / 2);
}
};
// 程序的主函数
int main() {
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
在这个例子中,我们定义了一个基类Shape,它包含了两个受保护的成员变量width和height,以及一个虚函数area()。然后,我们定义了两个派生类Rectangle和Triangle,它们分别继承了基类Shape。在派生类中,我们重写了基类中的虚函数area(),实现了多态。
在main()函数中,我们定义了一个基类指针shape,并分别将它指向派生类对象rec和tri。然后,我们通过基类指针调用虚函数area(),可以看到程序会根据指针所指向的对象类型来调用相应的虚函数。
lisp
1、一种古老的编程语言、与机器无关
2、列表:代码即数据
3、闭包
javascript
基于原型和头等函数的多范式语言
1、过程式
2、面向对象
3、函数式
4、响应式
编程语言总结
(二)编程范式
编程范式分类:
一、面向过程:过程式编程
1、自顶项下:自顶向下方法通常指将程序分解为一系列过程或函数,每个过程或函数都完成一个特定的任务。
2、结构式编程:强调程序的结构和模块化。使用控制结构(如顺序、选择和循环)来组织代码,避免使用无限制的跳转语句(如goto语句)。
二、面向对象:对象式编程
question: 面向对象编程有什么缺点?为什么我们推荐函数式编程?
1、数据与算法关联弱
2、不利于修改和扩充
3、不利于代码重用
面向对象编程
封装
继承
多态
依赖注入
面向对象编程五大原则
1、单一职责原则(SRP(Single Responsibility Principle))
2、开放封闭原则OCP(Open-Close Principle)
3、里式替换原则LSP(the Substitution Principle LSP)
4、依赖倒置原则DIP(the Dependency Inversion Principle DIP)
5、接口分离原则ISP(the Interface Segregation Principle ISP)
question: 面向对象编程有什么缺点?为什么我们推荐函数式编程
面向对象编程的一个缺点是它可能导致过度设计和过度复杂的类层次结构。此外,面向对象编程中的状态可变性可能会导致代码难以理解和维护。
函数式编程
函数式编程是一种编程范式,它强调函数的使用和避免状态改变和可变数据,函数式编程语言通常提供高阶函数和闭包等特性,这些特性可以帮助程序员更好地抽象和组合函数。
函数式编程优点:
- 可缓存
- 可测试
- 可移植
- 可推理
- 可并行
额外补充几个函数式编程的优点:
- 代码简洁:函数式编程通常使用更少的代码来实现相同的功能,使代码更加简洁易读。
- 易于调试和测试:由于函数式编程避免了程序状态和易变对象,因此调试和测试变得更加容易。
- 模块化:函数式编程强调模块化和可重用性,可以提高代码的可维护性和可扩展性。
函数式编程柯里化:
柯里化(Currying)是一种函数式编程技术,它将一个接受多个参数的函数转换为一系列接受单个参数的函数。这样,我们可以通过部分应用参数来创建新的函数。
例如,假设我们有一个接受两个参数的函数 add,它将两个数相加并返回结果:
function add(x, y) {
return x + y;
}
我们可以使用柯里化来将这个函数转换为一系列接受单个参数的函数:
function curriedAdd(x) {
return function(y) {
return x + y;
}
}
现在,我们可以通过部分应用参数来创建新的函数:
const add5 = curriedAdd(5);
console.log(add5(6)); // 输出 11
在这个例子中,我们首先调用 curriedAdd 函数并传入参数 5,它返回了一个新的函数 add5。然后,我们调用 add5 函数并传入参数 6,它返回了 11。
柯里化的优点在于它能够让我们更灵活地使用函数。我们可以通过部分应用参数来创建新的函数,并在需要时调用它们。这样可以提高代码的可重用性和可读性。
函数组合(Function Composition)
函数组合(Function Composition)是一种函数式编程技术,允许我们将多个函数组合在一起,创建一个新的函数。这样,我们可以将复杂的问题分解为多个简单的子问题,并使用函数组合来将它们组合在一起。
例如,假设我们有两个函数 f 和 g:
function f(x) {
return x + 1;
}
function g(x) {
return x * 2;
}
我们可以使用函数组合来创建一个新的函数 h,它首先调用 g 函数,然后将结果传递给 f 函数:
function h(x) {
return f(g(x));
}
现在,我们可以调用 h 函数来计算 (x * 2) + 1 的结果:
console.log(h(3)); // 输出 7
在这个例子中,我们首先调用 g 函数并传入参数 3,它返回了 6。然后,我们将 6 作为参数传递给 f 函数,它返回了 7。
函数组合的优点在于它能够让我们更灵活地使用函数。我们可以将复杂的问题分解为多个简单的子问题,并使用函数组合来将它们组合在一起。这样可以提高代码的可重用性和可读性。
函数式编程functor
函子(Functor)是一种函数式编程概念,它是一种支持 map 操作的容器类型。map 操作允许我们将一个函数应用于容器中的每个元素,并返回一个新的容器,其中包含应用函数后的结果。
例如,在 JavaScript 中,数组就是一种函子。我们可以使用数组的 map 方法来将一个函数应用于数组中的每个元素:
const arr = [1, 2, 3];
const newArr = arr.map(x => x * 2);
console.log(newArr); // 输出 [2, 4, 6]
在这个例子中,我们定义了一个数组 arr,并使用 map 方法将一个函数应用于数组中的每个元素。这个函数将每个元素乘以 2,并返回结果。最后,我们得到了一个新的数组 newArr,其中包含应用函数后的结果。
函子的优点在于它能够让我们更灵活地处理容器中的数据。我们可以使用 map 操作来将一个函数应用于容器中的每个元素,并得到一个新的容器。这样可以提高代码的可重用性和可读性。
函数式编程(Monad)
Monad 是一种函数式编程概念,就是去除嵌套容器的容器类型。
Monad 可以用来处理副作用和不确定性。例如,在 JavaScript 中,Promise 就是一种 Monad。我们可以使用 Promise 的 then 方法来将一个返回 Promise 的函数应用于一个 Promise:
const promise = Promise.resolve(1);
const newPromise = promise.then(x => Promise.resolve(x * 2));
newPromise.then(console.log); // 输出 2
在这个例子中,我们定义了一个 Promise promise,并使用 then 方法将一个返回 Promise 的函数应用于它。这个函数将输入值乘以 2,并返回一个新的 Promise。最后,我们得到了一个新的 Promise newPromise,它包含应用函数后的结果。
Monad 的优点在于它能够让我们更灵活地处理副作用和不确定性。我们可以使用 bind 操作来将一个返回 Monad 的函数应用于一个 Monad,并得到一个新的 Monad。这样可以提高代码的可重用性和可读性。
常见的Monad:Array,flatMap,Promise
函数式编程_Applicative
函数式编程_Applicative: 直接对两个容器进行直接操作。是一种支持 ap 操作的函子。ap 操作允许我们将一个包含函数的 Applicative 应用于一个包含值的 Applicative,并返回一个新的 Applicative,其中包含应用函数后的结果。
例如,在 Haskell 语言中,Maybe 类型就是一种 Applicative Functor。我们可以使用 Maybe 的 ap 方法来将一个包含函数的 Maybe 应用于一个包含值的 Maybe:
import Control.Applicative
addOne :: Maybe (Int -> Int)
addOne = Just (+1)
value :: Maybe Int
value = Just 1
result :: Maybe Int
result = addOne <*> value
在这个例子中,我们定义了一个包含函数的 Maybe 值 addOne 和一个包含值的 Maybe 值 value。然后,我们使用 <*> 操作符(即 ap 方法)将 addOne 应用于 value,并得到了一个新的 Maybe 值 result,其中包含应用函数后的结果。
Applicative Functor 的优点在于它能够让我们更灵活地处理函子中的数据。我们可以使用 ap 操作来将一个包含函数的函子应用于一个包含值的函子,并得到一个新的函子。这样可以提高代码的可重用性和可读性。
响应式编程(需要借助工具库)
响应式编程是一种异步和离散的函数式编程,它使用数据流和操作符来处理事件序列。观察者模式和迭代器模式都可以在响应式编程中使用。
RxJS 是一个库,它结合了观察者模式、迭代器模式和使用集合的函数式编程,以一种理想方式来管理事件序列所需要的一切。
1、观察者模式:观察者模式(Observer Pattern)也被称为发布-订阅(Publish/Subscribe)模式,属于行为型模式的一种。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当这个主题对象的状态发生改变时,所有依赖于它的观察者对象都会得到通知并被自动更新。
例如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
2、迭代者模式:迭代器模式(Iterator Pattern)是 Java 和 .Net 编程环境中非常常用的设计模式。这种模式用于顺序访问集合对象的元素,不需要知道集合对象的底层表示。迭代器模式属于行为型模式。
它提供一种方法顺序访问一个聚合对象中各个元素,而又无须暴露该对象的内部表示。
3、Promise/EventTarget超集*
总结:
领域特定语言
1、简介:
领域特定语言(Domain-Specific Language,DSL)是一种专注于某个应用程序领域的计算机语言。它与通用编程语言(General Purpose Language,GPL)不同,通用编程语言可以用来解决各种领域的问题,而领域特定语言则专注于解决特定领域的问题。
例如,HTML 是一种用于描述 Web 页面的标记语言,它是一种领域特定语言;CSS 是一种用于描述页面样式的语言,也是一种领域特定语言;SQL 是一种用于创建和检索关系型数据库的语言,也是一种领域特定语言。
领域特定语言的优点在于它能够提供更简洁、更易于理解和使用的语法来解决特定领域的问题。它能够提高开发效率,并且能够让非程序员更容易地参与到软件开发过程中来。
当然,领域特定语言也有其局限性。由于它只专注于特定领埴,因此在其他领域可能不太适用。此外,学习和使用领域特定语言可能需要一定的学习成本。
2、什么是领域特定语言
3、lexer
Lexer,也称为词法分析器,是一种软件组件,它将字符串分解为更小的单元,这些单元可以被语言理解。这些更小的单元被称为词法记号或词素。
词法分析器的作用是将源代码中的字符序列转换为记号序列。它通过扫描源代码,识别出各种记号,如关键字、标识符、常量、运算符等。这些记号将被传递给语法分析器进行进一步的处理。
SQL Token分类
-
注释
-
关键字
-
操作符
-
空格
-
字符串
-
变量
4、parser语法规则
上下文无关语法规则
- 推导式:表示非终结符到(非终结符或终结符)的关系。
- 终结符:构成句子的实际内容。可以简单理解为词法分析中的token.
- 非终结符:符号或变量的有限集合。它们表示在句子中不同类型的短语或子句。
5、Parser_LL
LL解析是一种自顶向下的解析方法,用于语法分析阶段的编译器设计。需要输入字符串、堆栈、给定语法的解析表和解析器。
LL解析表的构造需要满足一些条件,如语法必须没有左递归,不含二义性,并且必须是左因子化的,以便语法是确定性语法。这些条件是必要但不充分的。
LL:从左到右检查,从左到右构建语法树
6、Parser_LR
LR解析器是一种自底向上的解析器,用于解析上下文无关语法。
LR:从左到右检查,从右到左构建语法树
LL(K) > LR(1) > LL(1),括号里的内容构建语法树需要向下看的数量
7、Tools
进行语法分析
形成语法树
课程总结: