Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

use relative pathes globaly #11

Merged
merged 1 commit into from

2 participants

@jiyinyiyong

全部使用相对路径... 这样方便直接生成 gh-pages 分支

记得上一次试过在 Nginx 和 rake preview 都没遇到问题, 刚才忘了 preview 于是回去检查, 也没问题
除了首页有个在两种模式 GET 到不到结果外, 其他都没有 GET 请求的问题

GET http://dev:5000/img/bs-docs-masthead-pattern.png 404 (Not Found) 
@MnO2
Owner

稍早我有補上一些之前漏掉的圖片,很不幸地我那些也是寫絕對路徑。
所以需要麻煩您先抓一下稍早的commit並把哪些修成相對路徑。

另外補述當初寫絕對路徑的原因,
當初寫絕對路徑是覺得產生的目錄結構有可能會有變動,
結果也的確中間有多加一層zh-tw與zh-cn,
不過現在目錄結構應該是穩定了,所以要改成相對路徑我是覺得無妨。

@jiyinyiyong

@MnO2 呃, 我关注项目比较晚.. 你说的"稍早的 commit" 请问是哪?
我能直接看到的目录尽量都去改了.. (js css img 这 3 个文件的链接), 其他不太明白了..

@MnO2
Owner

就是我約兩小時前的這個commit (fd8f73d)

@jiyinyiyong

@MnO2 不好意思看漏消息了... 虽然还是不明白
因为我的 commit 时间是在你的 fd8f73d 之后的, 以为是不用改,
https://github.com/jiyinyiyong/learnyouahaskell/commits/relative-path
这是我用多一个 commit 生成的 gh-pages 分支和页面:
https://github.com/jiyinyiyong/learnyouahaskell/commits/gh-pages
http://jiyinyiyong.github.com/learnyouahaskell/public/
不知道我是不是有理解错的地方, 麻烦讲一下细节怎么样?

@MnO2
Owner

譬如說我在 fd8f73d 這個commit中第十四章增加了[$/img/bread.png], [^/img/almostzipper.png] 等等新的圖片,但你只有改到這個commit之前就存在的圖片。我希望你能順便把我新加的

@MnO2
Owner

譬如說我在 fd8f73d 這個commit中第十四章增加了[$/img/bread.png], [^/img/almostzipper.png] 等等新的圖片,但你只有改到這個commit之前就存在的圖片。我希望你能順便把我新加的圖片也順便改掉。

@jiyinyiyong

@MnO2 哦, 原来你的是在 master 分支上的, 我是一直在 source 分支上改的..
我没考虑过从 master 到这边的合并.. 两个分支区别是什么样的,,
master 是现在网页版跑的那个么? 那之前提交的 source 上的 commit 怎么处理的?

@MnO2
Owner

不, 我是改在 source 上。
master 是放產生出來的HTML, 基本上都是改 source 之後定期產生出 HTML 並 commit 到 master
這樣顯示出來的網頁結果才是對的。

@jiyinyiyong

发现仓库里的分支和本地分支不一致, 估计是你的分支我没 rebase 成功导致的..
只好删除分支再重新创建了, 再麻烦看下 :)
https://github.com/jiyinyiyong/learnyouahaskell/commits/relative-path
https://github.com/learnyouahaskell-zh-tw/learnyouahaskell-zh-tw.github.com/commits/source

@jiyinyiyong jiyinyiyong reopened this
@MnO2
Owner

如果可以的話,麻煩把這兩個commit合成一個。這樣比較一致。

@jiyinyiyong

应该可以了, 用 git reset 然后重新 commit

@MnO2 MnO2 merged commit 6b5ef68 into from
@MnO2
Owner

感謝你的配合 :)

@jiyinyiyong

哈, 终于搞定了

@jiyinyiyong jiyinyiyong deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 10, 2013
  1. @jiyinyiyong

    use relative path

    jiyinyiyong authored
This page is out of date. Refresh to see the latest.
View
14 _chapters.erb
@@ -5,15 +5,15 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
- @import url(/css/style.css);
- @import url(/css/chapters.css);
+ @import url(../css/style.css);
+ @import url(../css/chapters.css);
</style>
- <script src="/js/jquery.js"></script>
- <script src="/js/jquery.chili-2.2.js"></script>
- <script src="/js/script.js"></script>
+ <script src="../js/jquery.js"></script>
+ <script src="../js/jquery.chili-2.2.js"></script>
+ <script src="../js/script.js"></script>
<script>
- ChiliBook.recipeFolder = "/js/chili/";
+ ChiliBook.recipeFolder = "../js/chili/";
ChiliBook.automaticSelector = "pre";
</script>
@@ -22,7 +22,7 @@
<body>
<div id="header">
- <img id="beta" alt="beta" src="/img/beta.png" />
+ <img id="beta" alt="beta" src="../img/beta.png" />
</div>
<div id="main">
View
10 _faq.erb
@@ -5,13 +5,13 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
- @import url(/css/style.css);
+ @import url(css/style.css);
h2 { font-size: 17px; }
</style>
- <script src="/js/jquery.js"></script>
- <script src="/js/jquery.chili-2.2.js"></script>
- <script src="/js/script.js"></script>
+ <script src="js/jquery.js"></script>
+ <script src="js/jquery.chili-2.2.js"></script>
+ <script src="js/script.js"></script>
<script>
ChiliBook.recipeFolder = "/js/chili/";
ChiliBook.automaticSelector = "pre";
@@ -21,7 +21,7 @@
<body>
<div id="header">
- <img id="beta" alt="beta" src="/img/beta.png" />
+ <img id="beta" alt="beta" src="img/beta.png" />
</div>
<div id="main">
<%= c.to_html %>
View
26 _page.erb
@@ -4,16 +4,16 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
- @import url(/css/style.css);
- @import url(/css/feedback.css);
+ @import url(../css/style.css);
+ @import url(../css/feedback.css);
</style>
- <script src="/js/jquery.js"></script>
- <script src="/js/jquery.chili-2.2.js"></script>
- <script src="/js/script.js"></script>
- <script src="/js/html2canvas.js"></script>
- <script src="/js/jsfeedback.js"></script>
+ <script src="../js/jquery.js"></script>
+ <script src="../js/jquery.chili-2.2.js"></script>
+ <script src="../js/script.js"></script>
+ <script src="../js/html2canvas.js"></script>
+ <script src="../js/jsfeedback.js"></script>
<script>
- ChiliBook.recipeFolder = "/js/chili/";
+ ChiliBook.recipeFolder = "../js/chili/";
ChiliBook.automaticSelector = "pre";
</script>
<script>
@@ -38,20 +38,20 @@
</head>
<body>
<div id="header">
- <img id="beta" alt="beta" src="/img/beta.png" />
+ <img id="beta" alt="beta" src="../img/beta.png" />
</div>
<div id="main">
<ul class="nav">
- <li class="left"> <%if p%> <img src="/img/prv.png"></img><a href="<%=p.anchor%>.html"><%=p.title%></a><%end%></li>
+ <li class="left"> <%if p%> <img src="../img/prv.png"></img><a href="<%=p.anchor%>.html"><%=p.title%></a><%end%></li>
<li class="center"><a href="chapters.html">Index</a></li>
- <li class="right"><%if n%> <a href="<%=n.anchor%>.html"><%=n.title%></a><img src="/img/nxt.png"></img><%end%></li>
+ <li class="right"><%if n%> <a href="<%=n.anchor%>.html"><%=n.title%></a><img src="../img/nxt.png"></img><%end%></li>
</ul>
<%= c.to_html %>
<ul class="nav">
- <li class="left"> <%if p%> <img src="/img/prv.png"></img><a href="<%=p.anchor%>.html"><%=p.title%></a><%end%></li>
+ <li class="left"> <%if p%> <img src="../img/prv.png"></img><a href="<%=p.anchor%>.html"><%=p.title%></a><%end%></li>
<li class="center"><a href="chapters.html">Index</a></li>
- <li class="right"><%if n%> <a href="<%=n.anchor%>.html"><%=n.title%></a><img src="/img/nxt.png"></img><%end%></li>
+ <li class="right"><%if n%> <a href="<%=n.anchor%>.html"><%=n.title%></a><img src="../img/nxt.png"></img><%end%></li>
</ul>
</div>
<div id="footer">
View
8 source/_posts/01-introduction.txt
@@ -6,7 +6,7 @@
撰寫這份教學,一方面是讓我自己對 Haskell 更熟練,另一方面是希望能夠分享我的學習經驗,幫助初學者更快進入狀況。網路上已經有無數 Haskell 的教學文件,在我學習的過程中,我並不限於只參考一份來源。我常常閱讀不同的教學文章,他們每個都從不同的角度出發。參考這些資源讓我能將知識化整為零。這份教學是希望提供更多的機會能讓你找到你想要得到的解答。
-[^/img/bird.png]
+[^../img/bird.png]
這份教學主要針對已經有使用命令式程式語言 (imperative programming languages) 寫程式經驗 (C, C++, Java, Python …) 、卻未曾接觸過函數式程式語言 (functional programming languages) (Haskell, ML, OCaml …) 的讀者。就算沒有寫程式經驗也沒關係,會想學 Haskell 的人我相信都是很聰明的。
@@ -17,15 +17,15 @@
==什麼是 Haskell?
-[$/img/fx.png]
+[$../img/fx.png]
Haskell 是一門*純粹函數式程式語言 (purely functional programming language)*。在命令式語言中執行操作需要給電腦安排一組命令,隨着命令的執行,狀態就會隨之發生改變。例如你指派變數 ``a`` 的值為 5,而隨後做了其它一些事情之後 a 就可能變成的其它值。有控制流程 (control flow),你就可以重複執行操作。然而在純粹函數式程式語言中,你不是像命令式語言那樣命令電腦“要做什麼”,而是通過用函數來描述出問題“是什麼”,如“_階乘是指從1到某個數的乘積_”,"一個串列中數字的和"是指把第一個數字跟剩餘數字的和相加。你用宣告函數是什麼的形式來寫程式。另外,變數 (variable) 一旦被指定,就不可以更改了,你已經說了 ``a`` 就是 5,就不能再另說a是別的什麼數。(譯註:其實用 variable 來表達造成字義的 overloading,會讓人聯想到 imperative languages 中 variable 是代表狀態,但在 functional languages 中 variable 是相近於數學中使用的 variable。``x=5`` 代表 ``x`` 就是 5,不是說 ``x`` 在 5 這個狀態。) 所以說,在純粹函數式程式語言中的函數能做的唯一事情就是利用引數計算結果,不會產生所謂的"副作用 (side effect)"(譯註:也就是改變非函數內部的狀態,像是 imperative languages 裡面動到 global variable 就是 side effect)。一開始會覺得這限制很大,不過這也是他的優點所在:若以同樣的參數呼叫同一個函數兩次,得到的結果一定是相同。這被稱作“_引用透明 (Referential Transparency)_”(譯註:這就跟數學上函數的使用一樣)。如此一來編譯器就可以理解程式的行為,你也很容易就能驗證一個函數的正確性,繼而可以將一些簡單的函數組合成更複雜的函數。
-[^/img/lazy.png]
+[^../img/lazy.png]
Haskell 是*惰性 (lazy)*的。也就是說若非特殊指明,函數在真正需要結果以前不會被求值。再加上引用透明,你就可以把程式僅看作是數據的一系列變形。如此一來就有了很多有趣的特性,如無限長度的資料結構。假設你有一個 List: ``xs = [1,2,3,4,5,6,7,8]``,還有一個函數 ``doubleMe``,它可以將一個 List 中的所有元素都乘以二,返回一個新的 List。若是在命令式語言中,把一個 List 乘以8,執行 ``doubleMe(doubleMe(doubleMe(xs)))``,得遍歷三遍 ``xs`` 才會得到結果。而在惰性語言中,調用 ``doubleMe`` 時並不會立即求值,它會說“嗯嗯,待會兒再做!”。不過一旦要看結果,第一個 ``doubleMe`` 就會對第二個說“給我結果,快!”第二個 ``doubleMe`` 就會把同樣的話傳給第三個 ``doubleMe``,第三個 ``doubleMe`` 只能將 1 乘以 2 得 2 後交給第二個,第二個再乘以 2 得 4 交給第一個,最終得到第一個元素 8。也就是說,這一切只需要遍歷一次 list 即可,而且僅在你真正需要結果時才會執行。惰性語言中的計算只是一組初始數據和變換公式。
-[$/img/boat.png]
+[$../img/boat.png]
Haskell 是*靜態類型 (statically typed)* 的。當你編譯程式時,編譯器需要明確哪個是數字,哪個是字串。這就意味着很大一部分錯誤都可以在編譯時被發現,若試圖將一個數字和字串相加,編譯器就會報錯。Haskell 擁有一套強大的類型系統,支持自動類型推導 (type inference)。這一來你就不需要在每段程式碼上都標明它的類型,像計算 ``a=5+4``,你就不需另告訴編譯器“ a 是一個數值”,它可以自己推導出來。類型推導可以讓你的程式更加簡練。假設有個函數是將兩個數值相加,你不需要聲明其類型,這個函數可以對一切可以相加的值進行計算。
View
18 source/_posts/02-ready-go.txt
@@ -2,7 +2,7 @@
== 準備好了嗎?
-[$/img/startingout.png]
+[$../img/startingout.png]
準備來開始我們的旅程!如果你就是那種從不看說明書的人,我推薦你還是回頭看一下簡介的最後一節。那裡面講了這個教學中你需要用到的工具及基本用法。我們首先要做的就是進入 ghc 的互動模式,接着就可以寫幾個函數體驗一下 Haskell 了。打開終端機,輸入 ghci,你會看到下列歡迎訊息
@@ -79,7 +79,7 @@ In the definition of `it': it = 5 + "llama"  
這邊 ghci 提示說 ``"llama"`` 並不是數值型別,所以它不知道該怎樣才能給它加上 5。即便是 “four” 甚至是 “4” 也不可以,Haskell 不拿它當數值。執行 ``True==5``, ghci 就會提示型別不匹配。``+`` 運算子要求兩端都是數值,而 ``==`` 運算子僅對兩個可比較的值可用。這就要求他們的型別都必須一致,蘋果和橘子就無法做比較。我們會在後面深入地理解型別的概念。Note: ``5+4.0`` 是可以執行的,5 既可以做被看做整數也可以被看做浮點數,但 4.0 則不能被看做整數。
-[$/img/ringring.png]
+[$../img/ringring.png]
也許你並未察覺,不過從始至終我們一直都在使用函數。``*`` 就是一個將兩個數相乘的函數,就像三明治一樣,用兩個參數將它夾在中央,這被稱作中綴函數。而其他大多數不能與數夾在一起的函數則被稱作前綴函數。絶大部分函數都是前綴函數,在接下來我們就不多做區別。大多數命令式程式語言中的函數呼叫形式通常就是函數名,括號,由逗號分隔的參數列。而在 Haskell 中,函數呼叫的形式是函數名,空格,空格分隔的參數列。簡單據個例子,我們呼叫 Haskell 中最無趣的函數:
@@ -154,7 +154,7 @@ ghci> doubleUs 28 88 + doubleMe 123  
doubleUs x y = doubleMe x + doubleMe y  
}}
-[^/img/baby.png]
+[^../img/baby.png]
這種情形在 Haskell 下邊十分常見:編寫一些簡單的函數,然後將其組合,形成一個較為複雜的函數,這樣可以減少重複工作。設想若是哪天有個數學家驗證說 2 應該是 3,我們只需要將 ``doubleMe`` 改為 ``x+x+x`` 即可,由於 ``doubleUs`` 呼叫到 ``doubleMe``,於是整個程式便進入了 2 即是 3 的古怪世界。
@@ -182,7 +182,7 @@ conanO'Brien = "It's a-me, Conan O'Brien!"  
==List 入門
-[$/img/list.png]
+[$../img/list.png]
在 Haskell 中,List 就像現實世界中的購物單一樣重要。它是最常用的資料結構,並且十分強大,靈活地使用它可以解決很多問題。本節我們將對 List,字串和 list comprehension 有個初步瞭解。 在 Haskell 中,List 是一種單型別的資料結構,可以用來存儲多個型別相同的元素。我們可以在裡面裝一組數字或者一組字元,但不能把字元和數字裝在一起。
@@ -290,7 +290,7 @@ ghci> init [5,4,3,2,1]  
如果我們把 List 想象為一頭怪獸,那這就是它的樣子:
-[/img/listmonster.png]
+[../img/listmonster.png]
試一下,若是取一個空 List 的 ``head`` 又會怎樣?
@@ -381,7 +381,7 @@ False 
==使用 Range
-[$/img/cowboy.png]
+[$../img/cowboy.png]
今天如果想得到一個包含 1 到 20 之間所有數的 List,你會怎麼做? 我們可以將它們一個一個用鍵盤打出來,但很明顯地這不是一個完美的方案,特別是你追求一個好的程式語言的時候。我們想用的是區間 (Range)。Range 是構造 List 方法之一,而其中的值必須是可枚舉的,像 1、2、3、4...字元同樣也可以枚舉,字母表就是 ``A..Z`` 所有字元的枚舉。而名字就不可以枚舉了,``"john"`` 後面是誰?我不知道。
@@ -436,9 +436,9 @@ ghci> take 10 (repeat 5)  
==List Comprehension
-[$/img/kermit.png]
+[$../img/kermit.png]
-學過數學的你對集合的 comprehension(Set Comprehension) 概念一定不會陌生。通過它,可以從既有的集合中按照規則產生一個新集合。前十個偶數的 set comprehension 可以表示為[/img/setnotation.png],豎綫左端的部分是輸出函數,``x`` 是變數,``N`` 是輸入集合。在 Haskell 下,我們可以通過類似 ``take 10 [2,4..]`` 的程式碼來實現。但若是把簡單的乘 2 改成更複雜的函數操作該怎麼辦呢?用 list comprehension,它與 set comprehension 十分的相似,用它取前十個偶數輕而易舉。這個 list comprehension 可以表示為:
+學過數學的你對集合的 comprehension(Set Comprehension) 概念一定不會陌生。通過它,可以從既有的集合中按照規則產生一個新集合。前十個偶數的 set comprehension 可以表示為[../img/setnotation.png],豎綫左端的部分是輸出函數,``x`` 是變數,``N`` 是輸入集合。在 Haskell 下,我們可以通過類似 ``take 10 [2,4..]`` 的程式碼來實現。但若是把簡單的乘 2 改成更複雜的函數操作該怎麼辦呢?用 list comprehension,它與 set comprehension 十分的相似,用它取前十個偶數輕而易舉。這個 list comprehension 可以表示為:
{{
ghci> [x*2 | x <- [1..10]]  
@@ -538,7 +538,7 @@ ghci> [ [ x | x <- xs, even x ] | xs <- xxs]  
==Tuple
-[$/img/tuple.png]
+[$../img/tuple.png]
從某種意義上講,Tuple (元組)很像 List --都是將多個值存入一個個體的容器。但它們卻有着本質的不同,一組數字的 List 就是一組數字,它們的型別相 同,且不關心其中包含元素的數量。而 Tuple 則要求你對需要組合的數據的數目非常的明確,它的型別取決於其中項的數目與其各自的型別。Tuple 中的項 由括號括起,並由逗號隔開。
View
8 source/_posts/03-type-and-typeclass.txt
@@ -2,7 +2,7 @@
==Type
-[^/img/cow.png]
+[^../img/cow.png]
之前我們有說過 Haskell 是 Static Type,這表示在編譯時期每個表達式的型別都已經確定下來,這提高了程式碼的安全性。若程式碼中有讓布林值與數字相除的動作,就不會通過編譯。這樣的好處就是與其讓程序在運行時崩潰,不如在編譯時就找出可能的錯誤。Haskell 中所有東西都有型別,因此在編譯的時候編譯器可以做到很多事情。
@@ -25,7 +25,7 @@ ghci> :t 4 == 5
4 == 5 :: Bool
}}
-[$/img/bomb.png]
+[$../img/bomb.png]
可以看出,``:t`` 命令處理一個表達式的輸出結果為表達式後跟 ``::`` 及其型別,``::`` 讀作"它的型別為"。凡是明確的型別,其首字母必為大寫。``'a'``, 如它的樣子,是 ``Char`` 型別,易知是個字元 (character)。``True`` 是 ``Bool`` 型別,也靠譜。不過這又是啥,檢測 ``"hello"`` 得一個 ``[Char]`` 這方括號表示一個 List,所以我們可以將其讀作"一組字元的 List"。而與 List 不同,每個 Tuple 都是獨立的型別,於是 ``(True,"a")`` 的型別是 ``(Bool,Char)``,而 ``('a','b','c')`` 的型別為 ``(Char,Char,Char)``。``4==5`` 一定回傳 ``False``,所以它的型別為 Bool。
@@ -104,7 +104,7 @@ ghci> :t head
head :: [a] -> a
}}
-[^/img/box.png]
+[^../img/box.png]
嗯! ``a`` 是啥?型別嗎?想想我們在前面說過,凡是型別其首字母必大寫,所以它不會是個型別。它是個型別變數,意味着 a 可以是任意的型別。這一點與其他語言中的泛型 (generic) 很相似,但在 Haskell 中要更為強大。它可以讓我們輕而易舉地寫出型別無關的函數。使用到型別變數的函數被稱作"多態函數 ",``head`` 函數的型別聲明裡標明了它可以取任意型別的 List 並回傳其中的第一個元素。
@@ -122,7 +122,7 @@ fst :: (a, b) -> a
==Typeclasses入門
-[$/img/classes.png]
+[$../img/classes.png]
型別定義行為的介面,如果一個型別屬於某 Typeclass,那它必實現了該 Typeclass 所描述的行為。很多從 OOP 走過來的人們往往會把 Typeclass 當成物件導向語言中的 ``class`` 而感到疑惑,厄,它們不是一回事。易於理解起見,你可以把它看做是 Java 的 interface。
View
8 source/_posts/04-syntax-in-function.txt
@@ -2,7 +2,7 @@
==模式匹配 (Pattern matching)
-[$/img/pattern.png]
+[$../img/pattern.png]
本章講的就是 Haskell 那套獨特的語法結構,先從模式匹配開始。模式匹配通過檢查數據的特定結構來檢查其是否匹配,並按模式從中取得數據。
@@ -179,7 +179,7 @@ ghci> capital "Dracula"  
模式用來檢查一個值是否合適並從中取值,而 guard 則用來檢查一個值的某項屬性是否為真。咋一聽有點像是 ``if`` 語句,實際上也正是如此。不過處理多個條件分支時 guard 的可讀性要高些,並且與模式匹配契合的很好。
-[$/img/guards.png]
+[$../img/guards.png]
在講解它的語法前,我們先看一個用到 guard 的函數。它會依據你的 BMI 值 (body mass index,身體質量指數)來不同程度地侮辱你。BMI 值即為體重除以身高的平方。如果小於 18.5,就是太瘦;如果在 18.5 到 25 之間,就是正常;25 到 30 之間,超重;如果超過 30,肥胖。這就是那個函數(我們目前暫不為您計算 `bmi`,它只是直接取一個 emi 值)。
@@ -348,7 +348,7 @@ cylinder r h = 
    in  sideArea + 2 * topArea  
}}
-[$/img/letitbe.png]
+[$../img/letitbe.png]
``let`` 的格式為 ``let [bindings] in [expressions]``。在 ``let`` 中綁定的名字僅對 ``in`` 部分可見。``let`` 裡面定義的名字也得對齊到一列。不難看出,這用 ``where`` 綁定也可以做到。那麼它倆有什麼區別呢?看起來無非就是,``let`` 把綁定放在語句前面而 ``where`` 放在後面嘛。
@@ -424,7 +424,7 @@ ghci> boot
== Case expressions
-[$/img/case.png]
+[$../img/case.png]
有命令式程式語言 (C, C++, Java, etc.) 的經驗的同學一定會有所瞭解,很多命令式語言都提供了 ``case`` 語句。就是取一個變數,按照對變數的判斷選擇對應的程式碼塊。其中可能會存在一個萬能匹配以處理未預料的情況。
View
12 source/_posts/05-recursion.txt
@@ -2,7 +2,7 @@
==你好,遞迴!
-[$/img/recursion.png]
+[$../img/recursion.png]
前面的章節中我們簡要談了一下遞迴。而在本章,我們會深入地瞭解到它為何在 Haskell 中是如此重要,能夠以遞迴思想寫出簡潔優雅的程式碼。
@@ -44,7 +44,7 @@ maximum' (x:xs) = max x (maximum' xs)
太漂亮了!一個 List 的最大值就是它的首個元素與它尾部中最大值相比較所得的結果,簡明扼要。
-[/img/maxs.png]
+[../img/maxs.png]
==來看幾個遞迴函數
@@ -71,7 +71,7 @@ take' _ [] = []
take' n (x:xs) = x : take' (n-1) xs
}}
-[$/img/painter.png]
+[$../img/painter.png]
首個模式辨認若為 0 或負數, 回傳空 List. 同時注意這裡用了一個 guard 卻沒有指定 ``otherwise`` 部分, 這就表示 ``n`` 若大於 0, 會轉入下一模式. 第二個模式指明了若試圖從一個空 List 中取值, 則回傳空 List. 第三個模式將 List 分割為頭部和尾部, 然後表明從一個 List 中取多個元素等同於令 ``x`` 作頭部後接從尾部取 ``n-1`` 個元素所得的 List. 假如我們要從 ``[4,3,2,1]`` 中取 3 個元素, 試着從紙上寫出它的推導過程
@@ -121,7 +121,7 @@ elem' a (x:xs)
== "快速"排序
-[$/img/quickman.png]
+[$../img/quickman.png]
假定我們有一個可排序的 List, 其中元素的型別為 Ord Typeclass 的成員. 現在我們要給它排序! 有個排序算法非常的酷, 就是快速排序 (quick sort), 睿智的排序方法. 儘管它在命令式語言中也不過 10 行, 但在 Haskell 下邊要更短, 更漂亮, 儼然已經成了 Haskell 的招牌了. 嗯, 我們在這裡也實現一下. 或許會顯得很俗氣, 因為每個人都用它來展示 Haskell 究竟有多優雅!
@@ -149,13 +149,13 @@ booyah! 如我所說的一樣! 若給 ``[5,1,9,4,6,7,3]`` 排序,這個算法
橙色的部分表示已定位並不再移動的元素。從左到右看,便是一個排過序的 List。在這裡我們將所有元素與 ``head`` 作比較,而實際上就快速排序算法而言,選擇任意元素都是可以的。被選擇的元素就被稱作錨 (``pivot``),以方便模式匹配。小於錨的元素都在淺綠的部分,大於錨都在深綠部分,這個黃黃的坡就表示了快速排序的執行方式:
-[/img/quicksort.png]
+[../img/quicksort.png]
==用遞迴來思考
我們已經寫了不少遞迴了,也許你已經發覺了其中的固定模式:先定義一個邊界條件,再定義個函數,讓它從一堆元素中取一個並做點事情後,把餘下的元素重新交給這個函數。 這一模式對 List、Tree 等資料結構都是適用的。例如,``sum`` 函數就是一個 List 頭部與其尾部的 ``sum`` 的和。一個 List 的積便是該 List 的頭與其尾部的積相乘的積,一個 List 的長度就是 1 與其尾部長度的和. 等等
-[$/img/brain.png]
+[$../img/brain.png]
再者就是邊界條件。一般而言,邊界條件就是為避免程序出錯而設置的保護措施,處理 List 時的邊界條件大部分都是空 List,而處理 Tree 時的邊界條件就是沒有子元素的節點。
View
24 source/_posts/06-high-order-function.txt
@@ -1,6 +1,6 @@
=高階函數 #high-order-function
-[$/img/sun.png]
+[$../img/sun.png]
Haskell 中的函數可以作為參數和回傳值傳來傳去,這樣的函數就被稱作高階函數。高階函數可不只是某簡單特性而已,它貫穿于 Haskell 的方方面面。要拒絶循環與狀態的改變而通過定義問題"是什麼"來解決問題,高階函數必不可少。它們是編碼的得力工具。
@@ -15,7 +15,7 @@ ghci> (max 4) 5
5
}}
-[$/img/curry.png]
+[$../img/curry.png]
把空格放到兩個東西之間,稱作*函數呼叫*。它有點像個運算符,並擁有最高的優先順序。 看看 ``max`` 函數的型別: ``max :: (Ord a) => a -> a -> a``。 也可以寫作: ``max :: (Ord a) => a -> (a -> a)``。 可以讀作 ``max`` 取一個參數 ``a``,並回傳一個函數(就是那個 ``->``),這個函數取一個 ``a`` 型別的參數,回傳一個a。 這便是為何只用箭頭來分隔參數和回傳值型別。
@@ -97,7 +97,7 @@ applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)
}}
-[$/img/bonus.png]
+[$../img/bonus.png]
首先注意這型別聲明。 在此之前我們很少用到括號,因為 ``(->)`` 是自然的右結合,不過在這裡括號是必須的。 它標明了首個參數是個參數與回傳值型別都是a的函數,第二個參數與回傳值的型別也都是a。 我們可以用 Curried functions 的思路來理解這一函數,不過免得自尋煩惱,我們姑且直接把它看作是取兩個參數回傳一個值,其首個參數是個型別為 ``(a->a)`` 的函數,第二個參數是個 ``a``。 該函數的型別可以是 ``(Int->Int)``,也可以是 ``(String->String)``,但第二個參數必須與之一致。
@@ -240,7 +240,7 @@ quicksort (x:xs) =
in smallerSorted ++ [x] ++ biggerSorted
}}
-[^/img/map.png]
+[^../img/map.png]
``map`` 和 ``filter`` 是每個函數式程序員的麵包黃油(呃,``map`` 和 ``filter`` 還是 List Comprehension 並不重要)。 想想前面我們如何解決給定周長尋找合適直角三角形的問題的? 在命令式編程中,我們可以套上三個循環逐個測試當前的組合是否滿足條件,若滿足,就打印到屏幕或其他類似的輸出。 而在函數式編程中,這行就都交給 ``map`` 和 ``filter``。 你弄個取一參數的函數,把它交給 ``map`` 過一遍 List,再 ``filter`` 之找到合適的結果。 感謝 Haskell 的惰性,即便是你多次 ``map`` 一個 ``List` 也只會遍歷一遍該 List,要找出小於 100000 的數中最大的 3829 的倍數,只需過濾結果所在的 List 就行了.
@@ -317,7 +317,7 @@ ghci> (listOfFuns !! 4) 5
==lambda
-[^/img/lamb.png]
+[^../img/lamb.png]
lambda 就是匿名函數。有些時候我們需要傳給高階函數一個函數,而這函數我們只會用這一次,這就弄個特定功能的 lambda。編寫 lambda,就寫個 ``\`` (因為它看起來像是希臘字母的 lambda -- 如果你斜視的厲害),後面是用空格分隔的參數,``->`` 後面就是函數體。通常我們都是用括號將其括起,要不然它就會佔據整個右邊部分。
@@ -328,7 +328,7 @@ numLongChains :: Int
numLongChains = length (filter (\xs -> length xs > 15) (map chain [1..100]))
}}
-[$/img/lambda.png]
+[$../img/lambda.png]
lambda 是個表達式,因此我們可以任意傳遞。表達式 ``(\xs -> length xs > 15)`` 回傳一個函數,它可以告訴我們一個 List 的長度是否大於 15。
@@ -375,7 +375,7 @@ flip' f = \x y -> f y x
== 關鍵字 fold
-[$/img/origami.png]
+[$../img/origami.png]
回到當初我們學習遞歸的情景。我們會發現處理 List 的許多函數都有固定的模式,通常我們會將邊界條件設置為空 List,再引入 ``(x:xs)`` 模式,對單個元素和餘下的 List 做些事情。這一模式是如此常見,因此 Haskell 引入了一組函數來使之簡化,也就是 ``fold``。它們與map有點像,只是它們回傳的是單個值。
@@ -397,7 +397,7 @@ ghci> sum' [3,5,2,1]
11
}}
-[^/img/foldl.png]
+[^../img/foldl.png]
我們深入看下 ``fold`` 的執行過程:``\acc x-> acc + x`` 是個二元函數,``0`` 是初始值,``xs`` 是待摺疊的 List。一開始,累加值為 ``0``,當前項為 ``3``,呼叫二元函數 ``0+3`` 得 ``3``,作新的累加值。接着來,累加值為 ``3``,當前項為 ``5``,得新累加值 ``8``。再往後,累加值為 ``8``,當前項為 ``2``,得新累加值 ``10``。最後累加值為 ``10``,當前項為 ``1``,得 ``11``。恭喜,你完成了一次摺疊 ``(fold)``!
@@ -432,7 +432,7 @@ map' f xs = foldr (\x acc -> f x : acc) [] xs
當然,我們也完全可以用左摺疊來實現它,``map' f xs = foldl (\acc x -> acc ++ [f x]) [] xs`` 就行了。不過問題是,使用 ``(++)`` 往 List 後面追加元素的效率要比使用 ``(:)`` 低得多。所以在生成新 List 的時候人們一般都是使用右摺疊。
-[$/img/washmachine.png]
+[$../img/washmachine.png]
反轉一個 List,既也可以通過右摺疊,也可以通過左摺疊。有時甚至不需要管它們的分別,如 ``sum`` 函數的左右摺疊實現都是十分相似。不過有個大的不同,那就是右摺疊可以處理無限長度的資料結構,而左摺疊不可以。將無限 List 從中斷開執行左摺疊是可以的,不過若是向右,就永遠到不了頭了。
@@ -509,7 +509,7 @@ ghci> sum (map sqrt [1..130])
f $ x = f x
}}
-[^/img/dollar.png]
+[^../img/dollar.png]
什麼鬼東西?這沒啥意義的操作符?它只是個函數呼叫符罷了?好吧,不全是,但差不多。普通的函數呼叫符有最高的優先順序,而 ``$`` 的優先順序則最低。用空格的函數呼叫符是左結合的,如 ``f a b c`` 與 ``((f a) b) c`` 等價,而 ``$`` 則是右結合的。
@@ -528,7 +528,7 @@ ghci> map ($ 3) [(4+),(10*),(^2),sqrt]
== Function composition
-在數學中,函數組合是這樣定義的: [/img/composition.png],表示組合兩個函數成為一個函數。以 ``x`` 呼叫這一函數,就與用 ``x`` 呼叫 ``g`` 再用所得的結果呼叫 ``f`` 等價。
+在數學中,函數組合是這樣定義的: [../img/composition.png],表示組合兩個函數成為一個函數。以 ``x`` 呼叫這一函數,就與用 ``x`` 呼叫 ``g`` 再用所得的結果呼叫 ``f`` 等價。
Haskell 中的函數組合與之很像,即 **.** 函數。其定義為:
@@ -537,7 +537,7 @@ Haskell 中的函數組合與之很像,即 **.** 函數。其定義為:
f . g = \x -> f (g x)
}}
-[$/img/notes.png]
+[$../img/notes.png]
注意下這型別聲明,``f`` 的參數型別必須與 ``g`` 的回傳型別相同。所以得到的組合函數的參數型別與 ``g`` 相同,回傳型別與 ``f`` 相同。表達式 ``negate . (*3)`` 回傳一個求一數字乘以 3 後的負數的函數。
View
12 source/_posts/07-module.txt
@@ -2,7 +2,7 @@
== 裝載模組
-[$/img/modules.png]
+[$../img/modules.png]
Haskell 中的模組是含有一組相關的函數,型別和型別類的組合。而 Haskell 程序的本質便是從主模組中引用其它模組並呼叫其中的函數來執行操作。這樣可以把程式碼分成多塊,只要一個模組足夠的獨立,它裡面的函數便可以被不同的程序反覆重用。這就讓不同的程式碼各司其職,提高了程式碼的健壯性。
@@ -105,7 +105,7 @@ ghci> map sum $ transpose [[0,3,5,9],[10,0,0,9],[8,5,1,-1]]
[18,8,6,17]
}}
-[^/img/legolists.png]
+[^../img/legolists.png]
使用 ``transpose`` 處理這三個 List 之後,三次冪就倒了第一行,二次冪到了第二行,以此類推。在用 ``sum`` 函數將其映射,即可得到正確的結果。
@@ -512,7 +512,7 @@ ghci> sortBy (compare `on` length) xs
``Data.Char``模組中含有一系列用於判定字元範圍的函數,如下:
-[$/img/legochar.png]
+[$../img/legochar.png]
**isControl **判斷一個字元是否是控制字元。
**isSpace** 判斷一個字元是否是空格字元,包括空格,tab,換行符等.
@@ -676,7 +676,7 @@ findKey :: (Eq k) => k -> [(k,v)] -> v
findKey key xs = snd . head . filter (\(k,v) -> key == k) $ xs
}}
-[$/img/legomap.png]
+[$../img/legomap.png]
簡潔漂亮。這個函數取一個鍵和 List 做參數,過濾這一 List 僅保留鍵匹配的項,並返迴首個鍵值對。但若該關聯列表中不存在這個鍵那會怎樣? 哼,那就會在試圖從空 List 中取 ``head`` 時引發一個運行時錯誤。無論如何也不能讓程序就這麼輕易地崩潰吧,所以就應該用 ``Maybe`` 型別。如果沒找到相應的鍵,就返回 ``Nothing``。而找到了就返回 ``Just something``。而這 ``something`` 就是鍵對應的值。
@@ -861,7 +861,7 @@ fromList [(3,104),(5,103),(6,339)]
== Data.Set
-[$/img/legosets.png]
+[$../img/legosets.png]
``Data.Set`` 模組提供了對數學中集合的處理。集合既像 List 也像 ``Map``: 它裡面的每個元素都是唯一的,且內部的數據由一棵樹來組織(這和 ``Data.Map`` 模組的 ``map`` 很像),必須得是可排序的。同樣是插入,刪除,判斷從屬關係之類的操作,使用集合要比 List 快得多。對一個集合而言,最常見的操作莫過于並集,判斷從屬或是將集合轉為 List.
@@ -1029,7 +1029,7 @@ rectangleArea :: Float -> Float -> Float
rectangleArea a b = a * b
}}
-[$/img/making_modules.png]
+[$../img/making_modules.png]
標準的幾何公式。有幾個地方需要注意一下,由於立方體只是長方體的特殊形式,所以在求它面積和體積的時候我們就將它當作是邊長相等的長方體。在這裡還定義了一個 ``helper``函數,``rectangleArea`` 它可以通過長方體的兩條邊計算出長方體的面積。它僅僅是簡單的相乘而已,份量不大。但請注意我們可以在這一模組中呼叫這個函數,而它不會被導出! 因為我們這個模組只與三維圖形打交道.
View
10 source/_posts/08-build-our-own-type-and-typeclass.txt
@@ -18,7 +18,7 @@ data Bool = False | True
data Int = -2147483648 | -2147483647 | ... | -1 | 0 | 1 | 2 | ... | 2147483647
}}
-[$/img/caveman.png]
+[$../img/caveman.png]
頭尾兩個值構造子分別表示了 ``Int`` 型別的最小值和最大值,注意到真正的型別宣告不是長這個樣子的,這樣寫只是為了便於理解。我們用省略號表示中間省略的一大段數字。
@@ -267,7 +267,7 @@ Car {company = "Ford", model = "Mustang", year = 1967}
data Maybe a = Nothing | Just a
}}
-[$/img/yeti.png]
+[$../img/yeti.png]
這裡的a就是個型別參數。也正因為有了它,``Maybe`` 就成為了一個型別構造子。在它的值不是 ``Nothing`` 時,它的型別構造子可以搞出 ``Maybe Int``,``Maybe String`` 等等諸多型別。但只一個 ``Maybe`` 是不行的,因為它不是型別,而是型別構造子。要成為真正的型別,必須得把它需要的型別參數全部填滿。
@@ -294,7 +294,7 @@ Just 10.0
型別參數很實用。有了它,我們就可以按照我們的需要構造出不同的型別。若執行 ``:t Just "Haha"``,型別推導引擎就會認出它是個 ``Maybe [Char]``,由於 ``Just a`` 裡的 ``a`` 是個字元串,那麼 ``Maybe a`` 裡的 ``a`` 一定也是個字元串。
-[$/img/meekrat.png]
+[$../img/meekrat.png]
注意下,``Nothing`` 的型別為 ``Maybe a``。它是多態的,若有函數取 ``Maybe Int`` 型別的參數,就一概可以傳給它一個 ``Nothing``,因為 ``Nothing`` 中不包含任何值。``Maybe a`` 型別可以有 ``Maybe Int`` 的行為,正如 ``5`` 可以是 ``Int`` 也可以是 ``Double``。與之相似,空 List 的型別是 ``[a]``,可以與一切 List 打交道。因此,我們可以 ``[1,2,3]++[]``,也可以 ``["ha","ha,","ha"]++[]``。
@@ -392,7 +392,7 @@ Vector 148 666 222
== Derived instances
-[$/img/gob.png]
+[$../img/gob.png]
在 [/make-types-and-classes-for-ourselves.html#Typeclasses的第二堂課 Typeclass 101] 那一節裡面,我們瞭解了 Typeclass 的基礎內容。裡面提到,型別類就是定義了某些行為的介面。例如,Int 型別是 ``Eq`` 型別類的一個 instance,``Eq`` 類就定義了判定相等性的行為。Int 值可以判斷相等性,所以 Int 就是 ``Eq`` 型別類的成員。它的真正威力體現在作為 ``Eq`` 介面的函數中,即 ``==`` 和 ``/=``。只要一個型別是 ``Eq`` 型別類的成員,我們就可以使用 ``==`` 函數來處理這一型別。這便是為何 ``4==4`` 和 ``"foo"/="bar"`` 這樣的表達式都需要作型別檢查。
@@ -623,7 +623,7 @@ inPhoneBook :: Name -> PhoneNumber -> PhoneBook -> Bool
inPhoneBook name pnumber pbook = (name,pnumber) `elem` pbook
}}
-[^/img/chicken.png]
+[^../img/chicken.png]
如果不用型別別名,我們函數的型別聲明就只能是 ``String -> String -> [(String ,String)] -> Bool`` 了。在這裡使用型別別名是為了讓型別聲明更加易讀,但你也不必拘泥于它。引入型別別名的動機既非單純表示我們函數中的既有型別,也不是為了替換掉那些重複率高的長名字型別(如 ``[(String,String)]``),而是為了讓型別對事物的描述更加明確。
View
28 source/_posts/09-input-and-output.txt
@@ -1,6 +1,6 @@
=輸入與輸出 #input-and-output
-[$/img/dognap.png]
+[$../img/dognap.png]
我們已經說明了 Haskell 是一個純粹函數式語言。雖說在命令式語言中我們習慣給電腦執行一連串指令,在函數式語言中我們是用定義東西的方式進行。在 Haskell 中,一個函數不能改變狀態,像是改變一個變數的內容。(當一個函數會改變狀態,我們說這函數是有副作用的。)在 Haskell 中函數唯一可以做的事是根據我們給定的參數來算出結果。如果我們用同樣的參數呼叫兩次同一個函數,它會回傳相同的結果。儘管這從命令列語言的角度來看是蠻大的限制,我們已經看過它可以達成多麼酷的效果。在一個命令式語言中,程式語言沒辦法給你任何保證在一個簡單如印出幾個數字的函數不會同時燒掉你的房子,綁架你的狗並刮傷你車子的烤漆。例如,當我們要做一個二元樹的時候,我們並不插入一個結點來改變原有的樹。由於我們無法改變狀態,我們的函數實際上回傳了一棵新的二元樹。
@@ -13,7 +13,7 @@
==Hello, world!
-[^/img/helloworld.png]
+[^../img/helloworld.png]
到目前為止我們都是將函數載入 GHCi 中來測試,像是標準函式庫中的一些函式。但現在我們要做些不一樣的,寫一個真實跟世界互動的 Haskell 程式。當然不例外,我們會來寫個"hello world"。
@@ -79,7 +79,7 @@ getLine :: IO String
}}
-[^/img/luggage.png]
+[^../img/luggage.png]
我們可以看到 ``getLine`` 是一個回傳 ``String`` 的 I/O action。因為它會等使用者輸入某些字串,這很合理。那 ``name <- getLine`` 又是如何?你能這樣解讀它: 執行一個 I/O action ``getLine`` 並將它的結果綁定到 ``name`` 這個名字。``getLine`` 的型態是 ``IO String``,所以 ``name`` 的型態會是 ``String``。你能把 I/O action 想成是一個長了腳的盒子,它會跑到真實世界中替你做某些事,像是在牆壁上塗鴉,然後帶回來某些資料。一旦它帶了某些資料給你,打開盒子的唯一辦法就是用 ``<-``。而且如果我們要從 I/O action 拿出某些資料,就一定同時要在另一個 I/O action 中。這就是 Haskell 如何漂亮地分開純粹跟不純粹的程式的方法。``getLine`` 在這樣的意義下是不純粹的,因為執行兩次的時候它沒辦法保證會回傳一樣的值。這也是為什麼它需要在一個 ``IO`` 的型態建構子中,那樣我們才能在 I/O actio 中取出資料。而且任何一段程式一旦依賴著 I/O 資料的話,那段程式也會被視為 I/O code。
@@ -412,7 +412,7 @@ orange
==檔案與字符流
-[$/img/streams.png]
+[$../img/streams.png]
``getChar`` 是一個讀取單一字元的 I/O action。``getLine`` 是一個讀取一行的 I/O action。這是兩個非常直覺的函式,多數程式語言也有類似這兩個函式的 statement 或 function。但現在我們來看看*getContents*。``getContents`` 是一個從標準輸入讀取直到 end-of-file 字元的 I/O action。他的型態是 ``getContents :: IO String``。最酷的是 ``getContents`` 是惰性 I/O (Lazy I/O)。當我們寫了 ``foo <- getContents``,他並不會馬上讀取所有輸入,將他們存在 memory 裡面。他只有當你真的需要輸入資料的時候才會讀取。
@@ -646,7 +646,7 @@ type FilePath = String
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
}}
-[^/img/file.png]
+[^../img/file.png]
就像我們之前定義的型態,分別代表一個星期的七天。這個型態代表了我們想對打開的檔案做什麼。很簡單吧。留意到我們的型態是 ``IOMode`` 而不是 ``IO Mode``。``IO Mode`` 代表的是一個 I/O action 包含了一個型態為 ``Mode`` 的值,但 ``IOMode`` 不過是一個陽春的 enumeration。
@@ -680,7 +680,7 @@ withFile' path mode f = do
return result
}}
-[$/img/edd.png]
+[$../img/edd.png]
我們知道要回傳的是一個 I/O action,所以我們先放一個 do。首先我們打開檔案,得到一個 handle。然後我們 apply ``handle`` 到我們的函數,並得到一個做事的 I/O action。我們綁定那個 I/O action 到 ``result`` 這個名字,關上 handle 並 ``return result``。``return`` 的作用把從 ``f`` 得到的結果包在 I/O action 中,這樣一來 I/O action 中就包含了 ``f handle`` 得到的結果。如果 ``f handle`` 回傳一個從標準輸入讀去數行並寫到檔案然後回傳讀入的行數的 I/O action,在 ``withFile'`` 的情形中,最後的 I/O action 就會包含讀入的行數。
@@ -847,7 +847,7 @@ Take salad out of the oven
==命令列引數
-[$/img/arguments.png]
+[$../img/arguments.png]
如果你想要寫一個在終端裡運行的程式,處裡命令列引數是不可或缺的。幸運地,利用 Haskell 的 Standard Libary 能讓我們有效地處裡命令列引數。
@@ -1016,7 +1016,7 @@ remove [fileName, numberString] = do
}}
-[^/img/salad.png]
+[^../img/salad.png]
總結我們的程式:我們做了一個 dispatch association,將指令對應到一些會接受命令列引數並回傳 I/O action 的函數。我們知道使用者下了什麼命令,並根據那個命令從 dispatch list 取出對影的函數。我們用剩下的命令列引數呼叫哪些函數而得到一些作相對應事情的 I/O action。然後便執行那些 I/O action。
@@ -1053,7 +1053,7 @@ $ ./todo view todo.txt
==亂數
-[$/img/random.png]
+[$../img/random.png]
在許多情況下,你寫程式會需要些隨機的資料。或許你在製作一個遊戲,在遊戲中你需要擲骰子。或是你需要測試程式的測試資料。精準一點地說,我們需要 pseudo-random 的資料,我們知道真正的隨機資料好比是一隻猴子拿著起司跟奶油騎在單輪車上,任何事情都會發生。在這個章節,我們要看看如何讓 Haskell 產生些 pseudo-random 的資料。
@@ -1285,7 +1285,7 @@ askForNumber gen = do
askForNumber newGen
}}
-[^/img/jackofdiamonds.png]
+[^../img/jackofdiamonds.png]
我們寫了一個 ``askForNumber`` 的函數,他接受一個 random generator 並回傳一個問使用者要數字並回答是否正確的 I/O action。在那個函數裡面,我們先根據從參數拿到的 generator 產生一個亂數以及一個新的 generator,分別叫他們為 ``randomNumber`` 跟 ``newGen``。假設那個產生的數字是 ``7``。則我們要求使用者猜我們握有的數字是什麼。我們用 ``getLine`` 來將結果綁定到 ``numberString`` 上。當使用者輸入 ``7``,``numberString`` 就會是 ``"7"``。接下來,我們用 ``when`` 來檢查使用者輸入的是否是空字串。如果是,那一個空的 I/O action ``return ()`` 就會被回傳。基本上就等於是結束程式的意思。如果不是,那 I/O action 就會被執行。我們用 ``read`` 來把 ``numberString`` 轉成一個數字,所以 ``number`` 便會是 ``7``。
@@ -1335,7 +1335,7 @@ main = do
==Bytestrings
-[$/img/chainchomp.png]
+[$../img/chainchomp.png]
List 是一種有用又酷的資料結構。到目前為止,我們幾乎無處不使用他。有好幾個函數是專門處裡 List 的,而 Haskell 惰性的性質又讓我們可以用 filter 跟 map 來替換其他語言中的 for loop 跟 while loop。也由於 evaluation 只會發生在需要的時候,像 infinite list 也對於 Haskell 不成問題(甚至是 infinite list of infinite list)。這也是為什麼 list 能被用來表達 stream,像是讀取標準輸入或是讀取檔案。我們可以打開檔案然後讀取內容成字串,即便實際上我們是需要的時候才會真正取讀取。
@@ -1431,7 +1431,7 @@ $ runhaskell bytestringcopy.hs something.txt ../../something.txt
==Exceptions (例外)
-[^/img/timber.png]
+[^../img/timber.png]
所有的程式語言都有要處裡失敗的情形。這就是人生。不同的語言有不同的處裡方式。在 C 裡面,我們通常用非正常範圍的回傳值(像是 ``-1`` 或 null)來回傳錯誤。Java 跟 C#則傾向於使用 exception 來處裡失敗的情況。當一個 exception 被丟出的時候,控制流程就會跳到我們做一些清理動作的地方,做完清理後 exception 被重新丟出,這樣一些處裡錯誤的程式碼可以完成他們的工作。
@@ -1448,7 +1448,7 @@ ghci> head []
*** Exception: Prelude.head: empty list
}}
-[^/img/police.png]
+[^../img/police.png]
pure code 能丟出 Exception,但 Exception 只能在 I/O section 中被接到(也就是在 ``main`` 的 do block 中)這是因為在 pure code 中你不知道什麼東西什麼時候會被 evaluate。因為 lazy 特性的緣故,程式沒有一個特定的執行順序,但 I/O code 有。
@@ -1493,7 +1493,7 @@ main = do (fileName:_) <- getArgs
要這樣使用 Exception,我們必須使用 ``System.IO.Error`` 中的**catch**函數。他的型態是 ``catch :: IO a -> (IOError -> IO a) -> IO a``。他接受兩個參數,第一個是一個 I/O action。像是他可以接受一個打開檔案的 I/O action。第二個是 handler。如果第一個參數的 I/O action 丟出了 Exception,則他會被傳給 handler,他會決定要作些什麼。所以整個 I/O action 的結果不是如預期中做完第一個參數的 I/O action,就是 handler 處裡的結果。
-[$/img/puppy.png]
+[$../img/puppy.png]
如果你對其他語言像是 Java, Python 中 try-catch 的形式很熟,那 ``catch`` 其實跟他們很像。第一個參數就是其他語言中的 try block。第二個參數就是其他語言中的 catch block。其中 handler 只有在 exception 被丟出時才會被執行。
View
10 source/_posts/10-functionally-solving-problems.txt
@@ -10,7 +10,7 @@
逆波蘭表示法是另外一種數學式的描述方法。乍看之下顯得怪異,但他其實很容易理解並使用。因為我們不需要括弧來描述,也很容易放進計算機裡面運算。儘管現在的計算機都是用中置的方式讓你輸入,有些人仍堅持用 RPN 的計算機。前述的算式如果表達成 RPN 的話會是 ``10 4 3 + 2 * -``。我們要如何計算他的結果呢?可以想想堆疊,基本上你是從左向右閱讀算式。每當碰到一個數值,就把他堆上堆疊。當我們碰到一個運算子。就把兩個數值從堆疊上拿下來,用運算子運算兩個數值然後把結果推回堆疊中。當你消耗完整個算式的時候,而且假設你的算式是合法的,那你就應該只剩一個數值在堆疊中,
-[^/img/rpn.png]
+[^../img/rpn.png]
我們再接著看 ``10 4 3 + 2 * -``。首先我們把 ``10`` 推到堆疊上,所以堆疊現在是 ``10``。下一個接著的輸入是 ``4``,我們也把他推上堆疊。堆疊的狀態便變成 ``10, 4``。接著也對下一個輸入 ``3`` 做同樣的事,所以堆疊變成 ``10, 4, 3``。然後便碰到了第一個運算子 ``+``。我們把堆疊最上層的兩個數值取下來(所以堆疊變成 ``10``)把兩個數值加起來然後推回堆疊上。堆疊的狀態便變成 ``10, 7``。我們再把輸入 ``2`` 推上堆疊,堆疊變成 ``10, 7, 2``。我們又碰到另一個運算子,所以把 ``7`` 跟 ``2`` 取下,把他們相乘起來然後推回堆疊上。``7`` 跟 ``2`` 相乘的結果是 ``14``,所以堆疊的狀態是 ``10, 14``。最後我們碰到了 ``-``。我們把 ``10`` 跟 ``14`` 取下,將他們相減然後推回堆疊上。所以現在堆疊的狀態變成 ``-4``。而我們已經把所有數值跟運算子的消耗完了,所以 ``-4`` 便是我們的結果。
@@ -23,7 +23,7 @@
小建議:在你去實作函數之前,先想一下你會怎麼宣告這個函數的型別能夠幫助你釐清問題。在 Haskell 中由於我們有夠強的型別系統,光從函數的宣告就可以得到許多資訊。
-[^/img/calculator.png]
+[^../img/calculator.png]
當我們要實作一個問題的解法時,你可以先動手一步一步解看看,嘗試從裡面得到一些靈感。我們這邊把每一個用空白隔開的數值或運算子都當作獨立的一項。所以把 ``"10 4 3 + 2 * -"`` 這樣一個字串斷成一串 list ``["10","4","3","+","2","*","-"]`` 應該會有幫助。
@@ -150,7 +150,7 @@ ghci> solveRPN "43.2425 0.5 ^"
從希思羅機場到倫敦有兩條主要道路,他們中間有很多小路連接彼此。如果你要走小路的話都會花掉一定的時間。你的問題就是要選一條最佳路徑讓你可以盡快前往倫敦。你從圖的最左邊出發,中間可能穿越小路來前往右邊。
-[/img/roads.png]
+[../img/roads.png]
你可以從圖中看到,從希思羅機場到倫敦在這個路徑配置下的最短路徑是先選主要道路 B,經由小路到 A 之後,再走一小段,轉到 B 之後繼續往前走。如果採取這個路徑的話,會花去 75 分鐘。如果選其他道路的話,就會花更多時間。
@@ -185,7 +185,7 @@ ghci> solveRPN "43.2425 0.5 ^"
所以那並不是一個好作法。這邊有一張簡化過後的圖。
-[^/img/roads_simple.png]
+[^../img/roads_simple.png]
你能想出來到道路 A 上第一個交叉點的最短路徑嗎?(標記成 A1 的點)這太容易了。我們只要看看從道路 A 出發或是從道路 B 出發穿越至道路 A 兩種作法哪種比較短就好。很明顯的,從道路 B 出發的比較短,只要花費 40 分鐘,然而從道路 A 則要花費 50 分鐘。那到交叉點 B1 呢?同樣的作法可以看出從道路 B 出發只要花費 10 分鐘,遠比從道路 A 出發然後穿越小路要花費少,後者要花費 80 分鐘!
@@ -286,7 +286,7 @@ roadStep (pathA, pathB) (Section a b c) =
in (newPathToA, newPathToB)
}}
-[$/img/guycar.png]
+[$../img/guycar.png]
上面的程式究竟寫了些什麽?首先他根據先前 A 的最佳解計算出道路 A 的最佳解,之後也如法炮製計算 B 的最佳解。使用 ``sum $ map snd pathA``,所以如果 ``pathA`` 是 ``[(A,100),(C,20)]``。``priceA`` 就是 ``120``。``forwardPriceToA`` 就會是我們要付的成本。如果我們是從先前在 A 上的交叉點前往。那他就會等於我們至先前交叉點的最佳解加上目前 section 中 A 的部份。``crossPriceToA`` 則是我們從先前在 B 上的交叉點前往 A 所要付出的代價。他是先前 B 的最佳解加上 section 中 B 的部份加上 C 的長。同樣地方式也可以決定 ``forwardPriceToB`` 跟 ``crossPriceToB``。
View
28 source/_posts/11-functors-applicative-functors-and-monoids.txt
@@ -7,7 +7,7 @@ Typeclass 的運用是很隨意的。我們可以定義自己的資料型態,
==溫習 Functors
-[$/img/frogtor.png]
+[$../img/frogtor.png]
我們已經在之前的章節提到 functors。如果你還沒讀那個章節,也許你應該先去看看。或是你直接假裝你已經讀過了。
@@ -50,7 +50,7 @@ main = do line <- fmap reverse getLine
putStrLn $ "Yes, you really said" ++ line ++ " backwards!"
}}
-[^/img/alien.png]
+[^../img/alien.png]
就像我們用 ``fmap`` ``reverse`` 來 map over ``Just "blah"`` 會得到 ``Just "halb"``,我們也可以 ``fmap`` ``reverse`` 來 map over ``getLine``。``getLine`` 是一個 I/O action,他的 type 是 ``IO String``,而用 ``reverse`` 來 map over 他會回傳一個取回一個字串並 ``reverse`` 他的 I/O action。就像我們 apply 一個 function 到一個 ``Maybe`` 一樣,我們也可以 apply 一個 function 到一個 ``IO``,只是這個 ``IO`` 會跑去外面拿回某些值。然後我們把結果用 ``<-`` 綁定到某個名稱,而這個名稱綁定的值是已經 ``reverse`` 過了。
@@ -124,7 +124,7 @@ ghci> fmap (show . (*3)) (*100) 1
``fmap`` 等同於 function composition 這件事對我們來說並不是很實用,但至少是一個有趣的觀點。這也讓我們打開視野,看到盒子的比喻不是那麼恰當,functors 其實比較像 computation。function 被 map over 到一個 computation 會產生經由那個 function 映射過後的 computation。
-[$/img/lifter.png]
+[$../img/lifter.png]
在我們繼續看 ``fmap`` 該遵守的規則之前,我們再看一次 ``fmap`` 的型態,他是 ``fmap :: (a -> b) -> f a -> f b``。很明顯我們是在討論 Functor,所以為了簡潔,我們就不寫 ``(Functor f) =>`` 的部份。當我們在學 curry 的時候,我們說過 Haskell 的 function 實際上只接受一個參數。一個型態是 ``a -> b -> c`` 的函數實際上是接受 ``a`` 然後回傳 ``b -> c``,而 ``b -> c`` 實際上接受一個 ``b`` 然後回傳一個 ``c``。如果我們用比較少的參數呼叫一個函數,他就會回傳一個函數需要接受剩下的參數。所以 ``a -> b -> c`` 可以寫成 ``a -> (b -> c)``。這樣 curry 可以明顯一些。
@@ -195,7 +195,7 @@ instance Functor Maybe where
而將 ``id`` map over ``Nothing`` 會拿回 ``Nothing`` 並不稀奇。所以從這兩個 ``fmap`` 的實作,我們可以看到的確 ``fmap id = id`` 有被遵守。
-[^/img/justice.png]
+[^../img/justice.png]
第二定律描述說先將兩個函數合成並將結果 map over 一個 functor 的結果,應該跟先將第一個函數 map over 一個 functor,再將第二個函數 map over 那個 functor 的結果是一樣的。正式地寫下來的話就是 ``fmap (f . g) = fmap f . fmap g``。或是用另外一種寫法,對於任何一個 functor F,下面這個式子應該要被遵守:``fmap (f . g) F = fmap f (fmap g F)``v
@@ -278,7 +278,7 @@ CJust 0 "haha"
==Applicative functors
-[$/img/present.png]
+[$../img/present.png]
在這個章節中,我們會學到 applicative functors,也就是加強版的 functors,在 Haskell 中是用在 ``Control.Applicative`` 中的 ``Applicative`` 這個 typeclass 來定義的。
@@ -373,7 +373,7 @@ ghci> pure (+) <*> Nothing <*> Just 5
Nothing
}}
-[$/img/whale.png]
+[$../img/whale.png]
究竟我們寫了些什麽?我們來一步步看一下。``<*>`` 是 left-associative,也就是說 ``pure (+) <*> Just 3 <*> Just 5`` 可以寫成 ``(pure (+) <*> Just 3) <*> Just 5``。首先 ``+`` 是擺在一個 functor 中,在這邊剛好他是一個 ``Maybe``。所以首先,我們有 ``pure (+)``,他等價於 ``Just (+)``。接下來由於 partial application 的關係,``Just (+) <*> Just 3`` 等價於 ``Just (3+)``。把一個 ``3`` 餵給 ``+`` 形成另一個只接受一個參數的函數,他的效果等於加上 3。最後 ``Just (3+) <*> Just 5`` 被運算,其結果是 ``Just 8``。
@@ -498,7 +498,7 @@ instance Applicative IO where
}}
-[^/img/knight.png]
+[^../img/knight.png]
由於 ``pure`` 是把一個值放進最小的 context 中,所以將 ``return`` 定義成 ``pure`` 是很合理的。因為 ``return`` 也是做同樣的事情。他做了一個不做任何事情的 I/O action,他可以產生某些值來作為結果,但他實際上並沒有做任何 I/O 的動作,例如說印出結果到終端或是檔案。
@@ -580,7 +580,7 @@ ghci> (\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5
[8.0,10.0,2.5]
}}
-[$/img/jazzb.png]
+[$../img/jazzb.png]
這邊也一樣。我們創建了一個函數,他會呼叫 ``\x y z -> [x,y,z]``,而丟的參數是 ``(+3)``, ``(*2)`` 跟 ``(/2)``。``5`` 被丟給以上三個函數,然後他們結果又接到 `` \x y z -> [x, y, z]``。
@@ -791,7 +791,7 @@ woo
==關鍵字"newtype"
-[^/img/maoi.png]
+[^../img/maoi.png]
到目前為止,我們已經看過了如何用 ``data`` 關鍵字定義自己的 algebraic data type。我們也學習到了如何用 ``type`` 來定義 type synonyms。在這個章節中,我們會看一下如何使用 ``newtype`` 來從一個現有的型別中定義出新的型別,並說明我們為什麽會想要那麼做。
@@ -904,7 +904,7 @@ instance Functor Maybe where
fmap :: (a -> b) -> Maybe a -> Maybe b
}}
-[$/img/shamrock.png]
+[$../img/shamrock.png]
看起來不錯吧?現在我們想要 tuple 成為 ``Functor`` 的一個 instance,所以當我們用 ``fmap`` 來 map over 一個 tuple 時,他會先套用到 tuple 中的第一個元素。這樣當我們做 ``fmap (+3) (1,1)`` 會得到 ``(4,1)``。不過要定義出這樣的 instance 有些困難。對於 ``Maybe``,我們只要寫 ``instance Functor Maybe where``,這是因為對於只吃一個參數的型別構造子我們很容易定義成 ``Functor`` 的 instance。但對於 ``(a,b)`` 這樣的就沒辦法。要繞過這樣的困境,我們可以用 ``newtype`` 來重新定義我們的 tuple,這樣第二個型別參數就代表了 tuple 中的第一個元素部份。
@@ -1099,7 +1099,7 @@ class Monoid m where
mconcat = foldr mappend mempty
}}
-[$/img/balloondog.png]
+[$../img/balloondog.png]
``Monoid`` typeclass 被定義在 ``import Data.Monoid`` 中。我們來花些時間好好瞭解他。
@@ -1159,7 +1159,7 @@ ghci> mempty :: [a]
[]
}}
-[^/img/smug.png]
+[^../img/smug.png]
注意到最後一行我們明白地標記出型別。這是因為如果只些 ``mempty`` 的話,GHCi 不會知道他是哪一個 instance 的 ``mempty``,所以我們必須清楚說出他是 list instance 的 mempty。我們可以使用一般化的型別 ``[a]``,因為空的 list 可以看作是屬於任何型別。
@@ -1325,7 +1325,7 @@ instance Monoid Ordering where
GT `mappend` _ = GT
}}
-[$/img/bear.png]
+[$../img/bear.png]
這個 instance 定義如下:當我們用 ``mappend`` 兩個 ``Ordering`` 型別的值時,左邊的會被保留下來。除非左邊的值是 ``EQ``,那我們就會保留右邊的當作結果。而 identity 就是 ``EQ``。乍看之下有點隨便,但實際上他是我們比較兩個英文字時所用的方法。我們先比較兩個字母是否相等,如果他們不一樣,那我們就知道那一個字在字典中會在前面。而如果兩個字母相等,那我們就繼續比較下一個字母,以此類推。
@@ -1549,7 +1549,7 @@ instance F.Foldable Tree where
-[$/img/accordion.png]
+[$../img/accordion.png]
我們是這樣思考的:如果我們寫一個函數,他接受樹中的一個元素並回傳一個 monoid,那我們要怎麼簡化整棵樹到只有單一一個 monoid?當我們在對樹做 ``fmap`` 的時候,我們將那函數套用至節點上,並遞迴地套用至左子樹以及右子樹。這邊我們不只是 map 一個函數而已,我們還要求要把結果用 ``mappend`` 簡化成只有單一一個 monoid 值。首先我們考慮樹為空的情形,一棵沒有值也沒有子樹的情形。由於沒有值我們也沒辦法將他套用上面轉換成 monoid 的函數,所以當樹為空的時候,結果應該要是 ``mempty``。
View
24 source/_posts/12-a-fistful-of-monads.txt
@@ -4,7 +4,7 @@
在這一章,我們會學到 Monad,基本上他是一種加強版的 Applicative Functor,正如 Applicative Functor 是 Functor 的加強版一樣。
-[$/img/smugpig.png]
+[$../img/smugpig.png]
我們介紹到 Functor 是因為我們觀察到有許多型態都可以被 function 給 map over,了解到這個目的,便抽象化了 ``Functor`` 這個 typeclass 出來。但這讓我們想問:如果給定一個 ``a -> b`` 的函數以及 ``f a`` 的型態,我們要如何將函數 map over 這個型態而得到 ``f b``?我們知道要如何 map over ``Maybe a``,``[a]`` 以及 ``IO a``。我們甚至還知道如何用 ``a -> b`` map over ``r -> a``,並且會得到 ``r -> b``。要回答這個問題,我們只需要看 ``fmap`` 的型態就好了:
@@ -48,7 +48,7 @@ Monad 是一個從 Applicative functors 很自然的一個演進結果。對於
==動手做做看: Maybe Monad
-[^/img/buddha.png]
+[^../img/buddha.png]
現在對於什麼是 Monad 已經有了些模糊的概念,
我們來看看要如何讓這概念更具體一些。
@@ -159,7 +159,7 @@ class Monad m where
fail msg = error msg
}}
-[$/img/kid.png]
+[$../img/kid.png]
我們從第一行開始看。他說 ``class Monad m where``。但我們之前不是提到 monad 是 applicative functors 的加強版嗎?不是應該有一個限制說一個型態必須先是一個 applicative functor 才可能是一個 monad 嗎?像是 ``class (Applicative m) = > Monad m where``。他的確應該要有,但當 Haskell 被創造的早期,人們沒有想到 applicative functor 適合被放進語言中,所以最後沒有這個限制。但的確每個 monad 都是 applicative functor,即使 ``Monad`` 並沒有這麼宣告。
@@ -168,7 +168,7 @@ class Monad m where
提醒一下:``return`` 跟其他語言中的 ``return`` 是完全不一樣的。他並不是結束一個函數的執行,他只不過是把一個普通值包進一個 context 裡面。
-[^/img/tur2.png]
+[^../img/tur2.png]
接下來定義的函數是 bind: ``>>=``。他就像是函數套用一樣,只差在他不接受普通值,他是接受一個 monadic value(也就是具有 context 的值)並且把他餵給一個接受普通值得函數,並回傳一個 monadic value。
@@ -210,7 +210,7 @@ Nothing
==走鋼索
-[^/img/pierre.png]
+[^../img/pierre.png]
我們已經知道要如何把 ``Maybe a`` 餵進 ``a -> Maybe b`` 這樣的函數。我們可以看看我們如何重複使用 ``>>=`` 來處理多個 ``Maybe a`` 的值。
@@ -363,7 +363,7 @@ ghci> return (0,0) >>= landLeft 1 >>= landRight 4 >>= landLeft (-1) >>= landRigh
Nothing
}}
-[$/img/banana.png]
+[$../img/banana.png]
正如預期的,最後的情形代表了失敗的情況。我們再進一步看看這是怎麼產生的。首先 ``return`` 把 ``(0,0)`` 放到一個最小的 context 中,得到 ``Just (0,0)``。然後是 ``Just (0.0) >>= landLeft 1``。由於 ``Just (0,0)`` 是一個 ``Just`` 的值。``landLeft 1`` 被套用至 ``(0,0)`` 而得到 ``Just (1,0)``。這反應了我們仍保持在平衡的狀態。接著是 ``Just (1,0) >>= landright 4`` 而得到了 ``Just (1,4)``。距離不平衡只有一步之遙了。他又被餵給 ``landLeft (-1)``,這組合成了 ``landLeft (-1) (1,4)``。由於失去了平衡,我們變得到了 ``Nothing``。而我們把 ``Nothing`` 餵給 ``landRight (-2)``,由於他是 ``Nothing``,也就自動得到了 ``Nothing``。
@@ -428,7 +428,7 @@ routine = case landLeft 1 (0,0) of
Just pole3 -> landLeft 1 pole3
}}
-[$/img/centaur.png]
+[$../img/centaur.png]
左邊先停了一隻鳥,然後我們停下來檢查有沒有失敗。當失敗的時候我們回傳 ``Nothing``。當成功的時候,我們在右邊停一隻鳥,然後再重複前面做的事情。把這些瑣事轉換成 ``>>=`` 證明了 ``Maybe`` Monad 的力量,可以省去我們不少的時間。
@@ -494,7 +494,7 @@ foo = do
Just (show x ++ y)
}}
-[$/img/owld.png]
+[$../img/owld.png]
這看起來好像讓我們不用在每一步都去檢查 ``Maybe`` 的值究竟是 ``Just`` 或 ``Nothing``。這蠻方便的,如果在任何一個步驟我們取出了 ``Nothing``。那整個 ``do`` 的結果就會是 ``Nothing``。我們把整個責任都交給 ``>>=``,他會幫我們處理所有 context 的問題。這邊的 ``do`` 表示法不過是另外一種語法的形式來串連所有的 monadic value 罷了。
@@ -621,7 +621,7 @@ Nothing
==List Monad
-[^/img/deadcat.png]
+[^../img/deadcat.png]
我們已經了解了 ``Maybe`` 可以被看作具有失敗可能性 context 的值,也見識到如何用 ``>>=`` 來把這些具有失敗考量的值傳給函數。在這一個章節中,我們要看一下如何利用 list 的 monadic 的性質來寫 non-deterministic 的程式。
@@ -681,7 +681,7 @@ ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return (n,ch)
[(1,'a'),(1,'b'),(2,'a'),(2,'b')]
}}
-[^/img/concatmap.png]
+[^../img/concatmap.png]
``[1,2]`` 被綁定到 ``n`` 而 ``['a','b']`` 被綁定到 ``ch``。最後我們用 ``return (n,ch)`` 來把他放到一個最小的 context 中。在這個案例中,就是把 ``(n,ch)`` 放到 list 中,這代表最低程度的 non-determinism。整套結構要表達的意思就是對於 ``[1,2]`` 的每個元素,以及 ``['a','b']`` 的每個元素,我們產生一個 tuple,每項分別取自不同的 list。
@@ -800,7 +800,7 @@ ghci> [ x | x <- [1..50], '7' `elem` show x ]
這邊來看一個可以用 non-determinism 解決的問題。假設你有一個西洋棋盤跟一隻西洋棋中的騎士擺在上面。我們希望知道是否這隻騎士可以在三步之內移到我們想要的位置。我們只要用一對數值來表示騎士在棋盤上的位置。第一個數值代表棋盤的行,而第二個數值代表棋盤的列。
-[/img/chess.png]
+[../img/chess.png]
我們先幫騎士的位置定義一個 type synonym。
@@ -889,7 +889,7 @@ False
==Monad laws (單子律)
-[$/img/judgedog.png]
+[$../img/judgedog.png]
正如 aaplicative functors 以及 functors,Monad 也有一些要遵守的定律。我們定義一個 ``Monad`` 的 instance 並不代表他是一個 monad,只代表他被定義成那個 type class 的 instance。一個型態要是 monad,則必須遵守單子律。這些定律讓我們可以對這個型態的行為做一些合理的假設。
View
26 source/_posts/13-for-a-few-monads-more.txt
@@ -1,6 +1,6 @@
=再來看看更多 Monad #for-a-few-monads-more
-[$/img/clint.png]
+[$../img/clint.png]
我們已經看過 Monad 是如何接受具有 context 的值,並如何用函數操作他們 還有如何用 ``>>=`` 跟 ``do`` 來減輕我們對 context 的關注,集中精神在 value 本身。
@@ -39,7 +39,7 @@ ghci> isBigGang 30
(True,"Compared gang size to 9.")
}}
-[^/img/tuco.png]
+[^../img/tuco.png]
到目前為止都還不錯,``isBigGang`` 回傳一個值跟他的 context。對於正常的數值來說這樣的寫法都能運作良好。但如果我們想要把一個已經具有 context 的值,像是 ``(3, "Smallish gang.")``,餵給 ``isBigGang`` 呢?我們又面對了同樣的問題:如果我們有一個能接受正常數值並回傳一個具有 context 值的 function,那我們要如何餵給他一個具有 context 的值?
@@ -166,7 +166,7 @@ instance (Monoid w) => Monad (Writer w) where
(Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')
}}
-[$/img/angeleyes.png]
+[$../img/angeleyes.png]
首先,我們來看看 ``>>=``。他的實作基本上就是 ``applyLog``,只是我們的 tuple 現在是包在一個 ``Writer`` 的 ``newtype`` 中,我們可以用 pattern matching 的方式把他給 unwrap。我們將 ``x`` 餵給 ``f``。這會回給我們 ``Writer w a``。接著可以用 ``let`` expression 來做 pattern matching。把結果綁定到 ``y`` 這個名字上,然後用 ``mappend`` 來結合舊的 monoid 值跟新的 monoid 值。最後把結果跟 monoid 值用 ``Writer`` constructor 包起來,形成我們最後的 ``Writer`` value。
@@ -345,7 +345,7 @@ Finished with 1
===Difference lists
-[^/img/cactus.png]
+[^../img/cactus.png]
由於 list 在重複 append 的時候顯得低效,我們最好能使用一種支援高效 appending 的資料結構。其中一種就是 difference list。difference list 很類似 list,只是他是一個函數。他接受一個 list 並 prepend 另一串 list 到他前面。一個等價於 ``[1,2,3]`` 的 difference list 是這樣一個函數 ``\xs -> [1,2,3] ++ xs``。一個等價於 ``[]`` 的 difference list 則是 ``\xs -> [] ++ xs``。
@@ -473,7 +473,7 @@ ghci> mapM_ putStrLn . snd . runWriter $ finalCountDown 500000
==Reader Monad
-[^/img/revolver.png]
+[^../img/revolver.png]
在講 Applicative 的章節中,我們說過了 ``(->) r`` 的型態只是 ``Functor`` 的一個 instance。要將一個函數 ``f`` map over 一個函數 ``g``,基本上等價於一個函數,他可以接受原本 ``g`` 接受的參數,先套用 ``g`` 然後再把其結果丟給 ``f``。
@@ -540,7 +540,7 @@ addStuff x = let
==State Monad
-[^/img/texas.png]
+[^../img/texas.png]
Haskell 是一個純粹的語言,正因為如此,我們的程式是有一堆沒辦法改變全域狀態或變數的函數所組成,他們只會作些處理並回傳結果。這樣的性質讓我們很容易思考我們的程式在幹嘛,因為我們不需要擔心變數在某一個時間點的值是什麼。然而,有一些領域的問題根本上就是依賴於隨著時間而改變的狀態。雖然我們也可以用 Haskell 寫出這樣的程式,但有時候寫起來蠻痛苦的。這也是為什麼 Haskell 要加進 State Monad 這個特性。這讓我們在 Haskell 中可以容易地處理狀態性的問題,並讓其他部份的程式還是保持純粹性。
@@ -649,7 +649,7 @@ instance Monad (State s) where
我們先來看看 ``return`` 那一行。我們 ``return`` 要作的事是接受一個值,並做出一個改變狀態的操作,讓他永遠回傳那個值。所以我們才做了一個 lambda 函數,``\s -> (x,s)``。我們把 ``x`` 當成是結果,並且狀態仍然是 ``s``。這就是 ``return`` 要完成的 minimal context。
-[$/img/badge.png]
+[$../img/badge.png]
那 ``>>=`` 的實作呢?很明顯的把改變狀態的操作餵進 ``>>=`` 也必須要丟出另一個改變狀態的操作。所以我們用 ``State`` 這個 ``newtype`` wrapper 來把一個 lambda 函數包住。這個 lambda 會是新的一個改變狀態的操作。但裡面的內容是什麼?首先我們應該要從接受的操作取出結果。由於 lambda 是在一個大的操作中,所以我們可以餵給 ``h`` 我們現在的狀態,也就是 ``s``。那會產生 ``(a, newState)``。到目前為止每次我們在實作 ``>>=`` 的時候,我們都會先從 monadic value 中取出結果,然後餵給 ``f`` 來得到新的 monadic value。在寫 ``Writer`` 的時候,我們除了這樣作還要確保 context 是用 ``mappend`` 把舊的 monoid value 跟新的接起來。在這邊我們則是用 ``f a`` 得到一個新的操作 ``g``。現在我們有了新的操作跟新的狀態(叫做 ``newState``),我們就把 ``newState`` 餵給 ``g``。結果便是一個 tuple,裡面包含了最後的結果跟最終的狀態。
@@ -892,7 +892,7 @@ Right 103
===liftM
-[$/img/wolf.png]
+[$../img/wolf.png]
當我們開始學習 Monad 的時候,我們是先學習 functors,他代表可以被 map over 的事物。接著我們學了 functors 的加強版,也就是 applicative functors,他可以對 applicative values 做函數的套用,也可以把一個一般值放到一個預設的 context 中。最後,我們介紹在 applicative functors 上更進一步的 monad,他讓這些具有 context 的值可以被餵進一般函數中。
@@ -1081,7 +1081,7 @@ joinedMaybes = do
m
}}
-[$/img/tipi.png]
+[$../img/tipi.png]
最有趣的是對於一個 monadic value 而言,用 ``>>=`` 把他餵進一個函數其實等價於對 monad 做 mapping over 的動作,然後用 ``join`` 來把值從 nested 的狀態變成扁平的狀態。也就是說 ``m >>= f`` 其實就是 ``join (fmap f m)``。如果你仔細想想的話其實很明顯。``>>=`` 的使用方式是,把一個 monadic value 餵進一個接受普通值的函數,但他卻會回傳 monadic value。如果我們 map over 一個 monadic value,我們會做成一個 monadic value 包了另外一個 monadic value。例如說,我們現在手上有 ``Just 9`` 跟 ``\x -> Just (x+1)``。如果我們把這個函數 map over ``Just 9``,我們會得到 ``Just (Just 10)``
@@ -1229,7 +1229,7 @@ Nothing
===Making a safe RPN calculator
-[^/img/miner.png]
+[^../img/miner.png]
之前的章節我們實作了一個 RPN 計算機,但我們沒有做錯誤的處理。他只有在輸入是合法的時候才會運算正確。假如有東西出錯了,整個程式便會當掉。我們在這章看到了要怎樣把程式碼轉換成 monadic 的版本,我們先嘗適用 ``Maybe`` monad 來幫我們的 RPN 計算機加上些錯誤處理。
@@ -1391,7 +1391,7 @@ canReachIn x start end = end `elem` inMany x start
==定義自己的 Monad
-[/img/spearhead.png]
+[../img/spearhead.png]
在這一章節,我們會帶你看看究竟一個型態是怎麼被辨認,確認是一個 monad 而且正確定義出 ``Monad`` 的 instance。我們通常不會為了定義 monad 而定義。比較常發生的是,我們想要針對一個問題建立模型,卻稍後發現我們定義的型態其實是一個 Monad,所以就定義一個 ``Monad`` 的 instance。
@@ -1456,7 +1456,7 @@ Prob {getProb = [(-3,1 % 2),(-5,1 % 4),(-9,1 % 4)]}
至於 ``>>=`` 呢?看起來有點複雜,所以我們換種方式來思考,我們知道 ``m >>= f`` 會等價於 ``join (fmap f m)``,所以我們來想要怎麼把一串包含 probability list 的 list 弄平。舉個例子,考慮一個 list,``'a'`` 跟 ``'b'`` 恰出現其中一個的機率為 25%,兩個出現的機率相等。而 ``'c'`` 跟 ``'d'`` 恰出現其中一個的機率為 75%,兩個出現的機率也是相等。這邊有一個圖將情形畫出來。
-[^/img/prob.png]
+[^../img/prob.png]
每個字母發生的機率有多高呢?如果我們用四個盒子來代表每個字母,那每個盒子的機率為何?每個盒子的機率是他們所裝有的機率值相乘的結果。``'a'`` 的機率是八分之一,``'b'`` 同樣也是八分之一。八分之一是因為我們把二分之一跟四分之一相乘得到的結果。而 ``'c'`` 發生的機率是八分之三,是因為二分之一乘上四分之三。``'d'`` 同樣也是八分之三。如果把所有的機率加起來,就會得到一,符合機率的規則。
@@ -1489,7 +1489,7 @@ instance Monad Prob where
fail _ = Prob []
}}
-[$/img/ride.png]
+[$../img/ride.png]
由於我們已經把所有苦工的做完了,定義這個 instance 顯得格外輕鬆。我們也定義了 ``fail``,我們定義他的方式跟定義 list 一樣。如果在 ``do`` 中發生了失敗的 pattern match,那就會呼叫 ``fail``。
View
18 source/_posts/14-zippers.txt
@@ -1,6 +1,6 @@
=Zippers 資料結構 #zippers
-[$/img/60sdude.png]
+[$../img/60sdude.png]
儘管 Haskell 的純粹性質帶來很多好處,但他讓一些在非純粹語言很容易處理的一些事情變得要用另一種方法解決。由於 referential transparency,同樣一件事在 Haskell 中是沒有分別的。所以如果我們有一個裝滿 5 的樹,而我們希望把其中一個換成 6,那我們必須要知道我們究竟是想改變哪個 5。我們也必須知道我們身處在這棵樹的哪裡。但在 Haskell 中,每個 5 都長得一樣,我們並不能因為他們在記憶體中的位址不同就把他們區分開來。我們也不能改變任何狀態,當我們想要改變一棵樹的時候,我們實際上是說我們要一棵新的樹,只是他長得很像舊的。一種解決方式是記住一條從根節點到現在這個節點的路徑。我們可以這樣表達:給定一棵樹,先往左走,再往右走,再往左走,然後改變你走到的元素。雖然這是可行的,但這非常沒有效率。如果我們想接連改變一個在附近的節點,我們必須再從根節點走一次。在這個章節中,我們會看到我們可以集中注意在某個資料結構上,這樣讓改變資料結構跟遍歷的動作非常有效率。
@@ -43,7 +43,7 @@ freeTree =
畫成圖的話就是像這樣:
-[$/img/pollywantsa.png]
+[$../img/pollywantsa.png]
注意到 ``W`` 這個節點了嗎?如果我們想要把他變成 ``P``。我們會怎麼做呢?一種方式是用 pattern match 的方式做,直到我們找到那個節點為止。要先往右走再往左走,再改變元素內容,像是這樣:
@@ -94,7 +94,7 @@ ghci> elemAt [R,L] newTree
==凡走過必留下痕跡
-[$/img/bread.png]
+[$../img/bread.png]
我們需要一個比包含一串方向的 list 更好的聚焦的方法。如果我們能夠在從 root 走到指定地點的沿路上撒下些麵包屑,來紀錄我們的足跡呢?當我們往左走,我們便記住我們選擇了左邊,當我們往右走,便記住我們選擇了右邊。
@@ -125,7 +125,7 @@ ghci> goLeft (goRight (freeTree, []))
(Node 'W' (Node 'C' Empty Empty) (Node 'R' Empty Empty),[L,R])
}}
-[^/img/almostzipper.png]
+[^../img/almostzipper.png]
現在我們有了一棵樹,他的 root 是 ``'W'``,而他的左子樹的 root 是 ``'C'``,右子樹的 root 是 ``'R'``。而由於我們先往右走再往左走,所以麵包屑是 ``[L,R]``。
@@ -192,7 +192,7 @@ goUp (t, LeftCrumb x r:bs) = (Node x t r, bs)
goUp (t, RightCrumb x l:bs) = (Node x l t, bs)
}}
-[^/img/asstronaut.png]
+[^../img/asstronaut.png]
我們鎖定了 ``t`` 這棵樹並檢查最新的 ``Crumb``。如果他是 ``LeftCrumb``,那我們就建立一棵新的樹,其中 ``t`` 是他的左子樹並用關於我們沒走過得右子樹的資訊來填寫其他 ``Node`` 的資訊。由於我們使用了麵包屑的資訊來建立父子樹,所以新的 list 移除了我們的麵包屑。
@@ -281,7 +281,7 @@ Zippers 幾乎可以套用在任何資料結構上,所以聽到他可以被套
data List a = Empty | Cons a (List a) deriving (Show, Read, Eq, Ord)
}}
-[$/img/picard.png]
+[$../img/picard.png]
跟我們二元樹的定義比較,我們就可以看出我們把 list 看作樹的原則是正確的。
@@ -374,7 +374,7 @@ myDisk :: FSItem
===A zipper for our file system
-[$/img/spongedisk.png]
+[$../img/spongedisk.png]
我們有了一個檔案系統,我們需要一個 Zipper 來讓我們可以四處走動,並且增加、修改或移除檔案跟資料夾。就像二元樹或 list,我們會用麵包屑留下我們未走過路徑的資訊。正如我們說的,一個麵包屑就像是一個節點,只是他包含所有除了我們現在正鎖定的子樹的資訊。
@@ -423,7 +423,7 @@ nameIs name (File fileName _) = name == fileName
``fsTo`` 接受一個 ``Name`` 跟 ``FSZipper``,回傳一個新的 ``FSZipper`` 鎖定在某個檔案上。那個檔案必須在現在身處的資料夾才行。這函數不會四處找尋這檔案,他只會看現在的資料夾。
-[^/img/cool.png]
+[^../img/cool.png]
首先我們用 ``break`` 來把身處資料夾中的檔案們分成在我們要找的檔案前的,跟之後的。如果記性好,``break`` 會接受一個 predicate 跟一個 list,並回傳兩個 list 組成的 pair。第一個 list 裝有 predicate 會回傳 ``False`` 的元素,而一旦碰到一個元素回傳 ``True``,他就把剩下的所有元素都放進第二個 list 中。我們用了一個輔助函數叫做 ``nameIs``,他接受一個名字跟一個檔案系統的元素,如果名字相符的話他就會回傳 ``True``。
@@ -498,7 +498,7 @@ goLeft :: Zipper a -> Zipper a
goLeft (Node x l r, bs) = (l, LeftCrumb x r:bs)
}}
-[$/img/bigtree.png]
+[$../img/bigtree.png]
但如果我們走的樹其實是空的樹呢?也就是說,如果他不是 ``Node`` 而是 ``Empty``?再這情況,我們會因為模式匹配不到東西而造成 runtime error。我們沒有處理空的樹的情形,也就是沒有子樹的情形。到目前為止,我們並沒有試著在左子樹不存在的情形下鎖定左子樹。但要走到一棵空的樹的左子樹並不合理,只是到目前為止我們視而不見而已。
View
2  source/_posts/faq.txt
@@ -1,5 +1,5 @@
=FAQ
-[$/img/turtle.png]
+[$img/turtle.png]
==我能把這份教學放在我的網站嗎?我可以更改裡面的內容嗎?
當然。它受[http://creativecommons.org/licenses/by-nc-sa/3.0/ creative commons attribution noncommercial blah blah blah...]許可證的保護,因此你可以分享或修改它。只要你是發自內心想要這麼作,並且只將教學用於非商業目的。
View
2  source/index.html
@@ -4,7 +4,7 @@
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap-combined.min.css" rel="stylesheet">
- <link href="/css/docs.css" rel="stylesheet">
+ <link href="css/docs.css" rel="stylesheet">
<title>Haskell 趣學指南 (Learn You a Haskell for Great Good! 中文版)</title>
<style>
Something went wrong with that request. Please try again.