programming-languages学习笔记--第9部分
programming-languages学习笔记–第9部分
*/-->
pre.src {background-color: #292b2e; color: #b2b2b2;}
pre.src {background-color: #292b2e; color: #b2b2b2;}
pre.src {background-color: #292b2e; color: #b2b2b2;}
pre.src {background-color: #292b2e; color: #b2b2b2;}
programming-languages学习笔记–第9部分
目录
1 过程分解与面向对象分解
- 函数式编程中,把程序分解为完成一些操作的函数。
- 面向对象编程中,把程序分解为类,这些类为某些类型的数据提供行为。
这两种分解方式完全相反。
哪种方式更好看个人口味,但也依赖于你希望如何修改/扩展软件。对于包含两个或更多参数的操作,函数和模式匹配是很简明的,但是OOP可以使用double dispatch达到目的。
示例,实现一个表达式的小型语言:
| eval | toString | hasZero | … | |
| Int | ||||
| Add | ||||
| Negate | ||||
| … |
ML(函数式)中的标准方法:
- 为每种变量(每一行)定义一个数据类型和一个构造器(在动态类型语言中,我们不会给数据类型一个名字,但是仍然按这样的方式考虑问题。)
- 为每个操作定义一个函数
- 以每列一个函数的方式填写表格,每个函数中针对每个单元格有一个分支;如果列中的多项相同,可以组合分支(使用通配模式)。
这个方法就是过程分解:对问题分解为每个操作有一个对应的过程。
datatype exp =
Int of int
| Negate of exp
| Add of exp * exp exception BadResult of string fun add_values (v1, v2) =
case (v1, v2) of
(Int i, Int j) => Int (i+j)
| _ => raise BadResult "non-ints in addition" fun eval e =
case e of
Int _ => e
| Negate e1 => (case eval e1 of
Int i => Int (~i)
| _ => raise BadResult "non-int in negation")
| Add(e1, e2) => add_values(eval e1, eval e2) fun toString e =
case e of
Int i => Int.toString i
| Negate e1 => "-(" ^ (toString e1) ^ ")"
| Add(e1,e2) => "(" ^ (toString e1) ^ ")" ^ " + "
^ (toString e2) ^ ")" fun hasZero e =
case e of
Int i => i=
| Negate e1 => hasZero e1
| Add(e1, e2) => (hasZero e1) orelse (hasZero e2)
OO的标准方法:
- 为表达式定义一个类,为每个操作(每一列)定义一个抽象方法,(ruby中不需要,动态类型中不需要指定抽象方法)
- 为每个数据变量(每一行)定义一个子类
- 在每个子类中,为每个操作定义一个方法。
这个方法是面向数据的分解:把问题分解为每个数据变量对应一个类。
class Exp
# 可以在这里写默认实现或辅助函数
end class Value < Exp
end class Int < Value
attr_reader :i
def initialize i
@i = i
end def eval
self
end def toString
@i.to_s
end def hasZero
@i ==
end
end class Add < Exp
attr_reader :e1, :e2
def initialize(e1, e2)
@e1 = e1
@e2 = e2
end def eval
Int.new(@e1.eval.i + @e2.eval.i)
end def toString
"(" + @e1.toString + " + " + @e2.toString + ")"
end def hasZero
@e1.hasZero || @e2.hasZero
end
end class Negate < Exp
attr_reader :e
def initialize(e)
@e = e
end def eval
Int.new(-@e.eval.i)
end def toString
"-(" + @e.toString + ")"
end def hasZero
@e.hasZero
end
end
总结:
- FP和OOP总是按照相反的方式做同样的事,按行或按列组织程序
- 哪个更自然取决你做什么(解释器或GUI)或者个人爱好
- 代码布局是重要的,但是没有完美的方法,因为软件有许多种结构维度。工具,IDE可以给你多种视图(行/列)
面向对象首先关心的是对象,然后是针对这些对象有哪些操作。
函数式首先关心的是操作,然后是这些操作针对哪些数据。
2 扩展代码:添加操作或变体(variant)
可扩展性,扩展上一节的程序:
| eval | toString | hasZero | noNegConstants | |
| Int | ||||
| Add | ||||
| Negate | ||||
| Mult |
函数式方式:
- 容易添加新的操作(增加一个函数),比如noNegConstants,不需要修改以前的代码
- 添加新的变体需要修改旧函数,但是ML类型检查会给出todo list(在原先的代码没有通用匹配的情况下),
OOP方式:
- 容易添加新的变体(增加一个类即可)
- 添加一个新的操作需要修改旧的类,但是Java的类型检查会在原先的代码没有默认方法的情况下给出todo list
不管是函数式还是OOP,都可以提前计划方便以后新增变体或操作。
函数式用High-order function添加新类型, OOP使用双派发(double-dispatch)模式添加新操作。
未来是难以预测的,我们或许不知道需要什么样的扩展,或者两种扩展都需要。
可扩展性是一把双刃剑:
- 以后不需要修改就可以代码重用
- 但需要写更多的原始代码
- 原始代码更难理解或修改
- 一些语言机制让代码更难扩展,比如ML的模块隐藏了数据类型;Java的final阻止子类化/覆盖。
3 使用函数式分解二元方法
使Add支持更多操作:
| Int | String | Rational | |
| Int | |||
| String | |||
| Rational |
函数式中使用case表达式就解决了,因为函数首先关心的就是操作,针对不同数据之间的操作用case表达式。
datatype exp = Add of exp * exp
| Int of int
| Negate of exp
| String of string
| Rational of int * int fun add_values (v1, v2) =
case (v1, v2) of
(Int i, Int j) => Int (i+j)
| (Int i, String s) => String(Int.toString i ^ s)
| (Int i, Rational(j,k)) => Rational(i*k+j,k)
| (String s, Int i) => String(s ^ Int.toString i)
| (String s1, String s2) => String(s1 ^ s2)
| (String s, Rational(i,j)) => String(s ^ Int.toString i ^ "/"
^ Int.toString j)
| (Rational _, Int _) => add_values(v2, v1)
| (Rational(i, j), String s) => String(Int.toString i ^ "/"
^ Int.toString j ^ s)
| (Rational(a,b), Rational(c,d)) => Rational(a*d+b*c, b*d)
| _ => raise BadResult "non-values passed to add_values"
4 双重派发
OOP更关心对象,一个消息发送过来,不知道怎么处理,让对象自己去处理,就是双重派发,也可以做到三重、四重(一个操作作用于三个、四个对象),但要写很多方法。
class Add < Exp
def eval
e1.eval.add_values e2.eval
end
end class Int < Value
# OOP中的双重派发
def add_values v
v.addInt self
end
def addInt v
Int.new(v.i + i)
end
end
5 Multimethods
也叫多重派发。
针对不同对象的同一操作,用同一个方法名,自动调用对应的方法。
ruby动态类型,方法不能重名,因此没有多重方法。Java和C++是静态类型,一个类可以有重名方法,但在编译时确定了参数的类型,叫做静态重载。
许多OOP语言有multimethods。比如Clojure中的multimethod和Scala中的trait。
6 多重继承
单继承的类层次是一个树,多重继承的类层次更复杂。
7 Mixins
mixin是一个方法集合,没有实例。含有mixins的语言的类大都只能有一个父类,但可以包含多个mixin.
module Doubler
def double
self + self
end
end class Pt
attr_accessor :x, :y
include Doubler
def + other
ans = Pt.new
ans.x = self.x + other.x
ans.y = self.y + other.y
ans
end
end class String
include Doubler
end
Ruby中最大的两个mixins是Comparable和Enumerable。
class MyRange
include Enumerable
def initialize(low, high)
@low = low
@high = high
end # 支持low>high的情况
def each
if @low <= @high
i=@low
while i <= @high
yield i
i=i+
end
else
i=@low
while i >= @high
yield i
i=i-
end
end
end
end for i in MyRange.new(,)
for j in MyRange.new(,)
print (i+j).to_s + " "
end
puts
end MyRange.new(,).each { |x|
MyRange.new(,).each { |y|
print (x+y).to_s + " " }
puts }
8 接口
静态类型中的类是一个类型。
接口也是一个类型,但不是一个类.
9 抽象方法
静态类型中支持覆盖的方法就是抽象方法。
作者: ntestoc
Created: 2019-01-06 日 22:19
programming-languages学习笔记--第9部分的更多相关文章
- CUDA Programming Guide 学习笔记
CUDA学习笔记 GPU架构 GPU围绕流式多处理器(SM)的可扩展阵列搭建,每个GPU有多个SM,每个SM支持数百个线程并发执行.目前Nvidia推出了6种GPU架构(按时间顺序,详见下图):Fer ...
- Programming Erlang 学习笔记(一)
入门 启动Shell 在cmd中输入命令”erl”,百分号(%)表示一个注释的开始,从百分号开始到这行结束的所有文本都被看做是注释. 一个完整的命令需要以一个句点和一个回车结束. 退出erlang的命 ...
- UIView Programming Guide学习笔记
|View |Creating and Configuring View Objects |Creating and Managing a View Hierarchy |Adjusting the ...
- The C++ Programming Language 学习笔记 第7章 函数
1.关于内联函数(inline) 借用一下书中的例子. inline int fac(int n) { ) ? :n*fac(n-); } inline描述符给编译器一个提示,要求 ...
- The C++ Programming Language 学习笔记 第6章 表达式和语句
1.关于strcpy函数. 书中说c风格的字符串尽量少用,strcpy这样的函数应该也要少用.这里讲这个函数主要是要通过本章课后练习第十题来讲一下前面提及的要点.巩固一下前几章的知识.写了一段,本来感 ...
- The C++ Programming Language 学习笔记 第5章 指针、数组和结构
1.关于输出指向字符的指针的值. 现在定义,char c='a',char* pc=&c.在C中,输出该值只需要printf("%p\n",pc);而在C++中,如果cou ...
- The C++ Programming Language 学习笔记 第四章 类型和声明
1.关于main 函数中的 return 0 C99标准中,main 函数的返回值类型必须是 int ,这样返回值才能传递给程序的激活者(如操作系统).如果 main 函数的最后没有写 return ...
- 3D Game Programming withDX11 学习笔记(一) 数学知识总结
在图形学中,数学是不可或缺的一部分,所以本书最开始的部分就是数学知识的复习.在图形学中,最常用的是矢量和矩阵,所以我根据前面三个章节的数学知识,总结一下数学知识. 一.矢量 数学中的矢量,拥有方向和长 ...
- Learning ROS for Robotics Programming Second Edition学习笔记(十) indigo Gazebo rviz slam navigation
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 moveit是书的最后一章,由于对机械臂完全不知,看不懂 ...
- Learning ROS forRobotics Programming Second Edition学习笔记(八)indigo rviz gazebo
中文译著已经出版,详情请参考:http://blog.csdn.net/ZhangRelay/article/category/6506865 Learning ROS forRobotics Pro ...
随机推荐
- HDFS要点剖析
谈到大数据,不得不提的一个名词是"HDFS".它是一种分布式文件存储系统,其系统架构图如下图所示: 从图中可以了解到的几个关键概念 元数据(MetaData) 机架(Rock) 块 ...
- 悟空模式-java-普通工厂模式
[大圣看玩多时,问土地道:“此树有多少株数?”土地道:“有三千六百株.前面一千二百株,花微果小,三千年一熟,人吃了成仙了道,体健身轻.中间一千二百株,层花甘实,六千年一熟,人吃了霞举飞升,长生不老.后 ...
- HTML5 Form Data 对象的使用
HTML5 Form Data 对象的使用 MDN: https://developer.mozilla.org/zh-CN/docs/Web/Guide/Using_FormData_Object ...
- 客户端ajax请求为实现Token验证添加headers后导致正常请求变为options跨域请求解决方法
客户端为了实现token认证,通过Jquery的ajaxSetup方法全局配置headers: 全局配置headers后会导致部分不需要token认证的请求变为options请求,导致跨域访问.报错信 ...
- java图形界面之图形化按钮
要将按钮图形化,只需创建一个ImageIcon对象,将图形路径赋予ImageIcon对象,然后将该对象传递给按钮即可. 此处涉及eclipse中图形的路径设置,包括(项目路径下.非项目路径下.相对路径 ...
- ECMAScript 5和ECMAScript6的新特性以及浏览器支持情况
ECMAScript简介: 它是一种由Ecma国际(前身为欧洲计算机制造商协会)制定和发布的脚本语言规范,javascript在它基础上经行了自己的封装.但通常来说,术语ECMAScript和java ...
- asp.ne如何使用javascript去验证客户端信息,如果验证成功则送往服务器端处理,否则在客户端提示用户(不返回到服务器端处理)
一.问题 在网站一般都有很多地方需要用户去填写一些信息,然后用户点击提交,将信息送往后台储存到数据库中.在这一个过程我以前做法直接在button的click事件中来判断用户输入的数据是否完整和合法,虽 ...
- redis安装以及php扩展
启动安装: http://elain.blog.51cto.com/3339379/705846 redis下载: https://github.com/nicolasff/phpredis/do ...
- 网络I/O模型--05多路复用I/O
多路复用I/O模型在应用层工作效率比我们俗称的 BIO 模型快的本质原因是,前者不再使用操作系统级别的“同步 I/O”模型 . 在 Linux 操作系统环境下, 多路复用 I/O 模型就是技术人员通常 ...
- Atitit.播放系统的选片服务器,包厢记时系统 的说明,教程,维护,故障排查手册p825
Atitit.播放系统的选片服务器,包厢记时系统 的说明,教程,维护,故障排查手册p825 1. 播放系统服务器方面的维护2 1.1. 默认情况下,已经在系统的启动目录下增加了俩个启动项目2 1.2. ...