learn.lianglianglee.com/专栏/左耳听风/033 编程范式游记(4)- 函数式编程.md.html
2022-05-11 19:04:14 +08:00

1773 lines
58 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>033 编程范式游记4- 函数式编程.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/左耳听风/000 开篇词 洞悉技术的本质,享受科技的乐趣.md.html">000 开篇词 洞悉技术的本质,享受科技的乐趣.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/001 程序员如何用技术变现(上).md.html">001 程序员如何用技术变现(上).md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/002 程序员如何用技术变现(下).md.html">002 程序员如何用技术变现(下).md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/003 Equifax信息泄露始末.md.html">003 Equifax信息泄露始末.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/004 从Equifax信息泄露看数据安全.md.html">004 从Equifax信息泄露看数据安全.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/005 何为技术领导力.md.html">005 何为技术领导力.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/006 如何拥有技术领导力.md.html">006 如何拥有技术领导力.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/007 推荐阅读:每个程序员都该知道的事.md.html">007 推荐阅读:每个程序员都该知道的事.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/008 Go语言Docker和新技术.md.html">008 Go语言Docker和新技术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/009 答疑解惑:渴望、热情和选择.md.html">009 答疑解惑:渴望、热情和选择.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/010 如何成为一个大家愿意追随的Leader.md.html">010 如何成为一个大家愿意追随的Leader.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/011 程序中的错误处理:错误返回码和异常捕捉.md.html">011 程序中的错误处理:错误返回码和异常捕捉.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/012 程序中的错误处理:异步编程和最佳实践.md.html">012 程序中的错误处理:异步编程和最佳实践.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/013 魔数 0x5f3759df.md.html">013 魔数 0x5f3759df.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/014 推荐阅读机器学习101.md.html">014 推荐阅读机器学习101.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/015 时间管理:同扭曲时间的事儿抗争.md.html">015 时间管理:同扭曲时间的事儿抗争.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/016 时间管理:投资赚取时间.md.html">016 时间管理:投资赚取时间.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/017 故障处理最佳实践:应对故障.md.html">017 故障处理最佳实践:应对故障.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/018 故障处理最佳实践:故障改进.md.html">018 故障处理最佳实践:故障改进.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/019 答疑解惑:我们应该能够识别的表象和本质.md.html">019 答疑解惑:我们应该能够识别的表象和本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/020 分布式系统架构的冰与火.md.html">020 分布式系统架构的冰与火.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/021 从亚马逊的实践,谈分布式系统的难点.md.html">021 从亚马逊的实践,谈分布式系统的难点.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/022 分布式系统的技术栈.md.html">022 分布式系统的技术栈.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/023 分布式系统关键技术:全栈监控.md.html">023 分布式系统关键技术:全栈监控.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/024 分布式系统关键技术:服务调度.md.html">024 分布式系统关键技术:服务调度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/025 分布式系统关键技术:流量与数据调度.md.html">025 分布式系统关键技术:流量与数据调度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/026 洞悉PaaS平台的本质.md.html">026 洞悉PaaS平台的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/027 推荐阅读:分布式系统架构经典资料.md.html">027 推荐阅读:分布式系统架构经典资料.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/028 编程范式游记1- 起源.md.html">028 编程范式游记1- 起源.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/029 编程范式游记2- 泛型编程.md.html">029 编程范式游记2- 泛型编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/030 编程范式游记3 - 类型系统和泛型的本质.md.html">030 编程范式游记3 - 类型系统和泛型的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/031 Git协同工作流你该怎样选.md.html">031 Git协同工作流你该怎样选.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/032 推荐阅读:分布式数据调度相关论文.md.html">032 推荐阅读:分布式数据调度相关论文.md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/左耳听风/033 编程范式游记4- 函数式编程.md.html">033 编程范式游记4- 函数式编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/034 编程范式游记5- 修饰器模式.md.html">034 编程范式游记5- 修饰器模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/035 编程范式游记6- 面向对象编程.md.html">035 编程范式游记6- 面向对象编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/036 编程范式游记7- 基于原型的编程范式.md.html">036 编程范式游记7- 基于原型的编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/037 编程范式游记8- Go 语言的委托模式.md.html">037 编程范式游记8- Go 语言的委托模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/038 编程范式游记9- 编程的本质.md.html">038 编程范式游记9- 编程的本质.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/039 编程范式游记10- 逻辑编程范式.md.html">039 编程范式游记10- 逻辑编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/040 编程范式游记11- 程序世界里的编程范式.md.html">040 编程范式游记11- 程序世界里的编程范式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/041 弹力设计篇之“认识故障和弹力设计”.md.html">041 弹力设计篇之“认识故障和弹力设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/042 弹力设计篇之“隔离设计”.md.html">042 弹力设计篇之“隔离设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/043 弹力设计篇之“异步通讯设计”.md.html">043 弹力设计篇之“异步通讯设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/044 弹力设计篇之“幂等性设计”.md.html">044 弹力设计篇之“幂等性设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/045 弹力设计篇之“服务的状态”.md.html">045 弹力设计篇之“服务的状态”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/046 弹力设计篇之“补偿事务”.md.html">046 弹力设计篇之“补偿事务”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/047 弹力设计篇之“重试设计”.md.html">047 弹力设计篇之“重试设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/048 弹力设计篇之“熔断设计”.md.html">048 弹力设计篇之“熔断设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/049 弹力设计篇之“限流设计”.md.html">049 弹力设计篇之“限流设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/050 弹力设计篇之“降级设计”.md.html">050 弹力设计篇之“降级设计”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/051 弹力设计篇之“弹力设计总结”.md.html">051 弹力设计篇之“弹力设计总结”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/052 区块链技术 - 区块链的革命性及技术概要.md.html">052 区块链技术 - 区块链的革命性及技术概要.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/053 区块链技术 - 区块链技术细节 - 哈希算法.md.html">053 区块链技术 - 区块链技术细节 - 哈希算法.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/054 区块链技术 - 区块链技术细节 - 加密和挖矿.md.html">054 区块链技术 - 区块链技术细节 - 加密和挖矿.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/055 区块链技术 - 去中心化的共识机制.md.html">055 区块链技术 - 去中心化的共识机制.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/056 区块链技术 - 智能合约.md.html">056 区块链技术 - 智能合约.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/057 区块链技术 - 传统金融和虚拟货币.md.html">057 区块链技术 - 传统金融和虚拟货币.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/058 管理设计篇之分布式锁.md.html">058 管理设计篇之分布式锁.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/059 管理设计篇之配置中心.md.html">059 管理设计篇之配置中心.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/060 管理设计篇之边车模式.md.html">060 管理设计篇之边车模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/061 管理设计篇之服务网格.md.html">061 管理设计篇之服务网格.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/062 管理设计篇之网关模式.md.html">062 管理设计篇之网关模式.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/063 管理设计篇之部署升级策略.md.html">063 管理设计篇之部署升级策略.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/064 性能设计篇之缓存.md.html">064 性能设计篇之缓存.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/065 性能设计篇之异步处理.md.html">065 性能设计篇之异步处理.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/066 性能设计篇之数据库扩展.md.html">066 性能设计篇之数据库扩展.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/067 性能设计篇之秒杀.md.html">067 性能设计篇之秒杀.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/068 性能设计篇之边缘计算.md.html">068 性能设计篇之边缘计算.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/069 程序员练级攻略2018开篇词.md.html">069 程序员练级攻略2018开篇词.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/070 程序员练级攻略2018零基础启蒙.md.html">070 程序员练级攻略2018零基础启蒙.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/071 程序员练级攻略2018正式入门.md.html">071 程序员练级攻略2018正式入门.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/072 程序员练级攻略2018程序员修养.md.html">072 程序员练级攻略2018程序员修养.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/073 程序员练级攻略2018编程语言.md.html">073 程序员练级攻略2018编程语言.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/074 程序员练级攻略:理论学科.md.html">074 程序员练级攻略:理论学科.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/075 程序员练级攻略2018系统知识.md.html">075 程序员练级攻略2018系统知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/076 程序员练级攻略2018软件设计.md.html">076 程序员练级攻略2018软件设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/077 程序员练级攻略2018Linux系统、内存和网络.md.html">077 程序员练级攻略2018Linux系统、内存和网络.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/078 程序员练级攻略2018异步IO模型和Lock-Free编程.md.html">078 程序员练级攻略2018异步IO模型和Lock-Free编程.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/079 程序员练级攻略2018Java底层知识.md.html">079 程序员练级攻略2018Java底层知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/080 程序员练级攻略2018数据库.md.html">080 程序员练级攻略2018数据库.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/081 程序员练级攻略2018分布式架构入门.md.html">081 程序员练级攻略2018分布式架构入门.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/082 程序员练级攻略2018分布式架构经典图书和论文.md.html">082 程序员练级攻略2018分布式架构经典图书和论文.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/083 程序员练级攻略2018分布式架构工程设计.md.html">083 程序员练级攻略2018分布式架构工程设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/084 程序员练级攻略2018微服务.md.html">084 程序员练级攻略2018微服务.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/085 程序员练级攻略2018容器化和自动化运维.md.html">085 程序员练级攻略2018容器化和自动化运维.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/086 程序员练级攻略2018机器学习和人工智能.md.html">086 程序员练级攻略2018机器学习和人工智能.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/087 程序员练级攻略2018前端基础和底层原理.md.html">087 程序员练级攻略2018前端基础和底层原理.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/088 程序员练级攻略2018前端性能优化和框架.md.html">088 程序员练级攻略2018前端性能优化和框架.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/089 程序员练级攻略2018UIUX设计.md.html">089 程序员练级攻略2018UIUX设计.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/090 程序员练级攻略2018技术资源集散地.md.html">090 程序员练级攻略2018技术资源集散地.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/091 程序员面试攻略:面试前的准备.md.html">091 程序员面试攻略:面试前的准备.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/092 程序员面试攻略:面试中的技巧.md.html">092 程序员面试攻略:面试中的技巧.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/093 程序员面试攻略:面试风格.md.html">093 程序员面试攻略:面试风格.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/094 程序员面试攻略:实力才是王中王.md.html">094 程序员面试攻略:实力才是王中王.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/095 高效学习:端正学习态度.md.html">095 高效学习:端正学习态度.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/096 高效学习:源头、原理和知识地图.md.html">096 高效学习:源头、原理和知识地图.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/097 高效学习:深度,归纳和坚持实践.md.html">097 高效学习:深度,归纳和坚持实践.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/098 高效学习:如何学习和阅读代码.md.html">098 高效学习:如何学习和阅读代码.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/099 高效学习:面对枯燥和量大的知识.md.html">099 高效学习:面对枯燥和量大的知识.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/100 高效沟通Talk和Code同等重要.md.html">100 高效沟通Talk和Code同等重要.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/101 高效沟通:沟通阻碍和应对方法.md.html">101 高效沟通:沟通阻碍和应对方法.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/102 高效沟通:沟通方式及技巧.md.html">102 高效沟通:沟通方式及技巧.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/103 高效沟通:沟通技术.md.html">103 高效沟通:沟通技术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/104 高效沟通:好老板要善于提问.md.html">104 高效沟通:好老板要善于提问.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/105 高效沟通:好好说话的艺术.md.html">105 高效沟通:好好说话的艺术.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/106 加餐 谈谈我的“三观”.md.html">106 加餐 谈谈我的“三观”.md.html</a>
</li>
<li>
<a href="/专栏/左耳听风/107 结束语 业精于勤,行成于思.md.html">107 结束语 业精于勤,行成于思.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>033 编程范式游记4- 函数式编程</h1>
<p>从前三章内容中,我们了解到,虽然 C 语言简单灵活,能够让程序员在高级语言特性之上轻松进行底层上的微观控制,被誉为“高级语言中的汇编语言”,但其基于过程和底层的设计初衷又成了它的短板。</p>
<p>在程序世界中,编程工作更多的是解决业务上的问题,而不是计算机的问题,我们需要更为贴近业务更为抽象的语言,如面向对象语言 C++ 和 Java 等。</p>
<p>C++ 很大程度上解决了 C 语言中的各种问题和不便,尤其是通过类、模板、虚函数和运行时识别等解决了 C 语言的泛型编程问题。然而如何做更为抽象的泛型呢答案就是函数式编程Functional Programming</p>
<h1>函数式编程</h1>
<p>相对于计算机的历史而言,函数式编程其实是一个非常古老的概念。函数式编程的基础模型来源于 λ 演算,而 λ 演算并非设计于在计算机上执行。它是由 Alonzo Church 和 Stephen Cole Kleene 在 20 世纪 30 年代引入的一套用于研究函数定义、函数应用和递归的形式系统。</p>
<p>如 Alonzo 所说,像 booleans、integers 或者其他的数据结构都可以被函数取代掉。</p>
<p><img src="assets/7fac133e887bb91f6619887e6a6dcfcd.png" alt="img" /></p>
<p>我们来看一下函数式编程,它的理念就是借用于数学中的代数。</p>
<pre><code>f(x)=5x^2+4x+3
g(x)=2f(x)+5=10x^2+8x+11
h(x)=f(x)+g(x)=15x^2+12x+14
</code></pre>
<p>假设 f(x) 是一个函数g(x) 是第二个函数,把 f(x) 这个函数套下来,并展开。然后还可以定义一个由两个一元函数组合成的二元函数。还可以做递归,下面这个函数定义就是斐波那契数列。</p>
<pre><code>f(x)=f(x-1)+f(x-2)
</code></pre>
<p>对于函数式编程来说,其只关心,<strong>定义输入数据和输出数据相关的关系数学表达式里面其实是在做一种映射mapping输入的数据和输出的数据关系是什么样的是用函数来定义的</strong></p>
<p>函数式编程有以下特点。</p>
<p><strong>特征</strong></p>
<ul>
<li><strong>stateless</strong>:函数不维护任何状态。函数式编程的核心精神是 stateless简而言之就是它不能存在状态你给我数据我处理完扔出来里面的数据是不变的。</li>
<li><strong>immutable</strong>:输入数据是不能动的,动了输入数据就有危险,所以要返回新的数据集。</li>
</ul>
<p><strong>优势</strong></p>
<ul>
<li>没有状态就没有伤害。</li>
<li>并行执行无伤害。</li>
<li>Copy-Paste 重构代码无伤害。</li>
<li>函数的执行没有顺序上的问题。</li>
</ul>
<p><img src="assets/134bd812c06ca16d8f29bc40174055a2.png" alt="img" /></p>
<p>函数式编程还带来了以下一些好处。</p>
<ul>
<li><strong>惰性求值</strong>。这需要编译器的支持。表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值。也就是说,语句如 <code>x:=expression;</code> (把一个表达式的结果赋值给一个变量) 显式地调用这个表达式被计算并把结果放置到 <code>x</code> 中,但是先不管实际在 <code>x</code> 中的是什么,直到通过后面的表达式中到 <code>x</code> 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。</li>
<li><strong>确定性</strong>。所谓确定性,就是像在数学中那样,<code>f(x) = y</code> 这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景,就是我们的函数会根据运行中的状态信息的不同而发生变化。</li>
</ul>
<p>我们知道,因为状态,在并行执行和 copy-paste 时引发 bug 的概率是非常高的所以没有状态就没有伤害就像没有依赖就没有伤害一样并行执行无伤害copy 代码无伤害,因为没有状态,代码怎样拷都行。</p>
<p><strong>劣势</strong></p>
<ul>
<li>数据复制比较严重。</li>
</ul>
<blockquote>
<p><strong></strong>有一些人可能会觉得这会对性能造成影响。其实这个劣势并不见得会导致性能不好。因为没有状态所以代码在并行上根本不需要锁不需要对状态修改的锁所以可以拼命地并发反而可以让性能很不错。比如Erlang 就是其中的代表。</p>
</blockquote>
<p>对于纯函数式(也就是完全没有状态的函数)的编程来说,各个语言支持的程度如下。</p>
<ul>
<li>完全纯函数式 Haskell</li>
<li>容易写纯函数 F#, Ocaml, Clojure, Scala</li>
<li>纯函数需要花点精力 C#, Java, JavaScript</li>
</ul>
<p>完全纯函数的语言,很容易写成函数,纯函数需要花精力。只要所谓的纯函数的问题,传进来的数据不改,改完的东西复制一份拷出去,然后没有状态。</p>
<p>但是很多人并不习惯函数式编程,因为函数式编程和过程式编程的思维方式完全不一样。过程式编程是在把具体的流程描述出来,所以可以不假思索,而函数式编程的抽象度更大,在实现方式上,函数套函数,函数返回函数,函数里定义函数……把人搞得很糊涂。</p>
<h1>函数式编程用到的技术</h1>
<p>下面是函数式编程用到的一些技术。</p>
<ul>
<li><strong>first class function头等函数</strong> :这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建、修改,并当成变量一样传递、返回,或是在函数中嵌套函数。</li>
<li><strong>tail recursion optimization尾递归优化</strong> 我们知道递归的害处那就是如果递归很深的话stack 受不了,并会导致性能大幅度下降。因此,我们使用尾递归优化技术——每次递归时都会重用 stack这样能够提升性能。当然这需要语言或编译器的支持。Python 就不支持。</li>
<li><strong>map &amp; reduce</strong> :这个技术不用多说了,函数式编程最常见的技术就是对一个集合做 Map 和 Reduce 操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用 for/while 循环,然后在各种变量中把数据倒过来倒过去的)这个很像 C++ STL 中 foreach、find_if、count_if 等函数的玩法。</li>
<li><strong>pipeline管道</strong>:这个技术的意思是,将函数实例成一个一个的 action然后将一组 action 放到一个数组或是列表中,再把数据传给这个 action list数据就像一个 pipeline 一样顺序地被各个函数所操作,最终得到我们想要的结果。</li>
<li><strong>recursing递归</strong> :递归最大的好处就简化代码,它可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。</li>
<li><strong>currying柯里化</strong> :将一个函数的多个参数分解成多个函数, 然后将函数多层封装起来,每层函数都返回一个函数去接收下一个参数,这可以简化函数的多个参数。在 C++ 中,这很像 STL 中的 bind1st 或是 bind2nd。</li>
<li><strong>higher order function高阶函数</strong>:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。这个技术用来做 Decorator 很不错。</li>
</ul>
<p>上面这些技术太抽象了,我们还是从一个最简单的例子开始。</p>
<pre><code>// 非函数式,不是 pure funciton有状态
int cnt;
void increment(){
cnt++;
}
</code></pre>
<p>这里有个全局变量,调这个全局函数变量 ++,这里面是有状态的,这个状态在外部。所以,如果是多线程的话,这里面的代码是不安全的。</p>
<p>如果写成纯函数,应该是下面这个样子。</p>
<pre><code>// 函数式pure function 无状态
int increment(int cnt){
return cnt+1;
}
</code></pre>
<p>这个是你传给我什么,我就返回这个值的 +1 值,你会发现,代码随便拷,而且与线程无关,代码在并行时候不用锁,因为是复制了原有的数据,并返回了新的数据。</p>
<p>我们再来看另一个例子:</p>
<pre><code>def inc(x):
def incx(y):
return x+y
return incx
inc2 = inc(2)
inc5 = inc(5)
print inc2(5) # 输出 7
print inc5(5) # 输出 10
</code></pre>
<p>上面这段 Python 的代码,开始有点复杂了。我们可以看到上面那个例子<code>inc()</code>函数返回了另一个函数<code>incx()</code>,于是可以用<code>inc()</code>函数来构造各种版本的 inc 函数,比如:<code>inc2()</code><code>inc5()</code>。这个技术其实就是上面所说的 currying 技术。从这个技术上,你可能体会到函数式编程的理念。</p>
<ul>
<li><strong>把函数当成变量来用,关注描述问题而不是怎么实现,这样可以让代码更易读</strong></li>
<li><strong>因为函数返回里面的这个函数,所以函数关注的是表达式,关注的是描述这个问题,而不是怎么实现这个事情</strong></li>
</ul>
<h1>Lisp 语言介绍</h1>
<p>要说函数式语言,不可避免地要说一下 Lisp。</p>
<p>下面,我们再来看看 Scheme 语言Lisp 的一个方言)的函数式玩法。在 Scheme 里,所有的操作都是函数,包括加减乘除这样的东西。所以,一个表达式是这样的形式—— <em>(函数名 参数 1 参数 1</em></p>
<pre><code>(define (plus x y) (+ x y))
(define (times x y) (* x y))
(define (square x) (times x x))
</code></pre>
<p>上面三个函数:</p>
<ul>
<li>用内置的 <code>+</code> 函数定义了一个新的 <code>plus</code> 函数。</li>
<li>用内置的 <code>*</code> 函数定义了一个新的 <code>times</code> 函数。</li>
<li>用之前的 <code>times</code> 函数定义了一个 <code>square</code> 函数。</li>
</ul>
<p>下面这个函数定义了: f(x) = 5 * x^2 +10</p>
<pre><code>(define (f1 x) ;;; f(x) = 5 * x^2 + 10
(plus 10 (times 5 (square x))))
</code></pre>
<p>也可以这样定义——使用 lambda 匿名函数。</p>
<pre><code>(define f2
(lambda (x)
(define plus
(lambda (a b) (+ a b)))
(define times
(lambda (a b) (* a b)))
(plus 10 (times 5 (times x x)))))
</code></pre>
<p>在上面的这个代码里,我们使用 lambda 来定义函数 <code>f2</code> ,然后也同样用 lambda 定义了两个函数—— <code>plus</code><code>times</code>。 最后,由 <code>(plus 10 (times 5 (times x x)))</code> 定义了 <code>f2</code></p>
<p>我们再来看一个阶乘的示例:</p>
<pre><code>;;; recursion
(define factoral (lambda (x)
(if (&lt;= x 1) 1
(* x (factoral (- x 1))))))
(newline)
(display(factoral 6))
</code></pre>
<p>下面是另一个版本的,使用了尾递归。</p>
<pre><code>;;; another version of recursion
(define (factoral_x n)
(define (iter product counter)
(if (&lt; counter n)
product
(iter (* counter product) (+ counter 1))))
(iter 1 1))
(newline)
(display(factoral_x 5))
</code></pre>
<h1>函数式编程的思维方式</h1>
<p>前面提到过多次函数式编程关注的是describe what to do, rather than how to do it。于是我们把以前的过程式编程范式叫做 Imperative Programming 指令式编程,而把函数式编程范式叫做 Declarative Programming 声明式编程。</p>
<h2>传统方式的写法</h2>
<p>下面我们看一下相关的示例。比如,我们有 3 辆车比赛,简单起见,我们分别给这 3 辆车有 70% 的概率可以往前走一步,一共有 5 次机会,然后打出每一次这 3 辆车的前行状态。</p>
<p>对于 Imperative Programming 来说代码如下Python</p>
<pre><code>from random import random
time = 5
car_positions = [1, 1, 1]
while time:
# decrease time
time -= 1
print ''
for i in range(len(car_positions)):
# move car
if random() &gt; 0.3:
car_positions[i] += 1
# draw car
print '-' * car_positions[i]
</code></pre>
<p>我们可以把这两重循环变成一些函数模块,这样有利于更容易地阅读代码:</p>
<pre><code>from random import random
def move_cars():
for i, _ in enumerate(car_positions):
if random() &gt; 0.3:
car_positions[i] += 1
def draw_car(car_position):
print '-' * car_position
def run_step_of_race():
global time
time -= 1
move_cars()
def draw():
print ''
for car_position in car_positions:
draw_car(car_position)
time = 5
car_positions = [1, 1, 1]
while time:
run_step_of_race()
draw()
</code></pre>
<p>上面的代码,从主循环开始,我们可以很清楚地看到程序的主干,因为我们把程序的逻辑分成了几个函数。这样一来,代码逻辑就会变成几个小碎片,于是我们读代码时要考虑的上下文就少了很多,阅读代码也会更容易。不像第一个示例,如果没有注释和说明,你还是需要花些时间理解一下。而将代码逻辑封装成了函数后,我们就相当于给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的。</p>
<p>但是,你会发现,封装成函数后,这些函数都会依赖于共享的变量来同步其状态。于是,在读代码的过程中,每当我们进入到函数里,读到访问了一个外部的变量时,我们马上要去查看这个变量的上下文,然后还要在大脑里推演这个变量的状态, 才能知道程序的真正逻辑。也就是说,这些函数间必需知道其它函数是怎么修改它们之间的共享变量的,所以,这些函数是有状态的。</p>
<h2>函数式的写法</h2>
<p>我们知道,有状态并不是一件很好的事情,无论是对代码重用,还是对代码的并行来说,都是有副作用的。因此,要想个方法把这些状态搞掉,于是出现了函数式编程的编程范式。下面,我们来看看函数式的方式应该怎么写?</p>
<pre><code>from random import random
def move_cars(car_positions):
return map(lambda x: x + 1 if random() &gt; 0.3 else x,
car_positions)
def output_car(car_position):
return '-' * car_position
def run_step_of_race(state):
return {'time': state['time'] - 1,
'car_positions': move_cars(state['car_positions'])}
def draw(state):
print ''
print '\n'.join(map(output_car, state['car_positions']))
def race(state):
draw(state)
if state['time']:
race(run_step_of_race(state))
race({'time': 5,
'car_positions': [1, 1, 1]})
</code></pre>
<p>上面的代码依然把程序的逻辑分成了函数。不过这些函数都是函数式的,它们有三个特点:它们之间没有共享的变量;函数间通过参数和返回值来传递数据;在函数里没有临时变量。</p>
<p>我们还可以看到for 循环被递归取代了(见 race 函数)—— 递归是函数式编程中常用到的技术,正如前面所说的,递归的本质就是描述问题是什么。</p>
<h1>函数式语言的三套件</h1>
<p>函数式语言有三套件,<strong>Map</strong><strong>Reduce</strong><strong>Filter</strong>。这在谈 C++ 的泛型编程时已经介绍过。下面我们来看一下 Python 语言中的一个示例。这个示例的需求是,我们想把一个字符串数组中的字符串都转成小写。</p>
<p>用常规的面向过程的方式,代码如下所示:</p>
<pre><code># 传统的非函数式
upname =['HAO', 'CHEN', 'COOLSHELL']
lowname =[]
for i in range(len(upname)):
lowname.append( upname[i].lower() )
</code></pre>
<p>如果写成函数式,用 <code>map()</code> 函数,是下面这个样子。</p>
<pre><code># 函数式
def toUpper(item):
return item.upper()
upper_name = map(toUpper, [&quot;hao&quot;, &quot;chen&quot;, &quot;coolshell&quot;])
print upper_name
# 输出 ['HAO', 'CHEN', 'COOLSHELL']
</code></pre>
<p>顺便说一下,上面的例子是不是和我们 C++ 语言中的 STL 的<code>transform()</code>函数有些像?</p>
<pre><code>string s=&quot;hello&quot;;
transform(s.begin(), s.end(), back_inserter(out), ::toupper);
</code></pre>
<p>在上面 Python 的那个例子中可以看到,我们定义了一个函数 toUpper这个函数没有改变传进来的值只是把传进来的值做个简单的操作然后返回。然后我们把其用在 map 函数中,就可以很清楚地描述出我们想要干什么。而不会去理解一个在循环中怎么实现的代码,最终在读了很多循环的逻辑后才发现原来是这个或那个意思。</p>
<p>如果你觉得上面的代码在传统的非函数式的方式下还是很容易读的,那么我们再来看一个计算数组平均值的代码:</p>
<pre><code># 计算数组中正数的平均值
num = [2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8]
positive_num_cnt = 0
positive_num_sum = 0
for i in range(len(num)):
if num[i] &gt; 0:
positive_num_cnt += 1
positive_num_sum += num[i]
if positive_num_cnt &gt; 0:
average = positive_num_sum / positive_num_cnt
print average
</code></pre>
<p>上面的代码如果没有注释的话,你需要看一会儿才能明白,只是计算数组中正数的平均值。</p>
<p>我们再来看看函数式下使用 filter/reduce 函数的玩法。</p>
<pre><code># 计算数组中正数的平均值
positive_num = filter(lambda x: x&gt;0, num)
average = reduce(lambda x,y: x+y, positive_num) / len( positive_num )
</code></pre>
<p>首先,我们使用 filter 函数把正数过滤出来(注意: <code>lambda x : x&gt;0</code> 这个 lambda 表达式),保存在一个新的数组中 —— <code>positive_num</code>。然后,我们使用 reduce 函数对数组 <code>positive_num</code> 求和后,再除以其个数,就得到正数的平均值了。</p>
<p>我们可以看到, <strong>隐藏了数组遍历并过滤数组控制流程的 filter 和 reduce 不仅让代码更为简洁,因为代码里只有业务逻辑了,而且让我们能更容易地理解代码</strong></p>
<ol>
<li><code>num</code> 数组 <code>filter</code> 条件 <code>x &gt; 0</code> 的数据。</li>
<li>然后对 <code>positive_num</code> 进行 <code>x + y</code> 操作的 reduce即求和。</li>
<li>……</li>
</ol>
<p>感觉代码更亲切了,不是吗?因为:</p>
<ul>
<li>数据集、对数据的操作和返回值都放在了一起。</li>
<li>没有了循环体,就可以少了些临时用来控制程序执行逻辑的变量,也少了把数据倒来倒去的控制逻辑。</li>
<li><strong>代码变成了在描述你要干什么,而不是怎么干</strong></li>
</ul>
<p>当然,如果你是第一次见到 map/reduce/filter那你可能还是会有点儿陌生和不解这只是你不了解罢了。</p>
<p>对于函数式编程的思路,下图是一个比较形象的例子,面包和蔬菜 map 到切碎的操作上,再把结果给 reduce 成汉堡。</p>
<p><img src="assets/cfbbd404c980f98040514371aceb8881.png" alt="img" /></p>
<p>在这个图中,<strong>我们可以看到 map 和 reduce 不关心源输入数据,它们只是控制,并不是业务。控制是描述怎么干,而业务是描述要干什么</strong></p>
<h1>函数式的 pipeline 模式</h1>
<p>pipeline管道借鉴于 Unix Shell 的管道操作——把若干个命令串起来,前面命令的输出成为后面命令的输入,如此完成一个流式计算。(注:管道绝对是一个伟大的发明,它的设计哲学就是 KISS 让每个功能就做一件事,并把这件事做到极致,软件或程序的拼装会变得更为简单和直观。这个设计理念影响非常深远,包括今天的 Web Service、云计算以及大数据的流式计算等。</p>
<p>比如,我们如下的 shell 命令:</p>
<pre><code>ps auwwx | awk '{print $2}' | sort -n | xargs echo
</code></pre>
<p>上面的例子是要查看一个用户执行的进程列表,列出来以后,然后取第二列,第二列是它的进程 ID排个序再把它显示出来。</p>
<p>抽象成函数式的样子,我们就可以反过来,一层套一层。</p>
<pre><code>xargs( echo, sort(n, awk('print $2', ps(auwwx))) )
</code></pre>
<p>我们也可以把函数放进数组里面,然后顺序执行一下。</p>
<pre><code>pids = for_each(result, [ps_auwwx, awk_p2, sort_n, xargs_echo])
</code></pre>
<p>多说一句,如果我们把这些函数比作微服务,那么管道这个事是在干什么呢?其实就是在做服务的编排。像 Unix 这些经典的技术上的实践或理论,往往是可以反映到分布式架构的,所以,一般来说,一个好的分布式架构师,通常都是对这些传统的微观上的经典技术有非常深刻的认识,因为这些东西在方法论上都是相通的。</p>
<p>好了,还是让我们用一个简单的示例来看一下如何实现 pipeline。</p>
<p>我们先来看一个程序,这个程序的 process() 有三个步骤:</p>
<ol>
<li>找出偶数;</li>
<li>乘以 3</li>
<li>转成字符串返回。</li>
</ol>
<p>传统的非函数式的实现如下:</p>
<pre><code>def process(num):
# filter out non-evens
if num % 2 != 0:
return
num = num * 3
num = 'The Number: %s' % num
return num
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for num in nums:
print process(num)
# 输出:
# None
# The Number: 6
# None
# The Number: 12
# None
# The Number: 18
# None
# The Number: 24
# None
# The Number: 30
</code></pre>
<p>我们可以看到,输出的结果并不够完美,另外,代码阅读上如果没有注释,你也会比较晕。下面,我们来看看函数式的 pipeline第一种方式应该怎么写</p>
<p>第一步,我们先把三个“子需求”写成函数:</p>
<pre><code>def even_filter(nums):
for num in nums:
if num % 2 == 0:
yield num
def multiply_by_three(nums):
for num in nums:
yield num * 3
def convert_to_string(nums):
for num in nums:
yield 'The Number: %s' % num
</code></pre>
<p>然后,我们再把这三个函数串起来:</p>
<pre><code>nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pipeline = convert_to_string(multiply_by_three(even_filter(nums)))
for num in pipeline:
print num
# 输出:
# The Number: 6
# The Number: 12
# The Number: 18
# The Number: 24
# The Number: 30
</code></pre>
<p>上面,我们动用了 Python 的关键字 yield它是一个类似 return 的关键字,只是这个函数返回的是 Generator生成器。所谓生成器指的是 yield 返回的是一个可迭代的对象并没有真正的执行函数。也就是说只有其返回的迭代对象被迭代时yield 函数才会真正运行,运行到 yield 语句时就会停住,然后等下一次的迭代。( yield 是个比较诡异的关键字)这就是 lazy evluation懒惰加载</p>
<p>好了,根据前面的原则——“<strong>使用 Map &amp; Reduce不要使用循环</strong>”(还记得吗?使用循环会让我们只能使用顺序型的数据结构),那我们用比较纯朴的 Map &amp; Reduce 吧。</p>
<pre><code>def even_filter(nums):
return filter(lambda x: x%2==0, nums)
def multiply_by_three(nums):
return map(lambda x: x*3, nums)
def convert_to_string(nums):
return map(lambda x: 'The Number: %s' % x, nums)
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
pipeline = convert_to_string(
multiply_by_three(
even_filter(nums)
)
)
for num in pipeline:
print num
</code></pre>
<p>上面的代码是不是更容易读了,但需要嵌套使用函数,这个有点儿不爽,如果我们能像下面这个样子就好了(第二种方式)。</p>
<pre><code>pipeline_func(nums, [even_filter,
multiply_by_three,
convert_to_string])
</code></pre>
<p>可以看到,其实,就是对一堆函数做一个 reduce 于是pipeline 函数可以实现成下面这样:</p>
<pre><code>def pipeline_func(data, fns):
return reduce(lambda a, x: x(a), fns, data)
</code></pre>
<p>当然,使用 Python 的 <code>force</code> 函数以及 decorator 模式可以把上面的代码写得更像管道:</p>
<pre><code>class Pipe(object):
def __init__(self, func):
self.func = func
def __ror__(self, other):
def generator():
for obj in other:
if obj is not None:
yield self.func(obj)
return generator()
@Pipe
def even_filter(num):
return num if num % 2 == 0 else None
@Pipe
def multiply_by_three(num):
return num*3
@Pipe
def convert_to_string(num):
return 'The Number: %s' % num
@Pipe
def echo(item):
print item
return item
def force(sqs):
for item in sqs: pass
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
force(nums | even_filter | multiply_by_three | convert_to_string | echo)
</code></pre>
<h1>小结</h1>
<p>相对于计算机发展史,函数式编程是个非常古老的概念,它的核心思想是将运算过程尽量写成一系列嵌套的函数调用,关注的是做什么而不是怎么做,因而被称为声明式编程。以 Stateless无状态和 Immutable不可变为主要特点代码简洁易于理解能便于进行并行执行易于做代码重构函数执行没有顺序上的问题支持惰性求值具有函数的确定性——无论在什么场景下都会得到同样的结果。</p>
<p>本文结合递归、map 和 reduce以及 pipeline 等技术,对比了非函数式编程和函数式编程在解决相同问题时的不同处理思路,让你对函数式编程范式有了清晰明确的认知。并在文末引入了 decorator修饰器使得将普通函数管道化成为一件轻而易举的事情。此时你可能有疑问decorator 到底是什么呢?怎样使用它呢?敬请关注下一章中的内容,来得到这些答案。</p>
<p>了解了这么多函数式编程的知识,想请你深入思考一个问题:你是偏好在命令式编程语言中使用函数式编程风格呢,还是坚持使用函数式语言编程?原因是什么?欢迎在评论区留言和我一起探讨。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/左耳听风/032 推荐阅读:分布式数据调度相关论文.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/左耳听风/034 编程范式游记5- 修饰器模式.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997815df9f3cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>