网上扒了很多连接, 看了之后对问题有点改观, 可是消化不掉
因此整理一下放在这里, 但愿有点提高, 并且能够讨论下这个问题
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 并非彻底把这个路给堵死了