Skip to main content

你的对象可能是个函数!MaxMSP 函数式编程学习

Contra | Fri, 04/14/2017 - 14:40

v2-9067b7abbd70726d38eb58b73ea3ed3d_1200x500

先来一首定场诗:

There is a cycle, a rhythm to the universe.

Today one program will be popular, tomorrow another.

Today 500 bugs will be fixed, tomorrow another 500 will appear.

To understand life is to know that the rhythm exists.

To understand Zen is to live outside this rhythm,

detached from the everyday concerns of life.

Only then can the mind be free.

---- 摘自《The Tao of Programming(编程之道)》

面向对象编程(Object-Oriented Programming - OOP)理论认为,“万物皆对象”。

不是搞对象的对象。
其实严格地说,搞对象的对象,好像也是对象。

面向对象可能是学校和企业产品上使用最多的编程范式。
一旦被洗脑,很难掰回来。如:对象说的永远是对的。

近几年开始接触和使用函数式响应式编程(Functional Reactive Programming - FRP)。

FRP组合了响应式编程(Reactive Programming - RP)与函数式编程(Functional Programming - FP)。

响应式编程 RP:侧重于处理异步数据流,可以监听数据流并做出响应。

比如Excel的单元格,可以包含类似"=B1+C1"的公式,这类单元格的值会依据其他单元格(B1和C1)的值的变化而变化 。

图片出处见水印

响应式编程处理流、监听流、根据流迅速做出响应。
这一点,我在幼年自学截拳道时就懂了:

“Be water, my friend. ”

函数式编程 FP:更像是在做数学运算。
“把运算过程尽量写成一系列嵌套的函数调用”(函数式编程初探 - 阮一峰的网络日志)。

注意,上图函数 f(x) 的输入参数 x,也可以是另一个函数。

尽管对函数式响应式编程 FRP 不算太陌生,也曾使用 ReactCocoa Objective-C 上线过几款 App,将 signal,map,subscribe 等等用的不亦乐乎。

但是从 “万物皆对象”,切换到 “万物皆函数”,还是有点懵。

尤其在融会贯通了 OOP 和 FP 后,想到“其实你的对象是个函数”,更是大写的锟斤拷烫烫烫。

图片出处见水印

所以还是把理论丢一边,例子摆中间:

此图源自很火的一篇 FRP 教程:
《The introduction to Reactive Programming you've been missing》
https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

图中的黑色箭头线条,即代表 stream,数据流。

“灰色的矩形是把一个 stream 转换成另一个 stream 的函数。我们会每隔 250ms 把所有 click stream 都缓冲在一个数组里面,这是 buffer(stream.throttle(250ms)) 所要做的事情。

于是,我们得到的是一个包含多个数组的 stream,接着调用 map() 函数,把每个数组都映射成一个整数(数组的长度)。
随后,我们调用 filter(x >= 2) 来过滤掉那些长度为 1 的数组。
综上,我们只需要3次操作就能得到我们想要的 stream 。最后,我们调用 subscribe() 来监听,响应我们想要做的事情。”

此段引自知乎。
可能是最好的 Rx 初学者教程 - 知乎专栏

实现这一段数据流的转换,用 RxJS 来写的话,只需要这样4行:

var multiClickStream = clickStream
    .buffer(function() { return clickStream.throttle(250); })
    .map(function(list) { return list.length; })
    .filter(function(x) { return x >= 2; });

得到 multiClickStream 后,subscribe 它,就可以监听到数据流中的值:

multiClickStream.subscribe(function (numclicks) {
    console.log(numclicks);
)};

上述对 stream 的函数式操作,去掉换行后:
stream.buffer().map().filter()
连起来看丝般顺滑。

如果把 “.” 改为 “-”:
stream - buffer() - map() - filter()
是不是看着跟连连看编程很像了?

以 MaxMSP 为例:

上图即是那段 RxJS 代码的 Max 版本,功能一样的。
一次单击时,并不会从 stream 最后输出,最后的 print log 里显示的仍是之前的一次3连击。

如果来一发4连击,则会通过 filter,最终得到:

可以看到,上图中绿色部分的 map 与 filter,细节的处理并不太“函数式”,还是像在用命令式告诉机器如何执行指令,而不是优雅的书写函数。

伯克利大学的新音乐与音频技术研究中心,Center for New Music and Audio Technologies (CNMAT),设计开发了一套 Max 的 Library —— ODOT

ODOT 基于 OSC,就是那个熟悉的 Open Sound Control。

首先,重点在于利用了 OSC 对数据的封装格式,name - value 结构。

其次,OSC 数据可以方便的在程序内从一个模块流向另一个模块,甚至从硬件 Arduino,通过网络,流向 Max、Processing、C、Java 等软件模块。

用 ODOT 重构上文的 map 与 filter 模块,如下图所示:

o.pack 是把流入的 stream 数据,封装成 OSC 格式。
而后边两句,就是很直观的函数书写了:

/data = length(/data) ; //取长度

o.if /data >= 2 ; //过滤2以上的数据。

上述例子源文件:
https://github.com/avantcontra/maxmsp_examples/tree/master/functional_programming

 

ODOT 还可以实现更多函数式特征的功能

高阶函数

递归

此图自下方参考论文

另,利用 OSC 数据结构,ODOT 其实还可以实现面向对象的风格,可参考下方论文。

ODOT 下载
Downloads | CNMAT

参考论文

《Composability for Musical Gesture Signal Processing using new OSC-based Object and Functional Programming Extensions to Max/MSP》

《DYNAMIC, INSTANCE-BASED, OBJECT-ORIENTED PROGRAMMING IN MAX/MSP USING OPEN SOUND CONTROL MESSAGE DELEGATION》

谢谢阅读。


不要慌,再回味一下开篇定场诗里的精华:
Today 500 bugs will be fixed, tomorrow another 500 will appear.

微信公众号:浮生开方
HUDO.IT on Slack: hudoit.herokuapp.com




 

Leave a Comment