作者godfat (godfat 真常)
看板java
标题Re: purely functional (原 [问题] SCJP6.0)
时间Wed Aug 5 14:52:08 2009
果然出去吃个饭回来就断线了 orz
p 币看来会少很多 :s
※ 引述《sbrhsieh (sbr)》之铭言:
: ※ 引述《godfat (godfat 真常)》之铭言:
: : 其实我觉得简单地说就是,不能有任何 side-effect,
: : 因此不能有任何 state. 大概就这样,其他都是衍生出来的性质。
: : 这边不会跟 no side-effect 直接连上关系的原因,
: : 我想是有些情况下 side-effect 是可以被允许的。
: 这边的 side-effect 该怎麽定义?涵盖的范围?
我想 side-effect 的涵盖范围应该不太可能能被定义..?
如果是 side-effect 本身要怎麽定义的话,大概就是不能有 state,
然後 function/expression 本身具有感染性,只要用上任何有含
side-effect 的东西,本身就会变得具有 side-effect.
: 我试想过如果一个 FP 语言不允许 function/procedure 有任何的 side-effect,
: 那麽这个语言写出来的程式会蛮受限的,几乎只能写纯处理数据的工作。
: 有太多的工作本身就是一种 side-effect,是无法单纯 consume 某些 input,并
: 以特定 output value 来呈现(resource manage、IO)。比如:
: 「删除给定路径的某个档案」,这件工作本身就是变更档案系统的状态,这不可能
: 由一个毫无 side-effect 的 procedure(expression)来实现。
我觉得这个就可以去找特定的资料来看,例如 Haskell.
Haskell 本身是 purely functional, 但同样允许 side-effect,
也可以做 IO, 因此并不是只能处理纯数据的工作...
这靠 monad 来达成这件事。这是借自 category theory 的词:
http://en.wikipedia.org/wiki/Monad_(functional_programming)
很不幸的是 XD 虽然我一直想把他读完,不过到现在还是一直没找到
机会把他读完,所以细节都不是很清楚... 虽然听说这是 haskell 里,
会第一个碰到的障碍,甚至也有文章副标题是: "Don't Panic"
http://pages.cpsc.ucalgary.ca/~robin/class/521/monadGuide.pdf
噫,我应该想办法找时间来把这些看完的。
最早我是看 Yet Another Haskell Toturial:
http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf
这篇我之前看的时候还没写完,很多地方是空白的,现在不知道写完了没?
总而言之,我可以大概提一下目前的理解。很可能有错,就大概看看.....
跟下面的 stream 一起说好了。
: Stream 这种观念/东西感觉上也跟状态很有关系,有玩过比较纯的 FP 语言(诸如
: Clojure)的人,可否说明一下该 FP 语言是否有实做 stream 概念的东西?
: 若有,又是如何去实现管理/操作(manipulation) stream 的 procedure,能够
: 让这些 procedure 没有 side effect 又有好的效率?
我稍微查了一下,在 haskell 中,拿来对付类似的东西,好像大多是用
Data.ByteString.Lazy:
http://www.cse.unsw.edu.au/~dons/fps.html
效率好不好嘛,上面是说效率很好,有人说效率有 C 的 1/2
我觉得像是这种本身就具有大量 state 的问题,
purely functional 本身是不太可能比得赢 C 之类直接的操作啦...
这跟架构有关,在别人的地盘上,先天上就输了不是吗 XD
我个人觉得类似的效能评比,应该用在其他架构上,例如 distributed
最近很红的 Erlang, 实作 CouchDB, 诸如此类从「架构上」寻求的解答。
或许没什麽关系,不过有个类似的语言,也是写在 JVM 上:
http://en.wikipedia.org/wiki/E_(programming_language)
不过这个没怎麽维护了的样子?或许直接看 Erlang 即可...
回到 monad, 大抵上的概念就是我们把「状态」视为一种值,
而每一次状态改变,则会产生新的值,再把这个值继续传递下去。
就像 Schelfaniel 在推文里提到的:
: → Schelfaniel:函数式语言的变数,都是不变的吧,但物件导向有可能变 08/04 20
: → Schelfaniel:也就是说,如果process1要变,变化会放在它的传回值 08/04 20
: → Schelfaniel:像是这样 (process2 (process1 java-object)) 取其变 08/04 20
我们假设现在的系统状态是 X, 则两次对萤幕输出 Hello, World! 则可看成:
puts("Hello, World!", puts("Hello, World!", X))
而不是:
puts("Hello, World!")
puts("Hello, World!")
也就是说,puts 这个 function, 他会回传一个新的系统状态,假设叫 Y,
就可以看成是:
Y = puts("Hello, World!", X)
Z = puts("Heelo, World!", Y)
因为第二个 puts depend on 第一个 puts 的 return value,
也就是新的系统状态,因此两者的顺序是不可以被颠倒的,
这边不是两个各自独立的 statement, 而是一个不可分割的 expression.
haskell 的 monad 就是把这件事包装起来。很多 IO function 的回传
都是 IO (), 表示他是一个 IO monad.
Prelude> :t putStr
putStr :: String -> IO ()
putStr 本身是一个 String -> IO () 的 function.
来看刚刚 ByteString 的范例:
module Test where
import Data.ByteString.Lazy.Char8 as L
test = do
s <- L.readFile "/dev/zero"
-- 这边就是把 /dev/zero 这个档案的内容,读到一个 IO ByteString 里面:
-- L.readFile :: FilePath -> IO ByteString
-- 前面 IO () 是表示我们不关心这个 IO monad 究竟存有什麽状态,
-- 比方说,我们并不关心萤幕(or remote)被输出了什麽资料,
-- 但这边我们关心我们读入了什麽资料,因此是 IO ByteString
return (L.unpack (L.take 10 s))
-- 这边从刚刚的 s 里,读取前面前 10 个 Char8
-- 而 L.unpack 则是把这个 ByteString, 转成 haskell 原本的 String,
-- 也就是 [Char], 字元串列。最後再把整个结果回传回去。
-- return 是用在 monad 里面,他会把现在的「状态」包起来。
-- test :: IO [Char]
-- 我们最後是回传 String (等同於 [Char]), 因此整个 test 的结果,
-- 就是一个 IO String
main = do
s <- test
Prelude.putStr s
-- 这边我们把 test 的结果,也就是刚刚的 IO String,
-- 存到 s 里面,然後再把这个 s 印出来。
也就是说,monad 本身是有感染力的,只要你用到 monad,
整个 function 也会变成 monad. 要让你的 function 不成为
monad, 只能完全避开任何的 monad, 也就是任何 IO.
接着再由其他 monad function 去使用你其他的 pure function.
这边也可以参考 Eiffle, 在昨天 referential transparency 的连结里:
http://en.wikipedia.org/wiki/Referential_transparency_(computer_science)
提到 command-query separation:
http://en.wikipedia.org/wiki/Command-query_separation
command 有 side-effect, 而 query 没有,因此是 referential transparency.
这边其实有点类似这样的味道,像在上面的 main 里有:
s <- test
Prelude.putStr s
但是不能简写成:
Prelude.putStr test
因为 test 是回传 IO String, 而 putStr 是要吃 String 的。
透过 s <- test 把 IO String 里的 String 抓出来,放在 s 里,
才能把这个 s 丢给 putStr 把结果印出来。
在这边由於使用了 do notation, 因此写起来的感觉跟一般
imperative 的程式差不多:逐次执行,可以有 side-effect.
但实际上这其实是 syntactic 上的 sugar:
http://en.wikipedia.org/wiki/Monad_(functional_programming)#do-notation
看起来是一行一行的,也没有牵涉到 state 间的转换,是底层帮你做好了。
如果要全部自己来的话,写起来是会很可怕,很复杂的结构,例如上面的范例:
a = do x <- [3..4]
[1..2]
return (x, 42)
会被转成:
a = [3..4] >>= (\x -> [1..2] >>= (\_ -> return (x, 42)))
没有人会想这样写程式的... XD
简单地说,利用 monad, 我们在 pure function 的世界里,
做出一种模拟 side-effect 的结构,然後让你把 pure function
跟其他 side-effect 做一层切割,有点像是防火墙这样的味道...
因此如果写 haskell 程式只用 monad 和 do notation 的话,
感觉就没什麽用 pure function (thus haskell) 的意义在了...
然而如果谈到效率的话,当然不可能会有最底层那样来得好。
当然是越接近机器架构会越快,这是当然的。不过这也不表示说
用这种 monad 的方式会很慢很慢,因为很显然,大部份的时候
那些 state 根本不需要真的被 evaluate 出来,既然不需要被观察,
这些 state 也不会影响到其他 state, 那在 compile 时,
基本上就可以完全舍弃掉。这就是最佳化要解决的问题了...
理想上当然是能把抽象化程式,依照目前机器架构,做最好的最佳化。
把所有多余也不关心的操作都拿掉。GHC 本身就有很多很多的最佳化.. XD
==
我打太久了,不能再花时间在这上面,就暂时不校稿了...
--
Hear me exalted spirits. Hear me, be you gods or devils, ye who hold
dominion here:
I am a wizard without a home. I am a wonderer seeking refuge.
Sacrifice
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 220.128.121.85
※ 编辑: godfat 来自: 220.135.28.18 (08/05 20:56)