Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion zh-cn/01-introduction.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<p>[$../img/fx.png]</p>

<p>Haskell 是一门<em>纯函数式编程语言 (purely functional programming language)</em>。在命令式语言中执行操作需要给电脑安排一组命令,随着命令的执行,状态就会随之发生改变。例如你指派变量 <code>a</code> 的值为 5,而随后做了其它一些事情之后 a 就可能变成的其它值。有控制流程 (control flow),你就可以重复执行操作。然而在纯函数式编程语言中,你不是像命令式语言那样命令电脑“要做什么”,而是通过用函数来描述出问题“是什么”,如“<em>阶乘是指从1到某个数的乘积</em>”,&quot;一个串列中数字的和&quot;是指把第一个数字跟剩余数字的和相加。你用宣告函数是什么的形式来写程序。另外,变量 (variable) 一旦被指定,就不可以更改了,你已经说了 <code>a</code> 就是 5,就不能再另说 a 是别的什么数。(译注:其实用 variable 来表达造成字义的 overloading,会让人联想到 imperative languages 中 variable 是代表状态,但在 functional languages 中 variable 是相近于数学中使用的 variable。<code>x=5</code> 代表 <code>x</code> 就是 5,不是说 <code>x</code> 在 5 这个状态。) 所以说,在纯函数式编程语言中的函数能做的唯一事情就是利用引数计算结果,不会产生所谓的&quot;副作用 (side effect)&quot; (译注:也就是改变非函数内部的状态,像是 imperative languages 里面动到 global variable 就是 side effect)。一开始会觉得这限制很大,不过这也是他的优点所在:若以同样的参数调用同一个函数两次,得到的结果一定是相同。这被称作“<em>引用透明 (Referential Transparency)</em>” (译注:这就跟数学上函数的使用一样)。如此一来编译器就可以理解程序的行为,你也很容易就能验证一个函数的正确性,继而可以将一些简单的函数组合成更复杂的函数。</p>
<p>Haskell 是一门<em>纯函数式编程语言 (purely functional programming language)</em>。在命令式语言中执行操作需要给电脑安排一组命令,随着命令的执行,状态就会随之发生改变。例如你指派变量 <code>a</code> 的值为 5,而随后做了其它一些事情之后 a 就可能变成的其它值。有控制流程 (control flow),你就可以重复执行操作。然而在纯函数式编程语言中,你不是像命令式语言那样命令电脑“要做什么”,而是通过用函数来描述出问题“是什么”,如“<em>阶乘是指从1到某个数的乘积</em>”,&quot;一个串列中数字的和&quot;是指把第一个数字跟剩余数字的和相加。你用宣告函数是什么的形式来写程序。另外,变量 (variable) 一旦被指定,就不可以更改了,你已经说了 <code>a</code> 就是 5,就不能再另说 a 是别的什么数。(译注:其实用 variable 来表达造成字义的 overloading,会让人联想到 imperative languages 中 variable 是代表状态,但在 functional languages 中 variable 是相近于数学中使用的 variable。<code>x=5</code> 代表 <code>x</code> 就是 5,不是说 <code>x</code> 在 5 这个状态。) 所以说,在纯函数式编程语言中的函数能做的唯一事情就是利用参数计算结果,不会产生所谓的&quot;副作用 (side effect)&quot; (译注:也就是改变非函数内部的状态,像是 imperative languages 里面动到 global variable 就是 side effect)。一开始会觉得这限制很大,不过这也是他的优点所在:若以同样的参数调用同一个函数两次,得到的结果一定是相同。这被称作“<em>引用透明 (Referential Transparency)</em>” (译注:这就跟数学上函数的使用一样)。如此一来编译器就可以理解程序的行为,你也很容易就能验证一个函数的正确性,继而可以将一些简单的函数组合成更复杂的函数。</p>

<p>[^../img/lazy.png]</p>

Expand Down
24 changes: 12 additions & 12 deletions zh-cn/09-input-and-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -831,19 +831,19 @@ Which one do you want to delete?<br>
Take salad out of the oven<br>
}}</p>

<p>==命令行引数</p>
<p>==命令行参数</p>

<p>[$../img/arguments.png]</p>

<p>如果你想要写一个在终端里运行的程序,处理命令行引数是不可或缺的。幸运的是,利用 Haskell 的 Standard Libary 能让我们有效地处理命令行引数。</p>
<p>如果你想要写一个在终端里运行的程序,处理命令行参数是不可或缺的。幸运的是,利用 Haskell 的 Standard Libary 能让我们有效地处理命令行参数。</p>

<p>在之前的章节中,我们写了一个能将 to-do item 加进或移除 to-do list 的一个程序。但我们的写法有两个问题。第一个是我们把放 to-do list 的文件名称给写死了。我们擅自决定用户不会有很多个 to-do lists,就把文件命名为 todo.txt。</p>

<p>一种解决的方法是每次都询问用户他们想将他们的 to-do list 放进哪个文件。我们在用户要删除的时候也采用这种方式。这是一种可以运作的方式,但不太能被接受,因为他需要用户运行程序,等待程序询问才能回答。这被称为交互式的程序,但讨厌的地方在当你想要自动化执行程序的时候,好比说写成 script,这会让你的 script 写起来比较困难。</p>

<p>这也是为什么有时候让用户在执行的时候就告诉程序他们要什么会比较好,而不是让程序去问用户要什么。比较好的方式是让用户透过命令行引数告诉程序他们想要什么。</p>
<p>这也是为什么有时候让用户在执行的时候就告诉程序他们要什么会比较好,而不是让程序去问用户要什么。比较好的方式是让用户透过命令行参数告诉程序他们想要什么。</p>

<p>在 <code>System.Environment</code> 模块当中有两个很酷的 I/O actions,一个是 <strong>getArgs</strong>,他的 type 是 <code>getArgs :: IO [String]</code>,他是一个拿取命令行引数的 I/O action,并把结果放在包含的一个串列中。<strong>getProgName</strong> 的型态是 <code>getProgName :: IO String</code>,他则是一个 I/O action 包含了程序的名称。</p>
<p>在 <code>System.Environment</code> 模块当中有两个很酷的 I/O actions,一个是 <strong>getArgs</strong>,他的 type 是 <code>getArgs :: IO [String]</code>,他是一个拿取命令行参数的 I/O action,并把结果放在包含的一个串列中。<strong>getProgName</strong> 的型态是 <code>getProgName :: IO String</code>,他则是一个 I/O action 包含了程序的名称。</p>

<p>我们来看一个展现他们功能的程序。
{{
Expand All @@ -859,7 +859,7 @@ import Data.List </p>
putStrLn progName<br>
}}</p>

<p>我们将 <code>getArgs</code> 跟 <code>progName</code> 分别绑定到 <code>args</code> 跟 <code>progName</code>。我们印出 <code>The arguments are:</code> 以及在 <code>args</code> 中的每个引数。最后,我们印出程序的名字。我们把程序编译成 <code>arg-test</code>。</p>
<p>我们将 <code>getArgs</code> 跟 <code>progName</code> 分别绑定到 <code>args</code> 跟 <code>progName</code>。我们印出 <code>The arguments are:</code> 以及在 <code>args</code> 中的每个参数。最后,我们印出程序的名字。我们把程序编译成 <code>arg-test</code>。</p>

<p>{{
$ ./arg-test first second w00t &quot;multi word arg&quot;<br>
Expand All @@ -872,7 +872,7 @@ The program name is:<br>
arg-test<br>
}}</p>

<p>知道了这些函数现在你能写几个很酷的命令行程序。在之前的章节,我们写了一个程序来加入待作事项,也写了另一个程序删除事项。现在我们要把两个程序合起来,他会根据命令行引数来决定该做的事情。我们也会让程序可以处理不同的文件,而不是只有 todo.txt</p>
<p>知道了这些函数现在你能写几个很酷的命令行程序。在之前的章节,我们写了一个程序来加入待作事项,也写了另一个程序删除事项。现在我们要把两个程序合起来,他会根据命令行参数来决定该做的事情。我们也会让程序可以处理不同的文件,而不是只有 todo.txt</p>

<p>我们叫这程序 todo,他会作三件事:</p>

Expand All @@ -885,7 +885,7 @@ arg-test<br>

<p>我们的程序要像这样运作:假如我们要加入 <code>Find the magic sword of power</code>,则我们会打 <code>todo add todo.txt &quot;Find the magic sword of power&quot;</code>。要查看事项我们则会打 <code>todo view todo.txt</code>,如果要移除事项二则会打 <code>todo remove todo.txt 2</code></p>

<p>我们先作一个分发的 association list。他会把命令行引数当作 key,而对应的处理函数当作 value。这些函数的型态都是 <code>[String] -&gt; IO ()</code>。他们会接受命令行引数的串列并回传对应的查看,加入以及删除的 I/O action。</p>
<p>我们先作一个分发的 association list。他会把命令行参数当作 key,而对应的处理函数当作 value。这些函数的型态都是 <code>[String] -&gt; IO ()</code>。他们会接受命令行参数的串列并回传对应的查看,加入以及删除的 I/O action。</p>

<p>{{
import System.Environment<br>
Expand All @@ -909,11 +909,11 @@ main = do<br>
action args<br>
}}</p>

<p>首先,我们取出引数并把他们绑定到 <code>(command:args)</code>。如果你还记得 pattern matching,这么做会把第一个引数绑定到 <code>command</code>,把其他的绑定到 <code>args</code>。如果我们像这样执行程序 <code>todo add todo.txt &quot;Spank the monkey&quot;</code>,<code>command</code> 会变成 <code>&quot;add&quot;</code>,而 <code>args</code> 会变成 <code>[&quot;todo.txt&quot;, &quot;Spank the monkey&quot;]</code>。</p>
<p>首先,我们取出参数并把他们绑定到 <code>(command:args)</code>。如果你还记得 pattern matching,这么做会把第一个参数绑定到 <code>command</code>,把其他的绑定到 <code>args</code>。如果我们像这样执行程序 <code>todo add todo.txt &quot;Spank the monkey&quot;</code>,<code>command</code> 会变成 <code>&quot;add&quot;</code>,而 <code>args</code> 会变成 <code>[&quot;todo.txt&quot;, &quot;Spank the monkey&quot;]</code>。</p>

<p>在下一行,我们在一个分派的串列中寻到我们的指令是哪个。由于 <code>&quot;add&quot;</code> 指向 <code>add</code>,我们的结果便是 <code>Just add</code>。我们再度使用了 pattern matching 来把我们的函数从 <code>Maybe</code> 中取出。但如果我们想要的指令不在分派的串列中呢?那样 lookup 就会回传 <code>Nothing</code>,但我们这边并不特别处理失败的情况,所以 pattern matching 会失败然后我们的程序就会当掉。</p>

<p>最后,我们用剩下的引数调用 <code>action</code> 这个函数。他会还传一个加入 item,显示所有 items 或者删除 item 的 I/O action。由于这个 I/O action 是在 <code>main</code> 的 do block 中,他最后会被执行。如果我们的 <code>action</code> 函数是 <code>add</code>,他就会被喂 <code>args</code> 然后回传一个加入 <code>Spank the monkey</code> 到 todo.txt 中的 I/O action。</p>
<p>最后,我们用剩下的参数调用 <code>action</code> 这个函数。他会还传一个加入 item,显示所有 items 或者删除 item 的 I/O action。由于这个 I/O action 是在 <code>main</code> 的 do block 中,他最后会被执行。如果我们的 <code>action</code> 函数是 <code>add</code>,他就会被喂 <code>args</code> 然后回传一个加入 <code>Spank the monkey</code> 到 todo.txt 中的 I/O action。</p>

<p>我们剩下要做的就是实作 <code>add</code>,<code>view</code> 跟 <code>remove</code>,我们从 <code>add</code> 开始:</p>

Expand Down Expand Up @@ -1004,7 +1004,7 @@ remove [fileName, numberString] = do<br>

<p>[^../img/salad.png]</p>

<p>总结我们的程序:我们做了一个 dispatch association,将指令对应到一些会接受命令行引数并回传 I/O action 的函数。我们知道用户下了什么命令,并根据那个命令从 dispatch list 取出对影的函数。我们用剩下的命令行引数调用哪些函数而得到一些作相对应事情的 I/O action。然后便执行那些 I/O action。</p>
<p>总结我们的程序:我们做了一个 dispatch association,将指令对应到一些会接受命令行参数并回传 I/O action 的函数。我们知道用户下了什么命令,并根据那个命令从 dispatch list 取出对影的函数。我们用剩下的命令行参数调用哪些函数而得到一些作相对应事情的 I/O action。然后便执行那些 I/O action。</p>

<p>在其他编程语言,我们可能会用一个大的 switch case 来实作,但使用高阶函数让我们可以要 dispatch list 给我们要的函数,并要那些函数给我们适当的 I/O action。</p>

Expand Down Expand Up @@ -1403,7 +1403,7 @@ copyFile source dest = do<br>
B.writeFile dest contents<br>
}}</p>

<p>我们写了自己的函数,他接受两个 <code>FilePath</code>(记住 <code>FilePath</code> 不过是 <code>String</code> 的同义词。)并回传一个 I/O action,他会用 bytestring 拷贝第一个文件至另一个。在 <code>main</code> 函数中,我们做的只是拿到命令行引数然后调用那个函数来拿到一个 I/O action。</p>
<p>我们写了自己的函数,他接受两个 <code>FilePath</code>(记住 <code>FilePath</code> 不过是 <code>String</code> 的同义词。)并回传一个 I/O action,他会用 bytestring 拷贝第一个文件至另一个。在 <code>main</code> 函数中,我们做的只是拿到命令行参数然后调用那个函数来拿到一个 I/O action。</p>

<p>{{
$ runhaskell bytestringcopy.hs something.txt ../../something.txt<br>
Expand Down Expand Up @@ -1438,7 +1438,7 @@ ghci&gt; head []<br>

<p>先前我们谈过为什么在 I/O 部份的程序要越少越好。程序的逻辑部份尽量都放在 pure 的部份,因为 pure 的特性就是他们的结果只会根据函数的参数不同而改变。当思考 pure function 的时候,你只需要考虑他回传什么,因为除此之外他不会有任何副作用。这会让事情简单许多。尽管 I/O 的部份是难以避免的(像是打开文件之类),但最好是把 I/O 部份降到最低。Pure functions 缺省是 lazy,那代表我们不知道他什么时候会被 evaluate,不过我们也不该知道。然而,一旦 pure functions 需要丢出 Exception,他们何时被 evaluate 就很重要了。那是因为我们只有在 I/O 的部份才能接到 Exception。这很糟糕,因为我们说过希望 I/O 的部份越少越好。但如果我们不接 Exception,我们的程序就会当掉。这问题有解决办法吗?答案是不要在 pure code 里面使用 Exception。利用 Haskell 的型态系统,尽量使用 <code>Either</code> 或 <code>Maybe</code> 之类的型态来表示可能失败的计算。</p>

<p>这也是为什么我们要来看看怎么使用 I/O Excetion。I/O Exception 是当我们在 <code>main</code> 里面跟外界沟通失败而丢出的 Exception。例如我们尝试打开一个文件,结果发现他已经被删掉或是其他状况。来看看一个尝试打开命令行引数所指定文件名称,并计算里面有多少行的程序。</p>
<p>这也是为什么我们要来看看怎么使用 I/O Excetion。I/O Exception 是当我们在 <code>main</code> 里面跟外界沟通失败而丢出的 Exception。例如我们尝试打开一个文件,结果发现他已经被删掉或是其他状况。来看看一个尝试打开命令行参数所指定文件名称,并计算里面有多少行的程序。</p>

<p>{{
import System.Environment<br>
Expand Down
2 changes: 1 addition & 1 deletion zh-cn/ch01/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

![](../../.gitbook/assets/fx%20%281%29.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\)\_” \(译注:这就跟数学上函数的使用一样\)。如此一来编译器就可以理解程序的行为,你也很容易就能验证一个函数的正确性,继而可以将一些简单的函数组合成更复杂的函数。
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\)\_” \(译注:这就跟数学上函数的使用一样\)。如此一来编译器就可以理解程序的行为,你也很容易就能验证一个函数的正确性,继而可以将一些简单的函数组合成更复杂的函数。

![](../../.gitbook/assets/lazy%20%281%29.png)

Expand Down
Loading