網上扒了很多連接, 看了之後對問題有點改觀, 可是消化不掉
因此整理一下放在這裏, 但願有點提高, 並且能夠討論下這個問題
Clojure 教程當中明明白白講過 Atom, 因此可變數據的態度明確
Clojure 裏就是總體用不可變數據, 而可變的部分用 Atom 包裹起來
到了 ClojureScript 更放開了調用 JavaScript 代碼時的可變狀態
因爲 Clojure 沒有強制封裝反作用, 總體感受是比較輕鬆的html
與 Clojure 對比, Haskell 裏對於可變數據, 在教程上極少說起
做爲純函數語言, 並且限制反作用, 於是 IO 相關的篇幅很晚才介紹
好比 learn you a haskell 半本過去仍是純數據, 都沒有講反作用
我猜想這也許是 Haskell 推廣的一個問題, 反作用講得太不清楚了
對於應用開發者而言, 文件, 數據庫, 圖形界面, 網絡, 這纔是工做的主體
當 Haskell 對咱們工做的主體部分作大量的限制, 等於限制咱們的工做git
好比我在寫網頁應用時, 大量的 DOM 操做, 網絡讀取, 都是反作用
直到 React 對 DOM 總體進行了封裝, 事情纔有所改觀
但即使這樣, 反作用仍是任務的主體, 在純的組件以外, 絲絕不減小反作用
我回顧本身學 Haskell, 熟悉了高階函數不少用法, 但類型和反作用仍然菜鳥
除了 cljs 更合適的緣由以外, 我真的開始認爲 Haskell 教程設計存在問題
對於反作用的講解和改進, 作得也許真的不夠多github
好比 IORef 這個東西, 我刷了幾年社區了, 根本不知道有
交流會次日早上我試着去搜 Haskell 有沒有辦法模擬 Clojure 的 Atom
結果給我搜索到了, Wiki 上有, StackOverflow 有, 我的博客有
然而卻不在教程上, 卻是 PureScript 那份很長的教程結尾有一些
因此結論是, Haskell 當中有可變數據, 但不多有介紹, 並且不推薦用
從這個角度看 Haskell 的教程真的沒有把咱們照顧好數據庫
只是說我遇到的例子吧, 在用了 Clojure 以後發現仍是很難幹掉 mutable 狀態
在 MVC 中, 雖然 View 組件內部, Model 單頁內部, 能夠作到 pure
可是在 MVC 各個部分之間, 難以梳理造成直白的依賴關係
好比說 Model 的更新, 下一個 Model 依賴上一個 Model 狀態, 本身依賴本身
Controller 要把 MVC 鏈接成一個循環, 而循環致使循環引用
單純靠不可變數據的寫法, 不可能構造出這樣一個程序出來
加上 View 好比是 DOM, DOM 是可變狀態, 很難說用不可變數據去搞定編程
在 React 社區當中各類觀點說, 須要用 Immutable 來加強性能和可靠
但 Immutable 是體如今組件共享數據的過程中, 並非全部位置
這也許是我學習過程的一個誤區, 覺得 Immutable 解決一切, 實際固然不是
而 js 總體可變帶來一種誤解, 咱們不知道區分數據可變和不可變
(在 C 層級的語言中有 const, 那屬於硬件性能因素, 場景不一樣不討論)
換成 Atom 的概念就是, 數據不可變的, 但會用到可變的引用後端
好比有一個狀態稱爲 a
, 最開始數據是 a0
, 通過操做後數據是 a1
那通常咱們就理解 a
是從 a0
變成了 a1
, 在 js 裏寫 a=a0
和 a=a1
但按照 Atom 的概念仔細想, 不對, a
和 a0 a1
的類型是不同的
好比說 a
是車的位置, 那麼 a
將隨着時間改變, 至關於 f(t)
而 a0
做爲車的位置, 它卻不變, 爲何, 由於它是具體一個時間的位置
一個是跟時間相關的狀態, 一個是座標, 怎麼能徹底一致呢?
在 js 裏沒有區分, 可是 Clojure 區分了, a
是 Atom
, 類型不同安全
編程裏黑科技太多, 我也不敢把話說絕, 但至少我認爲這是一個場景
這種狀況下不可變數據是可靠的, 而可變狀態也是被須要的
Haskell 做爲通用編程語言, 缺失這種功能太不合常理了
實際上可變狀態這種東西, Haskell 歷來沒說過沒有, 只是我會錯意了
Haskell 說, 沒有可變數據, 全部狀態都是隔離的
並且教程上不會教我怎麼去寫模擬 Atom 的可變狀態這種東西 - -!網絡
而後我就搜索到了各樣一個問題, 怎麼模擬全局變量:
Haskell - simulate global variable(function)
問題的回答裏首先給了經典的 do 表達式封裝局部狀態的寫法
而後纔開始講萬不得已的話, 用 ugly tricky 的 IORef
:閉包
import Data.IORef import System.IO.Unsafe import qualified Data.Map as Map {-# NOINLINE funcs #-} funcs :: IORef (Map.Map String Double) funcs = unsafePerformIO $ newIORef Map.empty f :: String -> Double -> IO () f str d = atomicModifyIORef funcs (\m -> (Map.insert str d m, ())) g :: IO () g = do fs <- readIORef funcs if (Map.lookup "aaa" fs) == Nothing then error "not defined" else putStrLn "ok" main = do f "aaa" 1 g
g
和 f
兩個函數分別去讀和寫, 好吧, 真的很像全局變量了
而後第二個答案又來一個局部變量, r
, 每次讀取時在閉包裏 +1:編程語言
import Data.IORef type Counter = Int -> IO Int makeCounter :: IO Counter makeCounter = do r <- newIORef 0 return (\i -> do modifyIORef r (+i) readIORef r) testCounter :: Counter -> IO () testCounter counter = do b <- counter 1 c <- counter 1 d <- counter 1 print [b,c,d] main = do counter <- makeCounter testCounter counter testCounter counter
立刻腦補一個套路同樣的 CoffeeScript 版本, 看閉包:
makeCounter = -> r = 0 (i) -> r = r + i r testCounter = (counter) -> b = counter 1 c = counter 1 d = counter 1 console.log [b,c,d] do -> counter = makeCounter() testCounter counter testCounter counter
因此, Haskell 中是有辦法模擬出可變狀態的, 用的是好比 IORef
答案裏也說了, 比較亂來的黑科技, 不推薦用(用了就不嚴謹不安全了)
而後除了 IORef
, 還有 STRef
, 好比這兩個提問:
When is it OK to use an IORef?
When to use STRef or IORef?
扒到一篇博客講 IORef
, ST
, MVar
這幾個事情的, 跟並行計算有關
Mutable State in Haskell
開頭有句話我以爲比較適合用來解釋 Immutable 到底算什麼:
All variables are indeed immutable, but there are ways to construct mutable references where we can change what the reference points to.
數據是不可變的, 只是有時候須要一個可變的引用來幫助指向不一樣的數據
文章還給了一點例子, 其中的寫法就和 Clojure 裏的 Atom 挺像了:
import Data.IORef main :: IO () main = do ref <- newIORef (0 :: Int) modifyIORef ref (+1) readIORef ref >>= print
Clojure 顯得短一些(對比下 print
, Clojure 是不考慮反作用的類型問題):
(defn -main [] (let [ref (atom 0)] (swap! ref inc) (println @ref)))
整個文章大部分看不懂, 只是瞭解一下其中可變狀態的處理
可是能夠看到, Haskell 即使能夠搞出可變狀態, 它仍是作了隔離的IORef
返回的結果, 依然包裹在一個 IO
當中, 而不是直接的值
經過這一點, Haskell 仍是會把反作用給識別出來, 而不像常見編程語言
雖然我不喜歡, 但確實要程序更可靠仍是少不了藉助這類辦法
對了, 忘了貼一個官方 Wiki 上的相關文章, 看不懂地方更多 - -!
Top level mutable state
PureScript 據說在狀態方面抽象得更細緻一點
由於要編譯到 JavaScript, 須要涉及狀態的地方多了許多
不過它整體的文檔很少, 大概找到幾個可變狀態的介紹:
http://www.purescript.org/learn/eff/
collatz :: Int -> Int collatz n = pureST do r <- newSTRef n count <- newSTRef 0 untilE $ do modifySTRef count $ (+) 1 m <- readSTRef r writeSTRef r $ if m `mod` 2 == 0 then m / 2 else 3 * m + 1 return $ m == 1 readSTRef count
大體上是 Haskell 的 STRef
, 我看不出區別,
編譯結果的 JavaScript 是這樣:
var collatz = function (n) { return Control_Monad_Eff.runPure(function __do() { var r = n; var count = 0; (function () { while (!(function __do() { count = 1 + count; var m = r; r = (m % 2 === 0) ? m / 2 : 3 * m + 1; return m === 1; })()) { }; return {}; })(); return count; }); };
另外在書上還寫了一些, 寫得大概能看懂, 可是我估計本身不會用:
https://leanpub.com/purescript/read#leanpub-auto-global-mutable-state
https://leanpub.com/purescript/read#leanpub-auto-mutable-state
看下找到的代碼的例子:
https://github.com/purescript/purescript/blob/master/examples/passing/Eff.purs
module Main where import Prelude import Control.Monad.Eff import Control.Monad.ST import Control.Monad.Eff.Console test1 = do log "Line 1" log "Line 2" test2 = runPure (runST (do ref <- newSTRef 0.0 modifySTRef ref $ \n -> n + 1.0 readSTRef ref)) test3 = pureST (do ref <- newSTRef 0.0 modifySTRef ref $ \n -> n + 1.0 readSTRef ref) main = do test1 Control.Monad.Eff.Console.print test2 Control.Monad.Eff.Console.print test3
整體感受就是 Haskell 對反作用進行封裝以後, 整個概念多了太多
特別對可變狀態這種東西, 也許得說是共享的可變狀態, 這種結構, 太不明確
可是應用開發當中遇到可變狀態是稀鬆日常的事情(高端的數據庫後端就...)
Haskell 宣稱本身適合實際開發, 咱們也確實須要不少 Haskell 的特性
可是這麼多限制累積在一塊兒成了那麼高的門檻, 我仍是先折騰 Clojure 吧
Anyway, 經過 uglify 並且 tricky 的手法, 可變狀態也能模擬至少我如今知道了 Haskell 並非徹底把這個路給堵死了