阅读27:小语言 I

6.031中的软件

Safe from bugs :在今天可以改正,在以后也可以改正。
Easy to understand :与未来的程序员(包括未来的您)能进行清晰的沟通。
Ready for change :指能适应变化而无需重写。

目标

在本阅读中,我们将开始探索一种用于构造和操纵音乐的小语言的设计。底线是:当您需要解决问题时,不要编写程序来解决一个问题,而是构建一种可以解决一系列相关问题的语言。
阅读的目的是介绍将代码表示为数据的想法,并使您熟悉音乐语言的初始版本。

将代码表示为数据

Formula从“递归数据类型”中调用数据类型:

Formula = Variable(name:String)
+ Not(formula:Formula)
+ And(left:Formula, right:Formula)
+ Or(left:Formula, right:Formula)

我们使用的实例Formula采用命题逻辑公式,例如(p ∨ q) ∧ (¬p ∨ r)并在数据结构中表示它们,例如:

And(Or(Variable(“p”), Variable(“q”)),
Or(Not(Variable(“p”)), Variable(“r”)))

用语法和语法分析器来说,公式是一种语言,并且Formula是抽象的语法树
但是为什么我们要定义一个Formula类型呢?Java已经有一种方法可以用逻辑和(或)和(不是)来表示布尔变量的表达式。例如,给定boolean的变量p,q以及r:

(p || q) && ((!p) || r)

完毕!
答案是,(p || q) && ((!p) || r)一旦在运行的程序中遇到Java代码表达式,就会对其求值。该Formula值And(Or(…), Or(…))是一等值,可以根据需要将其存储,从一种方法传递并返回到另一种方法,进行操纵和评估,现在或以后(或多次)进行评估。
该Formula类型是一个例子代表代码数据,我们已经看到了许多。
考虑以下功能对象:

class VariableNameComparator implements Comparator<Variable> {public int compare(Variable v1, Variable v2) {return v1.name().compareTo(v2.name());}
}

的实例VariableNameComparator是可以传递,返回和存储的值。但是在任何时候,都可以通过compare使用几个Variable参数调用其方法来调用其表示的功能:

Variable v1, v2; Comparator c = new
VariableNameComparator(); …int a = c.compare(v1, v2);int b =
c.compare(v2, v1); SortedSet vars = new TreeSet<>©; //
vars is sorted by name

Lambda表达式使我们可以使用紧凑的语法创建功能对象:

Comparator c = (v1, v2) -> v1.name().compareTo(v2.name());

建立语言来解决问题

当我们定义抽象数据类型时,我们将扩展Java提供的内置类型的范围,以包括一种新的类型和具有适用于我们的问题领域的新操作。这种新类型就像一种新语言:我们可以操纵的一组新的名词(值)和动词(运算)。当然,这些名词和动词是在现有本身已经是抽象的名词和动词之上构建的抽象。
一种语言不仅仅是一种程序更大的灵活性,因为我们可以用语言来解决一大类的相关问题,而不仅仅是一个单一的问题。

  • 那就是编写(p || q) && ((!p) || r)和设计一个Formula表示语义上等效的布尔公式的类型之间的区别。
  • 这是编写矩阵乘法函数和设计表示矩阵乘法的MatrixExpression类型之间的区别-进行存储,操作,优化,评估等等。

一流的函数和功能对象使我们能够创建特别强大的语言,因为我们可以将计算模式捕获为可重用的抽象。

音乐语言

在课堂上,我们将设计和实现一种用于生成和播放音乐的语言。为了准备,首先让我们了解使用MIDI合成器播放音乐的Java API 。我们将看到如何编写程序来播放MIDI音乐。然后,我们将通过为简单的音乐曲调编写递归抽象数据类型来开始开发音乐语言。我们将选择一种以字符串形式编写音乐的符号,并且将实现一个解析器来创建我们Music类型的实例。

基本音乐语言的完整源代码在GitHub上。 克隆的ex27音乐启动回购所以你可以运行代码,然后按照下面的讨论。 播放MIDI音乐

music.midi.MidiSequencePlayer使用Java MIDI API播放音符序列。这是很多代码,您不需要了解它是如何工作的。
MidiSequencePlayer实现该music.SequencePlayer接口,使客户端可以在不依赖于特定MIDI实现的情况下使用它。我们确实需要了解此接口及其依赖的类型:
addNote : SequencePlayer × Instrument × Pitch × double × double → void
SequencePlayer.java:15)是我们音乐播放器的主力军。调用此方法可以安排在一段音乐的某个时间播放音乐音调。
play : SequencePlayer → void
SequencePlayer.java:20)实际上是在播放音乐。在调用此方法之前,我们只是在计划最终将要播放的音乐。
该addNote操作取决于另外两种类型:
Instrument 是所有可用的MIDI乐器的枚举。
Pitch 是音高的抽象数据类型(请考虑钢琴键盘上的键)。

阅读并了解Pitch其公共构造函数及其所有公共方法的文档和规范。
我们的音乐数据类型将依赖Pitch于它的rep,因此请务必了解Pitch规范及其rep和抽象功能。

使用MIDI序列播放器和Pitch,我们就可以为音乐的第一部分编写代码了!

阅读并理解music.examples.ScaleSequence代码。 在中运行main方法ScaleSequence。
您应该听到一个八度音阶!

阅读练习

Pitch
transpose
addNote

音乐数据类型
该Pitch数据类型是有用的,但是如果我们想用代表一个整首乐曲的Pitch对象,我们应该创建一个抽象数据类型来封装表示。
首先,我们将Music通过一些操作来定义类型:
notes : String × Instrument → MusicMusicLanguage.java:53)从一串简化的abc表示法制作一个新的Music,如下所述。
duration : Music → doubleMusic.java:11)返回音乐的持续时间(以拍子为单位)。
play : Music × SequencePlayer × double → voidMusic.java:18)使用给定的序列播放器播放音乐。
我们将实现duration和play作为实例方法Music,因此我们在Music接口中声明它们。
notes将是静态工厂方法;而不是将其放入Music(我们可以这样做),我们将其放入一个单独的类中:MusicLanguage它将是我们编写要在其上进行操作的所有静态方法的场所Music。
现在,我们已经选择了规范中的一些操作Music,让我们选择一种表示形式。

  • 看一下ScaleSequence,可能会跳出来的第一个具体变体是捕获每次调用的信息addNote:在特定乐器上演奏一定时间的特定音高。我们将其称为Note

  • 音乐的另一个基本元素是音符之间的静音:Rest

  • 最后,我们需要一种将这些基本元素粘合到更大的音乐片段中的方法。我们将选择一个树状结构:**Concat(m1,m2:Music)**代表,m1之后是m2,其中m1和m2是任何音乐。

    Music后来我们进一步开发类型时,这种树形结构确实是一个明智的决定。在实际的设计过程中,我们可能会Music在找到最佳实现之前对的递归结构进行迭代。

这是数据类型定义:

Music = Note(duration:double, pitch:Pitch, instrument:Instrument)
+ Rest(duration:double)
+ Concat(m1:Music, m2:Music)

Composite

Music是复合模式的一个示例,其中我们以相同的方式对待单个对象(例如,图元Note和Rest)和对象组(例如,图复合物Concat)。

  • Formula 也是复合图案的一个例子。
  • 图形用户界面(GUI)视图树在很大程度上依赖于复合模式:存在像和这样的原始视图没有子级,而像和这样的复合视图确实包含其他视图作为子级。两者都实现公共接口。

复合模式产生了一个树数据结构,在叶上有图元,在内部节点上有复合。

Emptiness

最后一个设计考虑因素:我们如何表现空虚的音乐?无所代表的表示总是很好,而且我们当然不会使用null。
我们可以引入一个Empty变体,但是我们将使用一个Rest持续时间0来表示空虚。

实施基本操作

首先,我们需要创建NoteRestConcat变种。从构造函数checkRep,一些观察者toString和相等方法开始,这三种方法都易于实现。

  • 由于该duration操作是实例方法,因此每个变体都可以duration适当地实现。
  • 该play操作也是一个实例方法;我们将在实现播放器的下面讨论它。

我们将讨论笔记操作,该笔记操作是在实现解析器的情况下在MusicLanguage类中作为静态方法实现的。

阅读和理解Note,Rest以及Concat类。

为了避免出现表示形式,让我们添加一些其他的静态工厂方法来构建Music实例:
note : double × Pitch × Instrument → MusicMusicLanguage.java:100
rest : double → MusicMusicLanguage.java:108
concat : Music × Music → MusicMusicLanguage.java:121)是我们的第一个制作人操作。
通过构建适当的变体,可以轻松实现所有这三个。

阅读练习

Music rep

音乐符号

我们将使用abc符号(基于文本的音乐格式)的简化版本编写音乐。
我们已经使用他们熟悉的字母来表示音高。我们的简化abc表示法表示音符和休止符序列,并带有用于指示其持续时间,偶然(尖锐或平坦)和八度的语法。
例如:
C D E F G A B C’ B A G F E D C代表我们在中演奏的八度音阶C大调音阶ScaleSequence。 C是中音C,并且C’是比中音C高八度的C。每个音符都是四分音符。
C/2 D/2 _E/2 F/2 G/2 _A/2 _B/2 C是C小调的升阶,演奏速度是后者的两倍。E,A和B是平坦的。每个音符都是八分音符。

阅读并了解notes in MusicLanguage的规范。
您还不需要了解解析器的实现,但是您应该足够了解简化的abc表示法,以使示例更有意义。

如果您不熟悉音乐理论-为什么八度音阶为8个音符,而只有12个半音?—不用担心。您可能无法查看abc字符串并猜测它们听起来像什么,但是您可以理解选择方便的文本语法的要点。

阅读练习

简化的abc语法

实现解析器

该notes方法将简化的abc表示法的字符串解析为Music。
notes : String × Instrument → MusicMusicLanguage.java:53)将输入到单个符号(例如A,/2,.1/2)。我们从empty Music,rest(0)符号开始分别进行解析,并建立Musicusing concat。
parseSymbol : String × Instrument → MusicMusicLanguage.java:64)对于单个abc符号(在语法中)返回aRest或a 。它仅解析类型(休息或音符)和持续时间;它依靠它来处理音高,偶然和八度。NotesymbolparsePitch
parsePitch : String → PitchMusicLanguage.java:79)Pitch通过解析pitch语法产生返回a 。您应该能够理解递归-基本情况是什么?递归情况是什么?

阅读练习

parsePitch

实施球员

回顾我们播放音乐的操作:
play : Music × SequencePlayer × double → voidMusic.java:18)在给定的节拍延迟后,使用给定的序列播放器播放音乐。
为什么要进行此操作atBeat?为什么不直接播放音乐现在?
如果play以这种方式定义,除非我们在操作过程中实际暂停play(例如使用),否则我们将无法随时间播放音符序列Thread.sleep。我们的序列播放器的addNote操作已经设计为将来安排音符-它可以处理延迟。
有了该设计决策,就可以play在中的每个变体中轻松实现Music。

阅读和理解Note.play,Rest.play和Concat.play方法。 您应该能够遵循其递归实现。

在我们准备阻塞之前,只需再添加一个实用程序代码即可:使用music.midi.MusicPlayer播放一个。 我不知道序列播放器的具体类型,因此我们需要一些代码才能将它们组合在一起。MusicMidiSequencePlayerMusic
将这一全在一起,让我们使用Music ADT:

阅读并理解music.examples.ScaleMusic代码。 在中运行main方法ScaleMusic。
您应该再次听到相同的八度音阶。

那不是很令人兴奋,因此请阅读 music.examples.RowYourBoatInitial并运行main方法。
您应该听到一排又一排划船的声音!
您可以遵循代码的流程,从调用notes(…)到实例化Music到递归play(…)调用再到单个addNote(…)调用?

阅读练习

notes
duration
Music

待续
逐行播放您的船非常令人兴奋,但是到目前为止,我们做的最有力的事情不是音乐语言,而是最基本的音乐解析器。 与使用addNote addNote addNote逐页书写相比,使用简化的abc书写法写音乐显然更容易理解,易受错误影响并且随时可以更改。
在接下来的课程中,我们将扩展我们的音乐语言,并将其转变为构建和操纵复杂音乐结构的强大工具。

麻省理工学院|软件构造|课程翻译相关推荐

  1. 哈工大18年春软件构造课程讨论题

    这是哈工大18年春软件构造课程(徐汉川老师)的讨论题目,少部分答案摘录自课件PPT和网上的资源(链接在文中给出).如有错误还望指出,谢谢. 一.在软件测试过程中,"测试用例的数目" ...

  2. 哈工大软件构造课程知识点总结(一)

    系列文章目录 哈工大软件构造课程知识点总结(一) 哈工大软件构造课程知识点总结(二) 哈工大软件构造课程知识点总结(三) 哈工大软件构造课程知识点总结(四) 哈工大软件构造课程知识点总结(五) 哈工大 ...

  3. 哈工大软件构造课程知识点总结(二)

    系列文章目录 哈工大软件构造课程知识点总结(一) 哈工大软件构造课程知识点总结(二) 哈工大软件构造课程知识点总结(三) 哈工大软件构造课程知识点总结(四) 哈工大软件构造课程知识点总结(五) 哈工大 ...

  4. 哈工大软件构造课程知识点总结(三)

    系列文章目录 哈工大软件构造课程知识点总结(一) 哈工大软件构造课程知识点总结(二) 哈工大软件构造课程知识点总结(三) 哈工大软件构造课程知识点总结(四) 哈工大软件构造课程知识点总结(五) 哈工大 ...

  5. 软件构造课程心得——软件构造实验二(Lab2)

    软件构造课程心得--Lab2 1. 实验目标概述 本次实验训练抽象数据类型(ADT)的设计.规约.测试,并使用面向对象 编程(OOP)技术实现 ADT.具体来说: 针对给定的应用问题,从问题描述中识别 ...

  6. 软件构造课程自我总结

    以下仅谈我个人这一学期软件构造学习印象最深的内容部分和个人学习成果: 1.为中等规模的程序应用基本的编程概念和ADT设计 2.理解基本类型和静态类型的好处,了解泛型,子类型和重载,以及它们在构造程序中 ...

  7. 2022哈工大软件构造课程总结与经验分享(复习指导)

    一.软构1-3讲 1.软件构造的多维度视图和质量目标 2.软件测试与测试优先的编程 3.软件构造过程与配置管理 二.软构4-8讲 4.数据类型与类型检验 5.设计规约 6.抽象数据类型 (ADT) 7 ...

  8. 麻省理工学院计算机系硕士课程,麻省理工学院计算机专业研究生申请 你听说过《算法导论吗》?...

    计算机专业的范围广泛,美国许多学校下设的计算机专业方向也很多.一般来讲,计算机有以下方向:人工智能,计算机程序设计,计算理论,软件工程,计算机图形学,数据库,信息学,信息系统,信息技术,信息安全,管理 ...

  9. 哈尔滨工业大学软件构造课程学习笔记第一章第一节

    1.1 软件构造过程中的多维度视图 一.多维软件视图 1.构建阶段 构建阶段:观点->需求->设计->代码->可安装/可执行包 代码角度:源代码--源代码的逻辑组织方式通过基本 ...

最新文章

  1. Seam的中文化支持
  2. 记住,永远不要在MySQL中使用“utf8”
  3. apache arrow mysql_新手搭建PHP环境必备知识:windows下PHP5+APACHE+MYSQ完整配置(个人总结)...
  4. Java throw:异常的抛出
  5. WPF中的容器控件——canvas
  6. 牛客小白月赛17-记录(附题解)
  7. guava读取配置文件_使用Guava MapSplitters配置Hadoop
  8. 【渝粤教育】电大中专跨境电子商务理论与实务 (29)作业 题库
  9. Python高级——闭包与装饰器
  10. Python库:Pyinstaller库、pip工具、pip指定安装源和版本
  11. 学习Python编程培训 有哪些爬虫技术课程需要掌握
  12. Swift进阶 - 更高效的使用集合
  13. PreScan笔记(2.1)——MATLAB更改初始data model文件中的参数
  14. 开发人员的不断流动、让我们更加坚定信念,一定要控制好整个系统的底层架构、核心设计、日常质量检查工作
  15. 2010年上半年5月份系统分析师上午试题答案(分析与解答)之十
  16. 宏转录组测序数据菌株层面的分析软件总结
  17. OpenCV4 快速入门 (学习笔记 全)
  18. linux keypad driver
  19. 什么是BOM(Byte Order Mark)?
  20. python 长字符串 ,每行指定长度输出

热门文章

  1. 免线圈高频无线充电IC无线供电芯片方案芯片XKT-511
  2. 第十周 项目一 计算税后收入
  3. Trino 本地编译搭建 standalone 模式
  4. 汉字转拼音工具JPinyin的介绍和使用示例
  5. 大数据学习第一课:虚拟机安装配置
  6. 初学solidworks,这些基础知识你必须要掌握!
  7. python记录程序运行时间的三种方法
  8. Listener refused the connection with the following error
  9. 32怎么通过一个按键实现不同工作模式_游戏工作室防封IP,免费领!!!魔兽世界怀旧版独享IP免费送...
  10. VUE购物车小案例—vue指令的综合应用