從 Stream 和函數式編程想到的

f627 9年前發布 | 16K 次閱讀 stream

面向對象的問題

我作為自學的程序員, 繞了很多彎子, 缺了很多編程的基礎理論
作為前端我也能拉很多人下水, 因為很多寫界面的人也是自學的
編程語言從 Fortran 跟 Lisp 已經被研究了半個世紀, 理論成果也是連篇累牘
我們常常覺得自己已經在編程了, 但是基于什么編程呢?

首先編程當然是對真實世界的情況的模擬, 這一點作為基礎
當然問題關鍵是, 用什么來模擬?
一類是表達式, 或者說遞歸嵌套的表達式, 比如((x * x) + (y * y))
不過表達式功能有限, 注意到嗎, 這里邊是沒有狀態的
那么, 有狀態的對象怎么模擬? 面向對象編程(OOP)說, 用面向對象啊

其實"對象(Object)"這個詞有點濫用翻譯了, 什么都是 Object 啊
OOP 當中的對象非常特別, 特指有內部狀態的對象
比如鼠標的位置P, 通過P.get()讀取, 會根據具體情況改變
面向對象以此為基礎, 說, 這可以模擬真實世界, 對象有內部狀態
于是編程就是各種對象通過接口交換內部狀態, 而最終形成

然而函數式編程來說, 它也有對象, 但并沒有可變的那種對象
Rich Hichkey 說, 一個值變來變去, 那就是不可靠的
特別是在復雜的系統中, 甚至并行當中, 發生混亂了怎么定位
其實函數式編程也有講上邊的問題, 真實世界各種狀態, 怎么模擬?
給了個例子, 比如英國國王是誰? 不清楚! 但是具體哪一年英國國王是誰? 知道了!
值并不是任意改變的, 而是隨著時間改變的, 就像f(x)函數
內存里的數據也不是任意改變的, 而是每個時刻有一個確定的狀態

SICP

所以, 在函數式變成當中, 隨著時間改變的就不是變量, 而是"流(Stream)"
這一點, 在 SICP 當中關于流的介紹做了這樣的說明:
http://sarabander.github.io/sicp/html/3_002e5.xhtml#g_t3_002e5

Can we avoid identifying time in the computer with time in the modeled world? Must we make the model change with time in order to model phenomena in a changing world?

</blockquote>

Think about the issue in terms of mathematical functions. We can describe the time-varying behavior of a quantity x as a function of time x(t). If we concentrate on x instant by instant, we think of it as a changing quantity. Yet if we concentrate on the entire time history of values, we do not emphasize change — the function itself does not change.

</blockquote>

If time is measured in discrete steps, then we can model a time function as a (possibly infinite) sequence. Stream processing lets us model systems that have state without ever using assignment or mutable data.

</blockquote>

剛接觸 Lisp 那時經常看到帖子里有人推薦 SICP, 說是很棒的書
里邊有大量的習題, 我看不下去, 到最近也只是把文字描述瀏覽一遍
http://kidneyball.iteye.com/blog/922953 說的徹頭徹尾的教程啊

SICP,Structure and Interpretation of Computer Programs,計算機程序的構造和解釋,是美國麻省理工學院(MIT)的計算機科學(CS)與電子工程(EE)本科的一門必修課。這本書在1984年 出版,而自從1980年開始,20多年來此書的內容一直是MIT的計算機編程入門課程,并且被世界各地百余所大學效仿。SICP是基于LISP語言展開論 述的,直到2008年,才被另一門基于python語言,但原理相同的課程取代。

</blockquote>

不管怎樣, 真的是一本很棒的書, 即便我很不喜歡看, 也不愛做習題
現在的編程當中遇到的很多問題, 至少理論上很早就被研究過了
SICP 當中程序數據甚至語言本身的抽象, 今天的腳本語言仍然混淆
而我也最近才慢慢學習理解到 Stream 這個概念有多么重要

架構圖

先拋開代碼, 看看現實當中的大問題我們怎樣思考, 我們常常畫圖
圖形界面怎樣編寫, 有 MVC 這樣的方案, 拆分出一些大的模塊
然后用戶有操作, 服務器有數據, 就開始在幾個模塊之間流動, 像這樣:

React 造出來 Flux, 至少看架構圖差別不是那么大, 也是幾個組成部分
然后用戶操作 Action, 以及數據, 沿著一個方向流動, 像這樣:

Apple 的 JavaScript 引擎, 用 LLVM 優化編譯 JavaScript 代碼
整個流程也劃分成了一些模塊, 然后數據在模塊之間逐個傳遞, 像這樣:

對于前端開發者來說 DOM 和 CSSOM 的解析也是被拆解的過程
兩者分別解析, 最后合并成一個用于渲染的 DOM Tree, 最后渲染
整個過程其實是從網絡加載代碼, 沿著箭頭流動處理的過程, 像這樣:

游戲引擎的架構我不熟悉, 不過拆分了模塊之后大概的意思也好懂, 像這樣:

我想說的是, 解決問題時, 拆分模塊, 在之間傳遞數據, 很通用的辦法
編程常常就是大問題拆成小問題, 然后拼在一起解決, 也是這意思

FBP(Flow-based Programming)

如果按照上邊的思路直接去找類似的編程語言, 那也是有的
FBP 的概念在 1970s 就出來了, 具體說來我也不懂, 自己看 Wiki 吧
https://en.wikipedia.org/wiki/Flow-based_programming
不過前兩年 Node 社區有個 Noflo 很火, 看界面也是挺棒的
只要找到需要的模塊, 然后連一連線條, 就能把程序寫出來了:

Noflo 也許不實用, 但是基于 Quartz Composer 的 Origami 總能用了
這個軟件里通過拖拽就能創建出可以交互的圖形界面
第一次看同事演示的時候, 盡管知道 Noflo, 還是覺得這很高明很強大
而且也說明, 復雜程序分明是可以不寫代碼, 直接拖著就出來了的:

然后來回顧一下, 我們每天寫的代碼, A 狀態改變, B 怎么跟著改變?
A 發生了什么, 我做事件監聽, 然后對 B 進行操作, B 終于跟著改變了
可是用上邊的圖形呢, 拖一條線, 把 B 跟 A 關聯起來, 不就好了嗎

Streams 和 Monad

晚上我翻到一片文章, 講的是 Swift 當中的 Monad
我知道這文章就是從 Haskell 改寫的, 而且圖片還非常好玩
http://www.mokacoding.com/blog/functor-applicative-monads-in-pictures/



Stream 在 Haskell 里大概也是 Monad 實現的, 細節我有點模糊
反正 Haskell 里的 State 都用 Haskell 封裝隔離, 也不會差太多了
Monad 很有意思, 被比作一個盒子, 盒子里裝了數據, 甚至還裝了函數
然后 Monad 講的都是數據甚至函數包在盒子里怎么去操作的故事
過程不難懂, 但有個事情很費解, 好端端地干嘛把數據裝進盒子里?
我寫 CoffeeScript 那么久了, 直接操作數據多方便, 為什么要盒子包起來?

到這里! 我要跳躍了, 回想一下前面說的 Stream, 還有架構圖, 數據在哪?
Stream 是隨著時間改變的數據, 是一個流, 不是單個單個的數據, 有盒子對吧
架構圖上, 數據在模塊里被處理, 沿著箭頭被模塊發送到另一個模塊,
然而, 注意編程語言怎樣發送數據,O.emit(data)嗎, 但是內部實現是什么?
再想一下, 是應該有一個管道, 數據被發送到管道里去了, 也像是盒子對吧
模擬狀態的時候, 常常有一層封裝, 就像是盒子, 或者說管道, 比如說 Stream

而且如果把 Stream 架構圖上的箭頭用 Stream 來替換的話, 就更清晰了
通常調用函數獲取數據, 在圖上相當于從后邊調用前邊模塊的數據
這個過程看起來流程后面的組件在驅動, 而不是前面的模塊在驅動
但是 Stream 的話, 當然是前面的模塊執行完, 再流動到后面的模塊
加入流的概念之后, 架構圖的方向和思路就清晰多了
我這樣說, 是因為看過 Elm 和 PureScript 當中 Signal 實現 Flux 的例子
相比 JavaScript 當中 Flux 用 Dispatcher, Signal 的寫法清晰太多了

我們想要模擬現實世界, 然而面向對象聲稱的辦法依然不夠強大
可變的狀態, 狀態一改變就消失了, 再也找不回來
監聽和操作數據, 寫起代碼來卻是各種繁瑣, 依然需要繼續做抽象
我想到 Node.js 的 Stream, JavaScript 是 OOP, 然而實現了 Stream
Stream 可以是基于 EventEmitter 的封裝, 把數據包在事件流當中的
而 Stream 帶來什么樣的方便呢, 組合起來很很靈活

流還是很難

前面羅列那么多, 并不是說我理解了, 而是說終于我把一些困惑串在一起了
我知道了為什么會有 Stream, 為什么 Stream 很重要, 我應該往哪兒學習
Node.js 當中實現的流相對簡單, 前端事件流也還不錯
但是也有復雜的, 敢不敢看看 PureScript 當中 UI 操作的 Signal 是如何處理的
http://begriffs.com/posts/2015-07-10-design-of-purescript-halogen.html
Signal 相當于流的概念, 但在 ADT 類型當中真是太難理解了

另外 Go 的 Channel 似乎也是流, 雖然有了新的名字, 新的操作語法
還有 Java 之類我無法理解的語言, 其中當然也實現了 Stream, 但我不懂
不懂所以文章寫到這里就結尾吧...

</div>
來自:http://segmentfault.com/a/1190000002992542

 本文由用戶 f627 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!