编程范式
课程介绍
-
课程背景
- 前端的主要编程语言是javascript
- javascript 融合了多种编程范式的语言,灵活性高
- 前端开发者需要根据不同场景在不同编程范式间自如切换
- 进一步创造领域特定语言抽象业务问题
-
课程收益:
- 了解不同编程范式的起源和适用场景
- 掌握 JavaScript 在不同的编程范式特别是函数编程范式的使用
- 掌握创建领域特定语言的相关工具和范式
-
为什么需要编程语言
-
机器语言
-
汇编语言
-
中级语言
- c 语言、C++
-
高级语言
- Lisp:列表及数据
- JavaScript:过程式,面向对象,函数式,响应式
-
-
程序语言的特性
- 是否允许副作用
- 操作的执行顺序
- 代码组织
- 状态管理
- 语法和词法
-
编程范式
-
命令式:
- 面向过程:把操作用过程就行分组
- 面向对象:根据状态和操作进行分组
-
声明式:
- 函数式:通过一系列函数组合来声明逻辑
- 响应式:通过数据流和映射函数来实现结果
-
-
过程式编程
-
自顶向下
-
程序
-
模块1
-
变量
- 数据结构
-
函数
- 函数
- 语句
-
-
模块2
-
变量
- 数据结构
-
函数
- 函数
- 语句
-
-
。。。
-
-
-
结构化编程
- 子程序
- 块结构(顺序结构,选择结构,循环结构)
-
js 中面向过程
- 变量 ---数据
- 函数--- 算法
-
思考:
-
面向过程式编程有什么缺点?
- 数据与算法关联弱
- 不利于修改和扩充
- 不利于代码复用
-
为什么后面会出现面向对象?
- 封装-->关联数据与算法
- 继承-- >无需复用的情况下进行功能扩充
- 多态-- >不同的结构可以进行接口共享,进而达到函数复用
- 依赖注入--> 去除代码耦合(声明一些依赖)
-
-
面向对象编程的五大原则
- 单一职责原则SRP
- 开放封闭原则OCP
- 里式替换替换原则LSP:
- 依赖倒置原则DIP:
- 接口分离原则
-
面向对象有什么缺点?
- 面向对象编程语言的问题在于,它总是附带着所有它需要的隐含环境。你想要一个香蕉,但得到的却是一个大猩猩拿着香蕉,而且还有整个丛林。
-
-
函数式编程范式
-
函数是一等公民
-
// 聚合转发 const BlogController = { index(posts) { return Views.index(posts); }, show(post) { return Views.show(post); }, create(attrs) { return Db.create(attrs); }, update(post, attrs) { return Db.update(post, attrs); }, destroy(post) { return Db.destroy(post); } };
-
-
纯函数、无副作用(优势:可缓存,可移植,可测试,可推理,可并行)
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; } -
高阶函数、闭包
// 函数颗粒化 function add(a,b,c) { return a + b + c } add(1,2,5) add(1,2,4) add(1,2,7) // 上面代码优化如下: const add = curry(add); const add12 = add_(1,2); add12(5); add12(4); add12(7); const add1 = add_(1); add1(2,5); add1(3,5); // 柯理化函数 function curry(fn) { const arity = fn.length; return function $curry(...args) { if(args.length < arity) { return $curry.bind(null,...args); } return fn.call(null,...args); } }
-
const toUpperCase = x => x.toUpperCase();
const log = x =>console.log;
function alertUppercase(str) {
log(toUpperCase(x))
}
const alertUppercaseFn = compose( log, toUpperCase);
// 高级函数,实现这些函数的一连串调用
const compose = (...fns) => (...args) => fns.reduceRight( (res,fn) => [fn.call(null,...res)],args)[0];
associativity: compose(f,compose(g,h))===compose(compose(f,g),h); map's composition law: compose(map(f),map(g))===map(compose(f,g))
函数式编程特性--Functor
-
可以当做容器的类型,类型支持对容器内元素进行操作
- 常见的
functor:Array(lterable)map, Promise.then
- 常见的
a.b != null ? (a.b.c != null ? (a.b.c.d !== null ? a.b.c.d.e: null) : null): null
// 上面代码优化如下:
class Maybe {
constructor(x) {
this.$value = x;
}
map(fn) {
return this.$value == null ? this:new Maybe(fn(this.$value));
}
}
new Maybe(a).map(prop('b')).map(prop('c')).map(prop('d')).map(prop('e'))
函数式编程特性-- Monad
-
可以去除嵌套容器的容器类型
-
常见monad: ArrayflatMap Promisethen
[1,2].flatMap( ()=> ([1,2])) Promise.resolve(1).then( r => Promise.resolve(2 * r)) Maybe.prototype.flat =function (level = 1) { if(this.$value?.constructor !== Maybe) { return new Maybe(this.$value); return level ? this.$value.flat(--level): this.$value; }
函数式编程特性-- Applicative
-
直接对两个容器直接操作
new Mabe(2).map(two => new Maybe(3).map(add(two)) ).flat(); new Maybe(add) .ap(new Maybe(3)) .ap(new Maybe(2)); ap(other) { return other.map(this.$value); }
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)===uap(vap(w));
响应式编程:
-
异步/离散的函数式编程
-
数据流
-
操作符
- 过滤
- 合并
- 转化
- 高阶
-
响应式编程特性-- Observable
- 观察者模式: 观察者 --订阅--> 发布者,发布者-- 推送数据--> 观察者
- 迭代器模式: 数据会持续不断的推送
- Promise/EventTarget 超集*
const { fromEvent } = rxjs;
const clicks = fromEvent(document, 'click'); // Observable
const sub = clicks.subscribe(x => console.log(x)); //订阅数据
setTimeout(() => sub.unsubscribe(),5000); //取消订阅
响应式编程特性-- 操作符
-
响应式编程 :“compose”
- 合并
- 过滤
- 转化
- 异常处理
- 多播
const { fromEvent, map } = rxjs;
const clicks = fromEvent(document, 'click');
const positions = clicks.pipe(
map(ev => ev.clientx),// 转化操作符
map(clientX => ++clientX)
);
positions.subscribe(x => console.log(x));
const events = [{ clientX: 1 },{ clientx: 2 }];
compose(
map(clientX => ++clientX),
map(ev=> ev.clientX)
)(events);
响应式编程特性-- Monad
- 去除嵌套的 Observable
const { fromEvent, flatMap, fetch }=rxjs;
const clicks = fromEvent(document, 'click');
const users=clicks.pipe(
flatMeple => fetch.fromFetch(
//嵌套 Observable
"https://api.github.com/users?perpage=5
)),
flatMap(res => res.json())//嵌套 Promise
);
users.subscribe(data => console.log(data));
04_什么是领域特定语言
-
Domain-specificlanguage(DSL):应用于特定领域的语言
- HTML
- SQL
-
General-purposelanguage
- C/C++
- Javascript ...
-
语言运行
- lexer -- > parse -- > vist -- > interpret / Code gen
-
词法解析lexer:
- SQL Token 分类 ·注释 关键字 操作符 空格 · 字符串 变量
-
04_Parser_语法规则 上下文无关语法规则
-
<selectStatement>=SELECT<selectList>FROM<tableNme> <selectList> ::= <selectField> [,<selectList> ] <tableName>:=<tableName>[,<tableList>]推导式:表示非终结符到(非终结符或终结符)的关系。 终结符:构成句子的实际内容。可以简单理解为词法分析中的token。 非终结符:符号或变量的有限集合。它们表示在句子中不同类型的短语或子句。
04_Parser_LL
-
LL:从左到右检查,从左到右构建语法树
-
select name from user
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(tableName()); } function selectList({ children }) { let node; while (node = selectField()){ children.push(node); } } function selectField() { if (currentToken(().match(/\w+/)) { return { type: 'field', token: nextToken() }; } }04_Parser_LR
- LR:从左到右检查,从右到左构建语法树
- select name from user
-
tools
-
visitor
附录 ·函数式编程 ·mostly-adequate.gitbook.io/mostly-adeq… ·ramdajs.com/ ·rxjs.dev/ ·创建DSL parser工具 ·pegjs.org/ ·github.com/antlr/antlr… ·github.com/yiminghe/ki…