From 488e6c6e5438b230eeec5178f8ae81acfbbdfbc7 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 10:39:16 +0800 Subject: [PATCH 01/23] update errata --- appendix/appendix-a-errata.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/appendix/appendix-a-errata.md b/appendix/appendix-a-errata.md index 380c923..a355636 100644 --- a/appendix/appendix-a-errata.md +++ b/appendix/appendix-a-errata.md @@ -6,15 +6,17 @@ `rand.Seed(time.Now().UTC().UnixNano())` -**p.15, ¶2:** For "inner loop", read "outer loop". (Thanks to Ralph Corderoy, 2015-11-28.) +**p.15, ¶2:** For "inner loop", read "outer loop". (Thanks to Ralph Corderoy, 2015-11-28. Corrected in the third printing.) -**p.19, ¶2:** For "Go's libraries makes", read "Go's library makes". (Thanks to Victor Farazdagi, 2015-11-30.) +**p.19, ¶2:** For "Go's libraries makes", read "Go's library makes". (Thanks to Victor Farazdagi, 2015-11-30. Corrected in the third printing.) **p.40, ¶4:** For "value of the underlying type", read "value of an unnamed type with the same underlying type". (Thanks to Carlos Romero Brox, 2015-12-19.) -**p.40, ¶1:** The paragraph should end with a period, not a comma. (Thanks to Victor Farazdagi, 2015-11-30.) +**p.40, ¶1:** The paragraph should end with a period, not a comma. (Thanks to Victor Farazdagi, 2015-11-30. Corrected in the third printing.) -**p.43, ¶3:** Import declarations are explained in §10.4, not §10.3. (Thanks to Peter Jurgensen, 2015-11-21.) +**p.43, ¶3:** Import declarations are explained in §10.4, not §10.3. (Thanks to Peter Jurgensen, 2015-11-21. Corrected in the third printing.) + +**p.48:** `f.ReadByte()` serves as an example of a reference to f, but `*os.File` has no such method. For "ReadByte", read "Stat", four times. (Thanks to Peter Olsen, 2016-01-06. Corrected in the third printing.) **p.52, ¶2:** for "an synonym", read "a synonym", twice. (Corrected in the second printing.) @@ -38,19 +40,19 @@ **p.76:** the comment `// "time.Duration 5ms0s` should have a closing double-quotation mark. (Corrected in the second printing.) **p.79, ¶4:** "When an untyped constant is assigned to a variable, as in the first statement below, or -appears on the right-hand side of a variable declaration with an explicit type, as in the other three statements, ..." has it backwards: the first statement is a declaration; the other three are assignments. (Thanks to Yoshiki Shibata, 2015-11-09.) +appears on the right-hand side of a variable declaration with an explicit type, as in the other three statements, ..." has it backwards: the first statement is a declaration; the other three are assignments. (Thanks to Yoshiki Shibata, 2015-11-09. Corrected in the third printing.) -**p.132, code display following ¶3:** the final comment should read: `// compile error: can't assign func(int, int) int to func(int) int` (Thanks to Toni Suter, 2015-11-21.) +**p.132, code display following ¶3:** the final comment should read: `// compile error: can't assign func(int, int) int to func(int) int` (Thanks to Toni Suter, 2015-11-21. Corrected in the third printing.) -**p.166, ¶2:** for "way", read "a way". +**p.166, ¶2:** for "way", read "a way". (Corrected in the third printing.) + +**p.200, TestEval function:** the format string in the final call to t.Errorf should format test.env with %v, not %s. (Thanks to Mitsuteru Sawa, 2015-12-07. Corrected in the third printing.) **p.222. Exercise 8.1:** The port numbers for `London` and `Tokyo` should be swapped in the final command to match the earlier commands. (Thanks to Kiyoshi Kamishima, 2016-01-08.) **p.288, code display following ¶4:** In the import declaration, for `"database/mysql"`, read `"database/sql"`. (Thanks to Jose Colon Rodriguez, 2016-01-09.) -**p.200, TestEval function:** the format string in the final call to t.Errorf should format test.env with %v, not %s. (Thanks to Mitsuteru Sawa, 2015-12-07.) - **p.347, Exercise 12.8:** for "like json.Marshal", read "like json.Unmarshal". (Thanks to @chai2010, 2016-01-01.) -**p.362:** the `gopl.io/ch13/bzip` program does not comply with the [proposed rules for passing pointers between Go and C code](https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) because the C function `bz2compress` temporarily stores a Go pointer (in) into the Go heap (the `bz_stream` variable). The `bz_stream` variable should be allocated, and explicitly freed after the call to `BZ2_bzCompressEnd`, by C functions. (Thanks to Joe Tsai, 2015-11-18.) +**p.362:** the `gopl.io/ch13/bzip` program does not comply with the [proposed rules for passing pointers between Go and C code](https://github.com/golang/proposal/blob/master/design/12416-cgo-pointers.md) because the C function `bz2compress` temporarily stores a Go pointer (in) into the Go heap (the `bz_stream` variable). The `bz_stream` variable should be allocated, and explicitly freed after the call to `BZ2_bzCompressEnd`, by C functions. (Thanks to Joe Tsai, 2015-11-18. Corrected in the third printing.) From d184cf41d8969acc4c34a131b018f35b6fd6174c Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 10:39:43 +0800 Subject: [PATCH 02/23] appendix: add translations --- SUMMARY-github.md | 1 + SUMMARY.md | 1 + appendix/appendix-d-translations.md | 17 +++++++++++++++++ 3 files changed, 19 insertions(+) create mode 100644 appendix/appendix-d-translations.md diff --git a/SUMMARY-github.md b/SUMMARY-github.md index ad27548..1766a94 100644 --- a/SUMMARY-github.md +++ b/SUMMARY-github.md @@ -171,3 +171,4 @@ * [附録A:原文勘誤](appendix/appendix-a-errata.md) * [附録B:作者譯者](appendix/appendix-b-author.md) * [附録C:譯文授權](appendix/appendix-c-cpoyright.md) + * [附録D:其它語言](appendix/appendix-d-translations.md) diff --git a/SUMMARY.md b/SUMMARY.md index d2d1eca..9324f9b 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -126,3 +126,4 @@ * [附録A:原文勘誤](appendix/appendix-a-errata.md) * [附録B:作者譯者](appendix/appendix-b-author.md) * [附録C:譯文授權](appendix/appendix-c-cpoyright.md) + * [附録D:其它語言](appendix/appendix-d-translations.md) diff --git a/appendix/appendix-d-translations.md b/appendix/appendix-d-translations.md new file mode 100644 index 0000000..c2a9cbf --- /dev/null +++ b/appendix/appendix-d-translations.md @@ -0,0 +1,17 @@ +## 附録D:其它語言 + +下表是 [The Go Programming Language](http://www.gopl.io/) 其它語言版本: + +語言 | 鏈接 | 時間 | 譯者 | ISBN +---- | ---- | ---- | ---- | ---- +中文 | [《Go語言聖經》][gopl-zh] | 2016/2/1 | chai2010, CrazySssst, foreversmart, Xargin | ? +韓語 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ? +俄語 | [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ? +波蘭語 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ? | +日語 | [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ? +葡萄牙語 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ? +中文簡體 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ? +中文繁體 | [Gotop Information (Taiwan)](http://www.gotop.com.tw/) | 2017 | ? | ? + + +[gopl-zh]: http://golang-china.github.io/gopl-zh/ "《Go語言聖經》" From 4253c608b9a79250423c5c69f2ed647e78a09493 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 11:05:22 +0800 Subject: [PATCH 03/23] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20=E8=AD=AF=E8=80=85?= =?UTF-8?q?=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- preface.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/preface.md b/preface.md index 051bba3..9677289 100644 --- a/preface.md +++ b/preface.md @@ -29,13 +29,13 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本 從早期提交日誌中也可以看出,Go語言是從[Ken Thompson](http://genius.cat-v.org/ken-thompson/)發明的B語言、[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)發明的C語言逐步演化過來的,是C語言家族的成員,因此很多人將Go語言稱爲21世紀的C語言。縱觀這幾年來的發展趨勢,Go語言已經成爲雲計算、雲存儲時代最重要的基礎編程語言。 -在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](The C Programming Language)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。同時,大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。 +在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](The C Programming Language)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。 同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必鬚進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化,充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題,希望大家能先用自己的方式嚐試完成習題,然後再參考官方給出的解決方案。 -該書英文版約從2015年10月開始公開發售,同時同步發售的還有日文版本。不過比較可惜的是,中文版併沒有在同步發售之列,甚至連中文版是否會引進、是由哪個出版社引進、卽使引進將由何人來翻譯、何時能出版都成了一個祕密。中国的Go語言社區是全球最大的Go語言社區,我們從一開始就始終緊跟着Go語言的發展腳步。我們應該也完全有能力以中国Go語言社區的力量同步完成Go語言聖經中文版的翻譯工作。與此同時,国內有很多Go語言愛好者也在積極關註該書(本人也在第一時間購買了紙質版本,[亞馬遜價格314人民幣](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/))。爲了Go語言的學習和交流,大家決定合作免費翻譯該書。 +該書英文版約從2015年10月開始公開發售,其中日文版本最早參與翻譯和審校(參考致謝部分)。在2015年10月,我們併不知道中文版是否會及時引進、將由哪家出版社引進、引進將由何人來翻譯、何時能出版,這些信息都成了一個祕密。中国的Go語言社區是全球最大的Go語言社區,我們從一開始就始終緊跟着Go語言的發展腳步。我們應該也完全有能力以中国Go語言社區的力量同步完成Go語言聖經中文版的翻譯工作。與此同時,国內有很多Go語言愛好者也在積極關註該書(本人也在第一時間購買了紙質版本,[亞馬遜價格314人民幣](http://www.amazon.cn/The-Go-Programming-Language-Donovan-Alan-A-A/dp/0134190440/))。爲了Go語言的學習和交流,大家決定合作免費翻譯該書。 -翻譯工作從2015年11月20日前後開始,到2016年1月底初步完成,前後歷時約2個月時間。其中,[chai2010](https://github.com/chai2010)翻譯了前言、第2~4章、第10~13章,[Xargin](https://github.com/cch123)翻譯了第1章、第6章、第8~9章,[CrazySssst](https://github.com/CrazySssst)翻譯了第5章,[foreversmart](https://github.com/foreversmart)翻譯了第7章,大家共同參與了基本的校驗工作,還有其他一些朋友提供了積極的反饋建議。如果大家還有任何問題或建議,可以直接到中文版項目頁面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果發現英文版原文在[勘誤](http://www.gopl.io/errata.html)中未提到的任何錯誤,可以直接去[英文版項目](https://github.com/adonovan/gopl.io/)提交。 +翻譯工作從2015年11月20日前後開始,到2016年1月底初步完成,前後歷時約2個月時間(在其它語言版本中,全球第一個完成翻譯的,基本做到和原版同步)。其中,[chai2010](https://github.com/chai2010)翻譯了前言、第2~4章、第10~13章,[Xargin](https://github.com/cch123)翻譯了第1章、第6章、第8~9章,[CrazySssst](https://github.com/CrazySssst)翻譯了第5章,[foreversmart](https://github.com/foreversmart)翻譯了第7章,大家共同參與了基本的校驗工作,還有其他一些朋友提供了積極的反饋建議。如果大家還有任何問題或建議,可以直接到中文版項目頁面提交[Issue](https://github.com/golang-china/gopl-zh/issues),如果發現英文版原文在[勘誤](http://www.gopl.io/errata.html)中未提到的任何錯誤,可以直接去[英文版項目](https://github.com/adonovan/gopl.io/)提交。 最後,希望這本書能夠幫助大家用Go語言快樂地編程。 From a34f70a4e9d06b82c5efb6eeb7aa92666f0e67fd Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 11:05:50 +0800 Subject: [PATCH 04/23] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=91=E8=80=85?= =?UTF-8?q?=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- appendix/appendix-b-author.md | 2 +- appendix/appendix-d-translations.md | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/appendix/appendix-b-author.md b/appendix/appendix-b-author.md index 145439a..2823d2e 100644 --- a/appendix/appendix-b-author.md +++ b/appendix/appendix-b-author.md @@ -12,6 +12,6 @@ 中文譯者 | 章節 -------------------------------------- | ------------------------- `chai2010 ` | 前言/第2~4章/第10~13章 +`Xargin ` | 第1章/第6章/第8~9章 `CrazySssst` | 第5章 `foreversmart ` | 第7章 -`Xargin ` | 第1章/第6章/第8~9章 diff --git a/appendix/appendix-d-translations.md b/appendix/appendix-d-translations.md index c2a9cbf..d23880d 100644 --- a/appendix/appendix-d-translations.md +++ b/appendix/appendix-d-translations.md @@ -4,10 +4,10 @@ 語言 | 鏈接 | 時間 | 譯者 | ISBN ---- | ---- | ---- | ---- | ---- -中文 | [《Go語言聖經》][gopl-zh] | 2016/2/1 | chai2010, CrazySssst, foreversmart, Xargin | ? +中文 | [《Go語言聖經》][gopl-zh] | 2016/2/1 | [chai2010][chai2010], [Xargin][Xargin], [CrazySssst][CrazySssst], [foreversmart][foreversmart] | ? 韓語 | [Acorn Publishing (Korea)](http://www.acornpub.co.kr/) | 2016 | ? | ? 俄語 | [Williams Publishing (Russia)](http://www.williamspublishing.com/) | 2016 | ? | ? -波蘭語 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ? | +波蘭語 | [Helion (Poland)](http://helion.pl/) | 2016 | ? | ? 日語 | [Maruzen Publishing (Japan)](http://www.maruzen.co.jp/corp/en/services/publishing.html) | 2017 | Yoshiki Shibata | ? 葡萄牙語 | [Novatec Editora (Brazil)](http://novatec.com.br/) |2017 | ? | ? 中文簡體 | [Pearson Education Asia](http://www.pearsonapac.com/) |2017 | ? | ? @@ -15,3 +15,8 @@ [gopl-zh]: http://golang-china.github.io/gopl-zh/ "《Go語言聖經》" + +[chai2010]: https://github.com/chai2010 +[Xargin]: https://github.com/cch123 +[CrazySssst]: https://github.com/CrazySssst +[foreversmart]: https://github.com/foreversmart From 75e7d15b6bc4405b0229a1c21969eb72901bf8ea Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 14:20:40 +0800 Subject: [PATCH 05/23] fix link --- preface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preface.md b/preface.md index 9677289..c196f98 100644 --- a/preface.md +++ b/preface.md @@ -29,7 +29,7 @@ Go語言聖經 [《The Go Programming Language》](http://gopl.io) 中文版本 從早期提交日誌中也可以看出,Go語言是從[Ken Thompson](http://genius.cat-v.org/ken-thompson/)發明的B語言、[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)發明的C語言逐步演化過來的,是C語言家族的成員,因此很多人將Go語言稱爲21世紀的C語言。縱觀這幾年來的發展趨勢,Go語言已經成爲雲計算、雲存儲時代最重要的基礎編程語言。 -在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](The C Programming Language)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。 +在C語言發明之後約5年的時間之後(1978年),[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)和[Dennis M. Ritchie](http://genius.cat-v.org/dennis-ritchie/)合作編寫出版了C語言方面的經典敎材《[The C Programming Language](http://s3-us-west-2.amazonaws.com/belllabs-microsite-dritchie/cbook/index.html)》,該書被譽爲C語言程序員的聖經,作者也被大家親切地稱爲[K&R](https://en.wikipedia.org/wiki/K%26R)。同樣在Go語言正式發布(2009年)約5年之後(2014年開始寫作,2015年出版),由Go語言核心糰隊成員[Alan A. A. Donovan](https://github.com/adonovan)和[K&R](https://en.wikipedia.org/wiki/K%26R)中的[Brian W. Kernighan](http://www.cs.princeton.edu/~bwk/)合作編寫了Go語言方面的經典敎材《[The Go Programming Language](http://gopl.io)》。Go語言被譽爲21世紀的C語言,如果説[K&R](https://en.wikipedia.org/wiki/K%26R)所著的是聖經的舊約,那麽D&K所著的必將成爲聖經的新約。該書介紹了Go語言幾乎全部特性,併且隨着語言的深入層層遞進,對每個細節都解讀得非常細致,每一節內容都精綵不容錯過,是廣大Gopher的必讀書目。大部分Go語言核心糰隊的成員都參與了該書校對工作,因此該書的質量是可以完全放心的。 同時,單憑閲讀和學習其語法結構併不能眞正地掌握一門編程語言,必鬚進行足夠多的編程實踐——親自編寫一些程序併研究學習别人寫的程序。要從利用Go語言良好的特性使得程序模塊化,充分利用Go的標準函數庫以Go語言自己的風格來編寫程序。書中包含了上百個精心挑選的習題,希望大家能先用自己的方式嚐試完成習題,然後再參考官方給出的解決方案。 From 81a9ebf4de7668fbc5f252d191381f36c5a33982 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 16:46:43 +0800 Subject: [PATCH 06/23] update progress.md --- progress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progress.md b/progress.md index da134f4..341b75e 100644 --- a/progress.md +++ b/progress.md @@ -78,7 +78,7 @@ - [x] 8.9 Cancellation - [x] 8.10 Example: Chat Server - [x] Chapter 9: Concurrency with Shared Variables - - [ ] 9.1 Race Conditions + - [x] 9.1 Race Conditions - [ ] 9.2 Mutual Exclusion: sync.Mutex - [x] 9.3 Read/Write Mutexes: sync.RWMutex - [x] 9.4 Memory Synchronization From 49388170e2e235e903e9473eb11271e792410492 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Wed, 13 Jan 2016 16:56:39 +0800 Subject: [PATCH 07/23] make loop --- ch9/ch9-01.md | 116 +++++++++++++++++++++++++------------------------- 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/ch9/ch9-01.md b/ch9/ch9-01.md index 30027b4..e1b337e 100644 --- a/ch9/ch9-01.md +++ b/ch9/ch9-01.md @@ -1,18 +1,18 @@ ## 9.1. 競爭條件 -在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法判断的。当我们能够没有办法自信地确认一个事件是在另一个事件的前面或者后面发生的话,就说明x和y这两个事件是并发的。 +在一個線性(就是説隻有一個goroutine的)的程序中,程序的執行順序隻由程序的邏輯來決定。例如,我們有一段語句序列,第一個在第二個之前(廢話),以此類推。在有兩個或更多goroutine的程序中,每一個goroutine內的語句也是按照旣定的順序去執行的,但是一般情況下我們沒法去知道分别位於兩個goroutine的事件x和y的執行順序,x是在y之前還是之後還是同時發生是沒法判斷的。當我們能夠沒有辦法自信地確認一個事件是在另一個事件的前面或者後面發生的話,就説明x和y這兩個事件是併發的。 -考虑一下,一个函数在线性程序中可以正确地工作。如果在并发的情况下,这个函数依然可以正确地工作的话,那么我们就说这个函数是并发安全的,并发安全的函数不需要额外的同步工作。我们可以把这个概念概括为一个特定类型的一些方法和操作函数,如果这个类型是并发安全的话,那么所有它的访问方法和操作就都是并发安全的。 +考慮一下,一個函數在線性程序中可以正確地工作。如果在併發的情況下,這個函數依然可以正確地工作的話,那麽我們就説這個函數是併發安全的,併發安全的函數不需要額外的同步工作。我們可以把這個概念概括爲一個特定類型的一些方法和操作函數,如果這個類型是併發安全的話,那麽所有它的訪問方法和操作就都是併發安全的。 -在一个程序中有非并发安全的类型的情况下,我们依然可以使这个程序并发安全。确实,并发安全的类型是例外,而不是规则,所以只有当文档中明确地说明了其是并发安全的情况下,你才可以并发地去访问它。我们会避免并发访问大多数的类型,无论是将变量局限在单一的一个goroutine内还是用互斥条件维持更高级别的不变性都是为了这个目的。我们会在本章中说明这些术语。 +在一個程序中有非併發安全的類型的情況下,我們依然可以使這個程序併發安全。確實,併發安全的類型是例外,而不是規則,所以隻有當文檔中明確地説明了其是併發安全的情況下,你才可以併發地去訪問它。我們會避免併發訪問大多數的類型,無論是將變量局限在單一的一個goroutine內還是用互斥條件維持更高級别的不變性都是爲了這個目的。我們會在本章中説明這些術語。 -相反,导出包级别的函数一般情况下都是并发安全的。由于package级的变量没法被限制在单一的gorouine,所以修改这些变量“必须”使用互斥条件。 +相反,導出包級别的函數一般情況下都是併發安全的。由於package級的變量沒法被限製在單一的gorouine,所以脩改這些變量“必鬚”使用互斥條件。 -一个函数在并发调用时没法工作的原因太多了,比如死锁(deadlock)、活锁(livelock)和饿死(resource starvation)。我们没有空去讨论所有的问题,这里我们只聚焦在竞争条件上。 +一個函數在併發調用時沒法工作的原因太多了,比如死鎖(deadlock)、活鎖(livelock)和餓死(resource starvation)。我們沒有空去討論所有的問題,這里我們隻聚焦在競爭條件上。 -竞争条件指的是程序在多个goroutine交叉执行操作时,没有给出正确的结果。竞争条件是很恶劣的一种场景,因为这种问题会一直潜伏在你的程序里,然后在非常少见的时候蹦出来,或许只是会在很大的负载时才会发生,又或许是会在使用了某一个编译器、某一种平台或者某一种架构的时候才会出现。这些使得竞争条件带来的问题非常难以复现而且难以分析诊断。 +競爭條件指的是程序在多個goroutine交叉執行操作時,沒有給出正確的結果。競爭條件是很惡劣的一種場景,因爲這種問題會一直潛伏在你的程序里,然後在非常少見的時候蹦出來,或許隻是會在很大的負載時才會發生,又或許是會在使用了某一個編譯器、某一種平台或者某一種架構的時候才會出現。這些使得競爭條件帶來的問題非常難以複現而且難以分析診斷。 -传统上经常用经济损失来为竞争条件做比喻,所以我们来看一个简单的银行账户程序。 +傳統上經常用經濟損失來爲競爭條件做比喻,所以我們來看一個簡單的銀行賬戶程序。 ```go // Package bank implements a bank with only one account. @@ -22,22 +22,22 @@ func Deposit(amount int) { balance = balance + amount } func Balance() int { return balance } ``` -(当然我们也可以把Deposit存款函数写成balance += amount,这种形式也是等价的,不过长一些的形式解释起来更方便一些。) +(當然我們也可以把Deposit存款函數寫成balance += amount,這種形式也是等價的,不過長一些的形式解釋起來更方便一些。) -对于这个具体的程序而言,我们可以瞅一眼各种存款和查余额的顺序调用,都能给出正确的结果。也就是说,Balance函数会给出之前的所有存入的额度之和。然而,当我们并发地而不是顺序地调用这些函数的话,Balance就再也没办法保证结果正确了。考虑一下下面的两个goroutine,其代表了一个银行联合账户的两笔交易: +對於這個具體的程序而言,我們可以瞅一眼各種存款和査餘額的順序調用,都能給出正確的結果。也就是説,Balance函數會給出之前的所有存入的額度之和。然而,當我們併發地而不是順序地調用這些函數的話,Balance就再也沒辦法保證結果正確了。考慮一下下面的兩個goroutine,其代表了一個銀行聯合賬戶的兩筆交易: ```go // Alice: go func() { - bank.Deposit(200) // A1 - fmt.Println("=", bank.Balance()) // A2 + bank.Deposit(200) // A1 + fmt.Println("=", bank.Balance()) // A2 }() // Bob: go bank.Deposit(100) // B ``` -Alice存了$200,然后检查她的余额,同时Bob存了$100。因为A1和A2是和B并发执行的,我们没法预测他们发生的先后顺序。直观地来看的话,我们会认为其执行顺序只有三种可能性:“Alice先”,“Bob先”以及“Alice/Bob/Alice”交错执行。下面的表格会展示经过每一步骤后balance变量的值。引号里的字符串表示余额单。 +Alice存了$200,然後檢査她的餘額,同時Bob存了$100。因爲A1和A2是和B併發執行的,我們沒法預測他們發生的先後順序。直觀地來看的話,我們會認爲其執行順序隻有三種可能性:“Alice先”,“Bob先”以及“Alice/Bob/Alice”交錯執行。下面的表格會展示經過每一步驟後balance變量的值。引號里的字符串表示餘額單。 ``` Alice first Bob first Alice/Bob/Alice @@ -47,9 +47,9 @@ A2 "=200" A1 300 B 300 B 300 A2 "=300" A2 "=300" ``` -所有情况下最终的余额都是$300。唯一的变数是Alice的余额单是否包含了Bob交易,不过无论怎么着客户都不会在意。 +所有情況下最終的餘額都是$300。唯一的變數是Alice的餘額單是否包含了Bob交易,不過無論怎麽着客戶都不會在意。 -但是事实是上面的直觉推断是错误的。第四种可能的结果是事实存在的,这种情况下Bob的存款会在Alice存款操作中间,在余额被读到(balance + amount)之后,在余额被更新之前(balance = ...),这样会导致Bob的交易丢失。而这是因为Alice的存款操作A1实际上是两个操作的一个序列,读取然后写;可以称之为A1r和A1w。下面是交叉时产生的问题: +但是事實是上面的直覺推斷是錯誤的。第四種可能的結果是事實存在的,這種情況下Bob的存款會在Alice存款操作中間,在餘額被讀到(balance + amount)之後,在餘額被更新之前(balance = ...),這樣會導致Bob的交易丟失。而這是因爲Alice的存款操作A1實際上是兩個操作的一個序列,讀取然後寫;可以稱之爲A1r和A1w。下面是交叉時産生的問題: ``` Data race @@ -60,11 +60,11 @@ A1w 200 balance = ... A2 "= 200" ``` -在A1r之后,balance + amount会被计算为200,所以这是A1w会写入的值,并不受其它存款操作的干预。最终的余额是$200。银行的账户上的资产比Bob实际的资产多了$100。(译注:因为丢失了Bob的存款操作,所以其实是说Bob的钱丢了) +在A1r之後,balance + amount會被計算爲200,所以這是A1w會寫入的值,併不受其它存款操作的榦預。最終的餘額是$200。銀行的賬戶上的資産比Bob實際的資産多了$100。(譯註:因爲丟失了Bob的存款操作,所以其實是説Bob的錢丟了) -这个程序包含了一个特定的竞争条件,叫作数据竞争。无论任何时候,只要有两个goroutine并发访问同一变量,且至少其中的一个是写操作的时候就会发生数据竞争。 +這個程序包含了一個特定的競爭條件,叫作數據競爭。無論任何時候,隻要有兩個goroutine併發訪問同一變量,且至少其中的一個是寫操作的時候就會發生數據競爭。 -如果数据竞争的对象是一个比一个机器字(译注:32位机器上一个字=4个字节)更大的类型时,事情就变得更麻烦了,比如interface,string或者slice类型都是如此。下面的代码会并发地更新两个不同长度的slice: +如果數據競爭的對象是一個比一個機器字(譯註:32位機器上一個字=4個字節)更大的類型時,事情就變得更麻煩了,比如interface,string或者slice類型都是如此。下面的代碼會併發地更新兩個不同長度的slice: ```go var x []int @@ -73,13 +73,13 @@ go func() { x = make([]int, 1000000) }() x[999999] = 1 // NOTE: undefined behavior; memory corruption possible! ``` -最后一个语句中的x的值是未定义的;其可能是nil,或者也可能是一个长度为10的slice,也可能是一个程度为1,000,000的slice。但是回忆一下slice的三个组成部分:指针(pointer)、长度(length)和容量(capacity)。如果指针是从第一个make调用来,而长度从第二个make来,x就变成了一个混合体,一个自称长度为1,000,000但实际上内部只有10个元素的slice。这样导致的结果是存储999,999元素的位置会碰撞一个遥远的内存位置,这种情况下难以对值进行预测,而且定位和debug也会变成噩梦。这种语义雷区被称为未定义行为,对C程序员来说应该很熟悉;幸运的是在Go语言里造成的麻烦要比C里小得多。 +最後一個語句中的x的值是未定義的;其可能是nil,或者也可能是一個長度爲10的slice,也可能是一個程度爲1,000,000的slice。但是迴憶一下slice的三個組成部分:指針(pointer)、長度(length)和容量(capacity)。如果指針是從第一個make調用來,而長度從第二個make來,x就變成了一個混合體,一個自稱長度爲1,000,000但實際上內部隻有10個元素的slice。這樣導致的結果是存儲999,999元素的位置會碰撞一個遙遠的內存位置,這種情況下難以對值進行預測,而且定位和debug也會變成噩夢。這種語義雷區被稱爲未定義行爲,對C程序員來説應該很熟悉;幸運的是在Go語言里造成的麻煩要比C里小得多。 -尽管并发程序的概念让我们知道并发并不是简单的语句交叉执行。我们将会在9.4节中看到,数据竞争可能会有奇怪的结果。许多程序员,甚至一些非常聪明的人也还是会偶尔提出一些理由来允许数据竞争,比如:“互斥条件代价太高”,“这个逻辑只是用来做logging”,“我不介意丢失一些消息”等等。因为在他们的编译器或者平台上很少遇到问题,可能给了他们错误的信心。一个好的经验法则是根本就没有什么所谓的良性数据竞争。所以我们一定要避免数据竞争,那么在我们的程序中要如何做到呢? +盡管併發程序的概念讓我們知道併發併不是簡單的語句交叉執行。我們將會在9.4節中看到,數據競爭可能會有奇怪的結果。許多程序員,甚至一些非常聰明的人也還是會偶爾提出一些理由來允許數據競爭,比如:“互斥條件代價太高”,“這個邏輯隻是用來做logging”,“我不介意丟失一些消息”等等。因爲在他們的編譯器或者平台上很少遇到問題,可能給了他們錯誤的信心。一個好的經驗法則是根本就沒有什麽所謂的良性數據競爭。所以我們一定要避免數據競爭,那麽在我們的程序中要如何做到呢? -我们来重复一下数据竞争的定义,因为实在太重要了:数据竞争会在两个以上的goroutine并发访问相同的变量且至少其中一个为写操作时发生。根据上述定义,有三种方式可以避免数据竞争: +我們來重複一下數據競爭的定義,因爲實在太重要了:數據競爭會在兩個以上的goroutine併發訪問相同的變量且至少其中一個爲寫操作時發生。根據上述定義,有三種方式可以避免數據競爭: -第一种方法是不要去写变量。考虑一下下面的map,会被“懒”填充,也就是说在每个key被第一次请求到的时候才会去填值。如果Icon是被顺序调用的话,这个程序会工作很正常,但如果Icon被并发调用,那么对于这个map来说就会存在数据竞争。 +第一種方法是不要去寫變量。考慮一下下面的map,會被“懶”填充,也就是説在每個key被第一次請求到的時候才會去填值。如果Icon是被順序調用的話,這個程序會工作很正常,但如果Icon被併發調用,那麽對於這個map來説就會存在數據競爭。 ```go var icons = make(map[string]image.Image) @@ -87,36 +87,36 @@ func loadIcon(name string) image.Image // NOTE: not concurrency-safe! func Icon(name string) image.Image { - icon, ok := icons[name] - if !ok { - icon = loadIcon(name) - icons[name] = icon - } - return icon + icon, ok := icons[name] + if !ok { + icon = loadIcon(name) + icons[name] = icon + } + return icon } ``` -反之,如果我们在创建goroutine之前的初始化阶段,就初始化了map中的所有条目并且再也不去修改它们,那么任意数量的goroutine并发访问Icon都是安全的,因为每一个goroutine都只是去读取而已。 +反之,如果我們在創建goroutine之前的初始化階段,就初始化了map中的所有條目併且再也不去脩改它們,那麽任意數量的goroutine併發訪問Icon都是安全的,因爲每一個goroutine都隻是去讀取而已。 ```go var icons = map[string]image.Image{ - "spades.png": loadIcon("spades.png"), - "hearts.png": loadIcon("hearts.png"), - "diamonds.png": loadIcon("diamonds.png"), - "clubs.png": loadIcon("clubs.png"), + "spades.png": loadIcon("spades.png"), + "hearts.png": loadIcon("hearts.png"), + "diamonds.png": loadIcon("diamonds.png"), + "clubs.png": loadIcon("clubs.png"), } // Concurrency-safe. func Icon(name string) image.Image { return icons[name] } ``` -上面的例子里icons变量在包初始化阶段就已经被赋值了,包的初始化是在程序main函数开始执行之前就完成了的。只要初始化完成了,icons就再也不会修改的或者不变量是本来就并发安全的,这种变量不需要进行同步。不过显然我们没法用这种方法,因为update操作是必要的操作,尤其对于银行账户来说。 +上面的例子里icons變量在包初始化階段就已經被賦值了,包的初始化是在程序main函數開始執行之前就完成了的。隻要初始化完成了,icons就再也不會脩改的或者不變量是本來就併發安全的,這種變量不需要進行同步。不過顯然我們沒法用這種方法,因爲update操作是必要的操作,尤其對於銀行賬戶來説。 -第二种避免数据竞争的方法是,避免从多个goroutine访问变量。这也是前一章中大多数程序所采用的方法。例如前面的并发web爬虫(§8.6)的main goroutine是唯一一个能够访问seen map的goroutine,而聊天服务器(§8.10)中的broadcaster goroutine是唯一一个能够访问clients map的goroutine。这些变量都被限定在了一个单独的goroutine中。 +第二種避免數據競爭的方法是,避免從多個goroutine訪問變量。這也是前一章中大多數程序所采用的方法。例如前面的併發web爬蟲(§8.6)的main goroutine是唯一一個能夠訪問seen map的goroutine,而聊天服務器(§8.10)中的broadcaster goroutine是唯一一個能夠訪問clients map的goroutine。這些變量都被限定在了一個單獨的goroutine中。 -由于其它的goroutine不能够直接访问变量,它们只能使用一个channel来发送给指定的goroutine请求来查询更新变量。这也就是Go的口头禅“不要使用共享数据来通信;使用通信来共享数据”。一个提供对一个指定的变量通过cahnnel来请求的goroutine叫做这个变量的监控(monitor)goroutine。例如broadcaster goroutine会监控(monitor)clients map的全部访问。 +由於其它的goroutine不能夠直接訪問變量,它們隻能使用一個channel來發送給指定的goroutine請求來査詢更新變量。這也就是Go的口頭禪“不要使用共享數據來通信;使用通信來共享數據”。一個提供對一個指定的變量通過cahnnel來請求的goroutine叫做這個變量的監控(monitor)goroutine。例如broadcaster goroutine會監控(monitor)clients map的全部訪問。 -下面是一个重写了的银行的例子,这个例子中balance变量被限制在了monitor goroutine中,名为teller: +下面是一個重寫了的銀行的例子,這個例子中balance變量被限製在了monitor goroutine中,名爲teller: ```go gopl.io/ch9/bank1 @@ -130,45 +130,45 @@ func Deposit(amount int) { deposits <- amount } func Balance() int { return <-balances } func teller() { - var balance int // balance is confined to teller goroutine - for { - select { - case amount := <-deposits: - balance += amount - case balances <- balance: - } - } + var balance int // balance is confined to teller goroutine + for { + select { + case amount := <-deposits: + balance += amount + case balances <- balance: + } + } } func init() { - go teller() // start the monitor goroutine + go teller() // start the monitor goroutine } ``` -即使当一个变量无法在其整个生命周期内被绑定到一个独立的goroutine,绑定依然是并发问题的一个解决方案。例如在一条流水线上的goroutine之间共享变量是很普遍的行为,在这两者间会通过channel来传输地址信息。如果流水线的每一个阶段都能够避免在将变量传送到下一阶段时再去访问它,那么对这个变量的所有访问就是线性的。其效果是变量会被绑定到流水线的一个阶段,传送完之后被绑定到下一个,以此类推。这种规则有时被称为串行绑定。 +卽使當一個變量無法在其整個生命週期內被綁定到一個獨立的goroutine,綁定依然是併發問題的一個解決方案。例如在一條流水線上的goroutine之間共享變量是很普遍的行爲,在這兩者間會通過channel來傳輸地址信息。如果流水線的每一個階段都能夠避免在將變量傳送到下一階段時再去訪問它,那麽對這個變量的所有訪問就是線性的。其效果是變量會被綁定到流水線的一個階段,傳送完之後被綁定到下一個,以此類推。這種規則有時被稱爲串行綁定。 -下面的例子中,Cakes会被严格地顺序访问,先是baker gorouine,然后是icer gorouine: +下面的例子中,Cakes會被嚴格地順序訪問,先是baker gorouine,然後是icer gorouine: ```go type Cake struct{ state string } func baker(cooked chan<- *Cake) { - for { - cake := new(Cake) - cake.state = "cooked" - cooked <- cake // baker never touches this cake again - } + for { + cake := new(Cake) + cake.state = "cooked" + cooked <- cake // baker never touches this cake again + } } func icer(iced chan<- *Cake, cooked <-chan *Cake) { - for cake := range cooked { - cake.state = "iced" - iced <- cake // icer never touches this cake again - } + for cake := range cooked { + cake.state = "iced" + iced <- cake // icer never touches this cake again + } } ``` -第三种避免数据竞争的方法是允许很多goroutine去访问变量,但是在同一个时刻最多只有一个goroutine在访问。这种方式被称为“互斥”,在下一节来讨论这个主题。 +第三種避免數據競爭的方法是允許很多goroutine去訪問變量,但是在同一個時刻最多隻有一個goroutine在訪問。這種方式被稱爲“互斥”,在下一節來討論這個主題。 -练习 9.1: 给gopl.io/ch9/bank1程序添加一个Withdraw(amount int)取款函数。其返回结果应该要表明事务是成功了还是因为没有足够资金失败了。这条消息会被发送给monitor的goroutine,且消息需要包含取款的额度和一个新的channel,这个新channel会被monitor goroutine来把boolean结果发回给Withdraw。 +練習 9.1: 給gopl.io/ch9/bank1程序添加一個Withdraw(amount int)取款函數。其返迴結果應該要表明事務是成功了還是因爲沒有足夠資金失敗了。這條消息會被發送給monitor的goroutine,且消息需要包含取款的額度和一個新的channel,這個新channel會被monitor goroutine來把boolean結果發迴給Withdraw。 From d144ab5b7526f67c98bd407e80646d9a3c247179 Mon Sep 17 00:00:00 2001 From: foreversmart Date: Wed, 13 Jan 2016 23:23:42 +0800 Subject: [PATCH 08/23] ch7-07 done --- ch7/ch7-07.md | 172 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 1 deletion(-) diff --git a/ch7/ch7-07.md b/ch7/ch7-07.md index 970aed3..5d38dad 100644 --- a/ch7/ch7-07.md +++ b/ch7/ch7-07.md @@ -1,3 +1,173 @@ ## 7.7. http.Handler接口 +在第一章中,我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中,我們會對那些基於http.Handler接口的服務器API做更進一步的學習: +```go +// net/http +package http -TODO +type Handler interface { + ServeHTTP(w ResponseWriter, r *Request) +} + +func ListenAndServe(address string, h Handler) error +``` +ListenAndServe函數需要一個例如“localhost:8000”的服務器地址,和一個所有請求都可以分派的Handler接口實例。它會一直運行,直到這個服務因爲一個錯誤而失敗(或者啟動失敗),它的返迴值一定是一個非空的錯誤。 + +想象一個電子商務網站,爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型,我們給這個類型一個ServeHttp方法,這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。 +```go +// gopl.io/ch7/http1 +func main() { + db := database{"shoes": 50, "socks": 5} + log.Fatal(http.ListenAndServe("localhost:8000", db)) +} + +type dollars float32 + +func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } + +type database map[string]dollars + +func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { + for item, price := range db { + fmt.Fprintf(w, "%s: %s\n", item, price) + } +} +``` +如果我們啟動這個服務, +``` +$ go build gopl.io/ch7/http1 +$ ./http1 & +``` +然後用1.5節中的獲取程序(如果你更喜歡可以使用web瀏覽器)來連接服務器,我們得到下面的輸出: +``` +$ go build gopl.io/ch1/fetch +$ ./fetch http://localhost:8000 +shoes: $50.00 +socks: $5.00 +``` +目前爲止,這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL,每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格,像這樣/price?item=socks來指定一個請求參數。 +```go +// gopl.io/ch7/http2 +func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { + switch req.URL.Path { + case "/list": + for item, price := range db { + fmt.Fprintf(w, "%s: %s\n", item, price) + } + case "/price": + item := req.URL.Query().Get("item") + price, ok := db[item] + if !ok { + w.WriteHeader(http.StatusNotFound) // 404 + fmt.Fprintf(w, "no such item: %q\n", item) + return + } + fmt.Fprintf(w, "%s\n", price) + default: + w.WriteHeader(http.StatusNotFound) // 404 + fmt.Fprintf(w, "no such page: %s\n", req.URL) + } +} +``` +現在handler基於URL的路徑部分(req.URL.Path)來決定執行什麽邏輯。如果這個handler不能識别這個路徑,它會通過調用w.WriteHeader(http.StatusNotFound)返迴客戶端一個HTTP錯誤;這個檢査應該在向w寫入任何值前完成。(順便提一下,http.ResponseWriter是另一個接口。它在io.Writer上增加了發送HTTP相應頭的方法。)等效地,我們可以使用實用的http.Error函數: +```go +msg := fmt.Sprintf("no such page: %s\n", req.URL) +http.Error(w, msg, http.StatusNotFound) // 404 +``` +/price的case會調用URL的Query方法來將HTTP請求參數解析爲一個map,或者更準確地説一個net/url包中url.Values(§6.2.1)類型的多重映射。然後找到第一個item參數併査找它的價格。如果這個貨品沒有找到會返迴一個錯誤。 + +這里是一個和新服務器會話的例子: +``` +$ go build gopl.io/ch7/http2 +$ go build gopl.io/ch1/fetch +$ ./http2 & +$ ./fetch http://localhost:8000/list +shoes: $50.00 +socks: $5.00 +$ ./fetch http://localhost:8000/price?item=socks +$5.00 +$ ./fetch http://localhost:8000/price?item=shoes +$50.00 +$ ./fetch http://localhost:8000/price?item=hat +no such item: "hat" +$ ./fetch http://localhost:8000/help +no such page: /help +``` +顯然我們可以繼續向ServeHTTP方法中添加case,但在一個實際的應用中,將每個case中的邏輯定義到一個分開的方法或函數中會很實用。此外,相近的URL可能需要相似的邏輯;例如幾個圖片文件可能有形如/images/\*.png的URL。因爲這些原因,net/http包提供了一個請求多路器ServeMux來簡化URL和handlers的聯繫。一個ServeMux將一批http.Handler聚集到一個單一的http.Handler中。再一次,我們可以看到滿足同一接口的不同類型是可替換的:web服務器將請求指派給任意的http.Handler +而不需要考慮它後面的具體類型。 + +對於更複雜的應用,一些ServeMux可以通過組合來處理更加錯綜複雜的路由需求。Go語言目前沒有一個權威的web框架,就像Ruby語言有Rails和python有Django。這併不是説這樣的框架不存在,而是Go語言標準庫中的構建模塊就已經非常靈活以至於這些框架都是不必要的。此外,盡管在一個項目早期使用框架是非常方便的,但是它們帶來額外的複雜度會使長期的維護更加睏難。 + +在下面的程序中,我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來,這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。 +```go +// gopl.io/ch7/http3 +func main() { + db := database{"shoes": 50, "socks": 5} + mux := http.NewServeMux() + mux.Handle("/list", http.HandlerFunc(db.list)) + mux.Handle("/price", http.HandlerFunc(db.price)) + log.Fatal(http.ListenAndServe("localhost:8000", mux)) +} + +type database map[string]dollars + +func (db database) list(w http.ResponseWriter, req *http.Request) { + for item, price := range db { + fmt.Fprintf(w, "%s: %s\n", item, price) + } +} + +func (db database) price(w http.ResponseWriter, req *http.Request) { + item := req.URL.Query().Get("item") + price, ok := db[item] + if !ok { + w.WriteHeader(http.StatusNotFound) // 404 + fmt.Fprintf(w, "no such item: %q\n", item) + return + } + fmt.Fprintf(w, "%s\n", price) +} +``` +讓我們關註這兩個註冊到handlers上的調用。第一個db.list是一個方法值 (§6.4),它是下面這個類型的值 +```go +func(w http.ResponseWriter, req *http.Request) +``` +也就是説db.list的調用會援引一個接收者是db的database.list方法。所以db.list是一個實現了handler類似行爲的函數,但是因爲它沒有方法,所以它不滿足http.Handler接口併且不能直接傳給mux.Handle。 + +語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用,因爲http.HandlerFunc是一個類型。它有如下的定義: +```go +// net/http +package http + +type HandlerFunc func(w ResponseWriter, r *Request) + +func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { + f(w, r) +} +``` +HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這是一個有實現了接口http.Handler方法的函數類型。ServeHTTP方法的行爲調用了它本身的函數。因此HandlerFunc是一個讓函數值滿足一個接口的適配器,這里函數和這個接口僅有的方法有相同的函數籤名。實際上,這個技巧讓一個單一的類型例如database以多種方式滿足http.Handler接口:一種通過它的list方法,一種通過它的price方法等等。 + +因爲handler通過這種方式註冊非常普遍,ServeMux有一個方便的HandleFunc方法,它幫我們簡化handler註冊代碼成這樣: +```go +// gopl.io/ch7/http3a +mux.HandleFunc("/list", db.list) +mux.HandleFunc("/price", db.price) +``` +從上面的代碼很容易看出應該怎麽構建一個程序,它有兩個不同的web服務器監聽不同的端口的,併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe(可能併行的)。但是在大多數程序中,一個web服務器就足夠了。此外,在一個應用程序的多個文件中定義HTTP handler也是非常典型的,如果它們必鬚全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。 + +所以爲了方便,net/http包提供了一個全局的ServeMux實例DefaultServerMux和包級别的http.Handle和http.HandleFunc函數。現在,爲了使用DefaultServeMux作爲服務器的主handler,我們不需要將它傳給ListenAndServe函數;nil值就可以工作。 + +然後服務器的主函數可以簡化成: +```go +// gopl.io/ch7/http4 +func main() { + db := database{"shoes": 50, "socks": 5} + http.HandleFunc("/list", db.list) + http.HandleFunc("/price", db.price) + log.Fatal(http.ListenAndServe("localhost:8000", nil)) +} +``` +最後,一個重要的提示:就像我們在1.7節中提到的,web服務器在一個新的協程中調用每一個handler,所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。 + +練習 7.11:增加額外的handler讓客服端可以創建,讀取,更新和刪除數據庫記録。例如,一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(註意:這個脩改會引入變量同時更新的問題) + +練習 7.12:脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。 From 6af9a836bfd913b9759a397507d5191083a6a732 Mon Sep 17 00:00:00 2001 From: james4e Date: Thu, 14 Jan 2016 07:10:11 +0800 Subject: [PATCH 09/23] Update ch11-02-2.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 106 line:以外 -> 意外 --- ch11/ch11-02-2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch11/ch11-02-2.md b/ch11/ch11-02-2.md index ede30d5..ed34fdd 100644 --- a/ch11/ch11-02-2.md +++ b/ch11/ch11-02-2.md @@ -103,6 +103,6 @@ FAIL gopl.io/ch11/echo 0.006s 錯誤信息描述了嚐試的操作(使用Go類似語法), 實際的行爲, 和期望的行爲. 通過這樣的錯誤信息, 你可以在檢視代碼之前就很容易定位錯誤的原因. -要註意的是在測試代碼中併沒有調用 log.Fatal 或 os.Exit, 因爲調用這類函數會導致程序提前退出; 調用這些函數的特權應該放在 main 函數中. 如果眞的有以外的事情導致函數發送 panic, 測試驅動應該嚐試 recover, 然後將當前測試當作失敗處理. 如果是可預期的錯誤, 例如非法的用戶輸入, 找不到文件, 或配置文件不當等應該通過返迴一個非空的 error 的方式處理. 幸運的是(上面的意外隻是一個插麴), 我們的 echo 示例是比較簡單的也沒有需要返迴非空error的情況. +要註意的是在測試代碼中併沒有調用 log.Fatal 或 os.Exit, 因爲調用這類函數會導致程序提前退出; 調用這些函數的特權應該放在 main 函數中. 如果眞的有意外的事情導致函數發送 panic, 測試驅動應該嚐試 recover, 然後將當前測試當作失敗處理. 如果是可預期的錯誤, 例如非法的用戶輸入, 找不到文件, 或配置文件不當等應該通過返迴一個非空的 error 的方式處理. 幸運的是(上面的意外隻是一個插麴), 我們的 echo 示例是比較簡單的也沒有需要返迴非空error的情況. From 5c34fbf4c897dd4c85e08b8439bb9611ac5509bb Mon Sep 17 00:00:00 2001 From: foreversmart Date: Thu, 14 Jan 2016 22:46:55 +0800 Subject: [PATCH 10/23] ch7-08 done --- ch7/ch7-08.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/ch7/ch7-08.md b/ch7/ch7-08.md index 603eda7..769f545 100644 --- a/ch7/ch7-08.md +++ b/ch7/ch7-08.md @@ -1,3 +1,62 @@ ## 7.8. error接口 +從本書的開始,我們就已經創建和使用過神祕的預定義error類型,而且沒有解釋它究竟是什麽。實際上它就是interface類型,這個類型有一個返迴錯誤信息的單一方法: +```go +type error interface { + Error() string +} +``` +創建一個error最簡單的方法就是調用errors.New函數,它會根據傳入的錯誤信息返迴一個新的error。整個errors包僅隻有4行: +```go +package errors -TODO +func New(text string) error { return &errorString{text} } + +type errorString struct { text string } + +func (e *errorString) Error() string { return e.text } +``` +承載errorString的類型是一個結構體而非一個字符串,這是爲了保護它表示的錯誤避免粗心(或有意)的更新。併且因爲是指針類型*errorString滿足error接口而非errorString類型,所以每個New函數的調用都分配了一個獨特的和其他錯誤不相同的實例。我們也不想要重要的error例如io.EOF和一個剛好有相同錯誤消息的error比較後相等。 +```go +fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false" +``` +調用errors.New函數是非常稀少的,因爲有一個方便的封裝函數fmt.Errorf,它還會處理字符串格式化。我們曾多次在第5章中用到它。 +```go +package fmt + +import "errors" + +func Errorf(format string, args ...interface{}) error { + return errors.New(Sprintf(format, args...)) +} +``` +雖然*errorString可能是最簡單的錯誤類型,但遠非隻有它一個。例如,syscall包提供了Go語言底層繫統調用API。在多個平台上,它定義一個實現error接口的數字類型Errno,併且在Unix平台上,Errno的Error方法會從一個字符串表中査找錯誤消息,如下面展示的這樣: +```go +package syscall + +type Errno uintptr // operating system error code + +var errors = [...]string{ + 1: "operation not permitted", // EPERM + 2: "no such file or directory", // ENOENT + 3: "no such process", // ESRCH + // ... +} + +func (e Errno) Error() string { + if 0 <= int(e) && int(e) < len(errors) { + return errors[e] + } + return fmt.Sprintf("errno %d", e) +} +``` +下面的語句創建了一個持有Errno值爲2的接口值,表示POSIX ENOENT狀況: +```go +var err error = syscall.Errno(2) +fmt.Println(err.Error()) // "no such file or directory" +fmt.Println(err) // "no such file or directory" +``` +err的值圖形化的呈現在圖7.6中。 + +![](../images/ch7-06.png) + +Errno是一個繫統調用錯誤的高效表示方式,它通過一個有限的幾何進行描述,併且它滿足標準的錯誤接口。我們會在第7.11節了解到其它滿足這個接口的類型。 From d3b6e9dcefb0295681c16884c4953ec8695c13d4 Mon Sep 17 00:00:00 2001 From: foreversmart Date: Thu, 14 Jan 2016 22:49:54 +0800 Subject: [PATCH 11/23] fix wrong word --- ch7/ch7-08.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ch7/ch7-08.md b/ch7/ch7-08.md index 769f545..5827e79 100644 --- a/ch7/ch7-08.md +++ b/ch7/ch7-08.md @@ -59,4 +59,4 @@ err的值圖形化的呈現在圖7.6中。 ![](../images/ch7-06.png) -Errno是一個繫統調用錯誤的高效表示方式,它通過一個有限的幾何進行描述,併且它滿足標準的錯誤接口。我們會在第7.11節了解到其它滿足這個接口的類型。 +Errno是一個繫統調用錯誤的高效表示方式,它通過一個有限的集合進行描述,併且它滿足標準的錯誤接口。我們會在第7.11節了解到其它滿足這個接口的類型。 From 525cfc579b8f1cbb3bbdf4292aedb4d8cbabf10f Mon Sep 17 00:00:00 2001 From: foreversmart Date: Sun, 17 Jan 2016 23:53:02 +0800 Subject: [PATCH 12/23] ch7-09 done --- ch7/ch7-09.md | 279 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 278 insertions(+), 1 deletion(-) diff --git a/ch7/ch7-09.md b/ch7/ch7-09.md index a24b28c..73de2df 100644 --- a/ch7/ch7-09.md +++ b/ch7/ch7-09.md @@ -1,3 +1,280 @@ ## 7.9. 示例: 表達式求值 +在本節中,我們會構建一個簡單算術表達式的求值器。我們將使用一個接口Expr來表示Go語言中任意的表達式。現在這個接口不需要有方法,但是我們後面會爲它增加一些。 +```go +// An Expr is an arithmetic expression. +type Expr interface{} +``` +我們的表達式語言由浮點數符號(小數點);二元操作符+,-,\*, 和/;一元操作符-x和+x;調用pow(x,y),sin(x),和sqrt(x)的函數;例如x和pi的變量;當然也有括號和標準的優先級運算符。所有的值都是float64類型。這下面是一些表達式的例子: +```go +sqrt(A / pi) +pow(x, 3) + pow(y, 3) +(F - 32) * 5 / 9 +``` +下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。(我們很快會知道爲什麽它可以被輸出。)literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式,這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用;我們限製它的fn字段隻能是pow,sin或者sqrt。 +```go +// gopl.io/ch7/eval +// A Var identifies a variable, e.g., x. +type Var string -TODO +// A literal is a numeric constant, e.g., 3.141. +type literal float64 + +// A unary represents a unary operator expression, e.g., -x. +type unary struct { + op rune // one of '+', '-' + x Expr +} + +// A binary represents a binary operator expression, e.g., x+y. +type binary struct { + op rune // one of '+', '-', '*', '/' + x, y Expr +} + +// A call represents a function call expression, e.g., sin(x). +type call struct { + fn string // one of "pow", "sin", "sqrt" + args []Expr +} +``` +爲了計算一個包含變量的表達式,我們需要一個environment變量將變量的名字映射成對應的值: +```go +type Env map[Var]float64 +``` +我們也需要每個表示式去定義一個Eval方法,這個方法會根據給定的environment變量返迴表達式的值。因爲每個表達式都必鬚提供這個方法,我們將它加入到Expr接口中。這個包隻會對外公開Expr,Env,和Var類型。調用方不需要獲取其它的表達式類型就可以使用這個求值器。 +```go +type Expr interface { + // Eval returns the value of this Expr in the environment env. + Eval(env Env) float64 +} +``` +下面給大家展示一個具體的Eval方法。Var類型的這個方法對一個environment變量進行査找,如果這個變量沒有在environment中定義過這個方法會返迴一個零值,literal類型的這個方法簡單的返迴它眞實的值。 +```go +func (v Var) Eval(env Env) float64 { + return env[v] +} + +func (l literal) Eval(_ Env) float64 { + return float64(l) +} +``` +unary和binary的Eval方法會遞歸的計算它的運算對象,然後將運算符op作用到它們上。我們不將被零或無窮數除作爲一個錯誤,因爲它們都會産生一個固定的結果無限。最後,call的這個方法會計算對於pow,sin,或者sqrt函數的參數值,然後調用對應在math包中的函數。 +```go +func (u unary) Eval(env Env) float64 { + switch u.op { + case '+': + return +u.x.Eval(env) + case '-': + return -u.x.Eval(env) + } + panic(fmt.Sprintf("unsupported unary operator: %q", u.op)) +} + +func (b binary) Eval(env Env) float64 { + switch b.op { + case '+': + return b.x.Eval(env) + b.y.Eval(env) + case '-': + return b.x.Eval(env) - b.y.Eval(env) + case '*': + return b.x.Eval(env) * b.y.Eval(env) + case '/': + return b.x.Eval(env) / b.y.Eval(env) + } + panic(fmt.Sprintf("unsupported binary operator: %q", b.op)) +} + +func (c call) Eval(env Env) float64 { + switch c.fn { + case "pow": + return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env)) + case "sin": + return math.Sin(c.args[0].Eval(env)) + case "sqrt": + return math.Sqrt(c.args[0].Eval(env)) + } + panic(fmt.Sprintf("unsupported function call: %s", c.fn)) +} +``` +一些方法會失敗。例如,一個call表達式可能未知的函數或者錯誤的參數個數。用一個無效的運算符如!或者<去構建一個unary或者binary表達式也是可能會發生的(盡管下面提到的Parse函數不會這樣做)。這些錯誤會讓Eval方法panic。其它的錯誤,像計算一個沒有在environment變量中出現過的Var,隻會讓Eval方法返迴一個錯誤的結果。所有的這些錯誤都可以通過在計算前檢査Expr來發現。這是我們接下來要講的Check方法的工作,但是讓我們先測試Eval方法。 + +下面的TestEval函數是對evaluator的一個測試。它使用了我們會在第11章講解的testing包,但是現在知道調用t.Errof會報告一個錯誤就足夠了。這個函數循環遍歷一個表格中的輸入,這個表格中定義了三個表達式和針對每個表達式不同的環境變量。第一個表達式根據給定圓的面積A計算它的半徑,第二個表達式通過兩個變量x和y計算兩個立方體的體積之和,第三個表達式將華氏溫度F轉換成攝氏度。 +```go +func TestEval(t *testing.T) { + tests := []struct { + expr string + env Env + want string + }{ + {"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"}, + {"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"}, + {"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"}, + {"5 / 9 * (F - 32)", Env{"F": -40}, "-40"}, + {"5 / 9 * (F - 32)", Env{"F": 32}, "0"}, + {"5 / 9 * (F - 32)", Env{"F": 212}, "100"}, + } + var prevExpr string + for _, test := range tests { + // Print expr only when it changes. + if test.expr != prevExpr { + fmt.Printf("\n%s\n", test.expr) + prevExpr = test.expr + } + expr, err := Parse(test.expr) + if err != nil { + t.Error(err) // parse error + continue + } + got := fmt.Sprintf("%.6g", expr.Eval(test.env)) + fmt.Printf("\t%v => %s\n", test.env, got) + if got != test.want { + t.Errorf("%s.Eval() in %v = %q, want %q\n", + test.expr, test.env, got, test.want) + } + } +} +``` +對於表格中的每一條記録,這個測試會解析它的表達式然後在環境變量中計算它,輸出結果。這里我們沒有空間來展示Parse函數,但是如果你使用go get下載這個包你就可以看到這個函數。 + +go test(§11.1) 命令會運行一個包的測試用例: +``` +$ go test -v gopl.io/ch7/eval +``` +這個-v標識可以讓我們看到測試用例打印的輸出;正常情況下像這個一樣成功的測試用例會阻止打印結果的輸出。這里是測試用例里fmt.Printf語句的輸出: +``` +sqrt(A / pi) + map[A:87616 pi:3.141592653589793] => 167 + +pow(x, 3) + pow(y, 3) + map[x:12 y:1] => 1729 + map[x:9 y:10] => 1729 + +5 / 9 * (F - 32) + map[F:-40] => -40 + map[F:32] => 0 + map[F:212] => 100 +``` +幸運的是目前爲止所有的輸入都是適合的格式,但是我們的運氣不可能一直都有。甚至在解釋型語言中,爲了靜態錯誤檢査語法是非常常見的;靜態錯誤就是不用運行程序就可以檢測出來的錯誤。通過將靜態檢査和動態的部分分開,我們可以快速的檢査錯誤併且對於多次檢査隻執行一次而不是每次表達式計算的時候都進行檢査。 + +讓我們往Expr接口中增加另一個方法。Check方法在一個表達式語義樹檢査出靜態錯誤。我們馬上會説明它的vars參數。 +```go +type Expr interface { + Eval(env Env) float64 + // Check reports errors in this Expr and adds its Vars to the set. + Check(vars map[Var]bool) error +} +``` +具體的Check方法展示在下面。literal和Var類型的計算不可能失敗,所以這些類型的Check方法會返迴一個nil值。對於unary和binary的Check方法會首先檢査操作符是否有效,然後遞歸的檢査運算單元。相似地對於call的這個方法首先檢査調用的函數是否已知併且有沒有正確個數的參數,然後遞歸的檢査每一個參數。 +```go +func (v Var) Check(vars map[Var]bool) error { + vars[v] = true + return nil +} + +func (literal) Check(vars map[Var]bool) error { + return nil +} + +func (u unary) Check(vars map[Var]bool) error { + if !strings.ContainsRune("+-", u.op) { + return fmt.Errorf("unexpected unary op %q", u.op) + } + return u.x.Check(vars) +} + +func (b binary) Check(vars map[Var]bool) error { + if !strings.ContainsRune("+-*/", b.op) { + return fmt.Errorf("unexpected binary op %q", b.op) + } + if err := b.x.Check(vars); err != nil { + return err + } + return b.y.Check(vars) +} + +func (c call) Check(vars map[Var]bool) error { + arity, ok := numParams[c.fn] + if !ok { + return fmt.Errorf("unknown function %q", c.fn) + } + if len(c.args) != arity { + return fmt.Errorf("call to %s has %d args, want %d", + c.fn, len(c.args), arity) + } + for _, arg := range c.args { + if err := arg.Check(vars); err != nil { + return err + } + } + return nil +} + +var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1} +``` +我們在兩個組中有選擇地列出有問題的輸入和它們得出的錯誤。Parse函數(這里沒有出現)會報出一個語法錯誤和Check函數會報出語義錯誤。 +``` +x % 2 unexpected '%' +math.Pi unexpected '.' +!true unexpected '!' +"hello" unexpected '"' + +log(10) unknown function "log" +sqrt(1, 2) call to sqrt has 2 args, want 1 +``` +Check方法的參數是一個Var類型的集合,這個集合聚集從表達式中找到的變量名。爲了保證成功的計算,這些變量中的每一個都必鬚出現在環境變量中。從邏輯上講,這個集合就是調用Check方法返迴的結果,但是因爲這個方法是遞歸調用的,所以對於Check方法填充結果到一個作爲參數傳入的集合中會更加的方便。調用方在初始調用時必鬚提供一個空的集合。 + +在第3.2節中,我們繪製了一個在編譯器才確定的函數f(x,y)。現在我們可以解析,檢査和計算在字符串中的表達式,我們可以構建一個在運行時從客戶端接收表達式的web應用併且它會繪製這個函數的表示的麴面。我們可以使用集合vars來檢査表達式是否是一個隻有兩個變量,x和y的函數——實際上是3個,因爲我們爲了方便會提供半徑大小r。併且我們會在計算前使用Check方法拒絶有格式問題的表達式,這樣我們就不會在下面函數的40000個計算過程(100x100個柵格,每一個有4個角)重複這些檢査。 + +這個ParseAndCheck函數混合了解析和檢査步驟的過程: +```go +// gopl.io/ch7/surface +import "gopl.io/ch7/eval" + +func parseAndCheck(s string) (eval.Expr, error) { + if s == "" { + return nil, fmt.Errorf("empty expression") + } + expr, err := eval.Parse(s) + if err != nil { + return nil, err + } + vars := make(map[eval.Var]bool) + if err := expr.Check(vars); err != nil { + return nil, err + } + for v := range vars { + if v != "x" && v != "y" && v != "r" { + return nil, fmt.Errorf("undefined variable: %s", v) + } + } + return expr, nil +} +``` +爲了編寫這個web應用,所有我們需要做的就是下面這個plot函數,這個函數有和http.HandlerFunc相似的籤名: +```go +func plot(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + expr, err := parseAndCheck(r.Form.Get("expr")) + if err != nil { + http.Error(w, "bad expr: "+err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "image/svg+xml") + surface(w, func(x, y float64) float64 { + r := math.Hypot(x, y) // distance from (0,0) + return expr.Eval(eval.Env{"x": x, "y": y, "r": r}) + }) +} +``` + +![](../images/ch7-07.png) + +這個plot函數解析和檢査在HTTP請求中指定的表達式併且用它來創建一個兩個變量的匿名函數。這個匿名函數和來自原來surface-plotting程序中的固定函數f有相同的籤名,但是它計算一個用戶提供的表達式。環境變量中定義了x,y和半徑r。最後plot調用surface函數,它就是gopl.io/ch3/surface中的主要函數,脩改後它可以接受plot中的函數和輸出io.Writer作爲參數,而不是使用固定的函數f和os.Stdout。圖7.7中顯示了通過程序産生的3個麴面。 + +練習7.13:爲Expr增加一個String方法來打印美觀的語法樹。當再一次解析的時候,檢査它的結果是否生成相同的語法樹。 + +練習7.14:定義一個新的滿足Expr接口的具體類型併且提供一個新的操作例如對它運算單元中的最小值的計算。因爲Parse函數不會創建這個新類型的實例,爲了使用它你可能需要直接構造一個語法樹(或者繼承parser接口)。 + +練習7.15:編寫一個從標準輸入中讀取一個單一表達式的程序,用戶及時地提供對於任意變量的值,然後在結果環境變量中計算表達式的值。優雅的處理所有遇到的錯誤。 + +練習7.16:編寫一個基於web的計算器程序。 From 8116733b0c08320567ec5676cafde4a4c8587e43 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 18 Jan 2016 08:11:33 +0800 Subject: [PATCH 13/23] update progress.md --- progress.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/progress.md b/progress.md index 341b75e..2022c37 100644 --- a/progress.md +++ b/progress.md @@ -57,9 +57,9 @@ - [x] 7.4 Parsing Flags with flag.Value - [x] 7.5 Interface Values - [x] 7.6 Sorting with sort.Interface - - [ ] 7.7 The http.Handler Interface - - [ ] 7.8 The error Interface - - [ ] 7.9 Example: Expression Evaluator + - [x] 7.7 The http.Handler Interface + - [x] 7.8 The error Interface + - [x] 7.9 Example: Expression Evaluator - [ ] 7.10 Type Assertions - [ ] 7.11 Discriminating Errors with Type Assertions - [ ] 7.12 Querying Behaviors with Interface Type Assertions @@ -79,7 +79,7 @@ - [x] 8.10 Example: Chat Server - [x] Chapter 9: Concurrency with Shared Variables - [x] 9.1 Race Conditions - - [ ] 9.2 Mutual Exclusion: sync.Mutex + - [x] 9.2 Mutual Exclusion: sync.Mutex - [x] 9.3 Read/Write Mutexes: sync.RWMutex - [x] 9.4 Memory Synchronization - [x] 9.5 Lazy Initialization: sync.Once @@ -117,4 +117,3 @@ - [x] 13.3 Example: Deep Equivalence - [x] 13.4 Calling C Code with cgo - [x] 13.5 Another Word of Caution - From 324d2c8925530422f43eb8c52307624e3184b9fc Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 18 Jan 2016 09:14:09 +0800 Subject: [PATCH 14/23] make loop --- ch9/ch9-02.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/ch9/ch9-02.md b/ch9/ch9-02.md index e556c05..2471040 100644 --- a/ch9/ch9-02.md +++ b/ch9/ch9-02.md @@ -1,6 +1,6 @@ ## 9.2. sync.Mutex互斥鎖 -在8.6节中,我们使用了一个buffered channel作为一个计数信号量,来保证最多只有20个goroutine会同时执行HTTP请求。同理,我们可以用一个容量只有1的channel来保证最多只有一个goroutine在同一时刻访问一个共享变量。一个只能为1和0的信号量叫做二元信号量(binary semaphore)。 +在8.6節中,我們使用了一個buffered channel作爲一個計數信號量,來保證最多隻有20個goroutine會同時執行HTTP請求。同理,我們可以用一個容量隻有1的channel來保證最多隻有一個goroutine在同一時刻訪問一個共享變量。一個隻能爲1和0的信號量叫做二元信號量(binary semaphore)。 ```go gopl.io/ch9/bank2 @@ -23,7 +23,7 @@ func Balance() int { } ``` -这种互斥很实用,而且被sync包里的Mutex类型直接支持。它的Lock方法能够获取到token(这里叫锁),并且Unlock方法会释放这个token: +這種互斥很實用,而且被sync包里的Mutex類型直接支持。它的Lock方法能夠獲取到token(這里叫鎖),併且Unlock方法會釋放這個token: ```go gopl.io/ch9/bank3 @@ -48,13 +48,13 @@ func Balance() int { } ``` -每次一个goroutine访问bank变量时(这里只有balance余额变量),它都会调用mutex的Lock方法来获取一个互斥锁。如果其它的goroutine已经获得了这个锁的话,这个操作会被阻塞直到其它goroutine调用了Unlock使该锁变回可用状态。mutex会保护共享变量。惯例来说,被mutex所保护的变量是在mutex变量声明之后立刻声明的。如果你的做法和惯例不符,确保在文档里对你的做法进行说明。 +每次一個goroutine訪問bank變量時(這里隻有balance餘額變量),它都會調用mutex的Lock方法來獲取一個互斥鎖。如果其它的goroutine已經獲得了這個鎖的話,這個操作會被阻塞直到其它goroutine調用了Unlock使該鎖變迴可用狀態。mutex會保護共享變量。慣例來説,被mutex所保護的變量是在mutex變量聲明之後立刻聲明的。如果你的做法和慣例不符,確保在文檔里對你的做法進行説明。 -在Lock和Unlock之间的代码段中的内容goroutine可以随便读取或者修改,这个代码段叫做临界区。goroutine在结束后释放锁是必要的,无论以哪条路径通过函数都需要释放,即使是在错误路径中,也要记得释放。 +在Lock和Unlock之間的代碼段中的內容goroutine可以隨便讀取或者脩改,這個代碼段叫做臨界區。goroutine在結束後釋放鎖是必要的,無論以哪條路徑通過函數都需要釋放,卽使是在錯誤路徑中,也要記得釋放。 -上面的bank程序例证了一种通用的并发模式。一系列的导出函数封装了一个或多个变量,那么访问这些变量唯一的方式就是通过这些函数来做(或者方法,对于一个对象的变量来说)。每一个函数在一开始就获取互斥锁并在最后释放锁,从而保证共享变量不会被并发访问。这种函数、互斥锁和变量的编排叫作监控monitor(这种老式单词的monitor是受"monitor goroutine"的术语启发而来的。两种用法都是一个代理人保证变量被顺序访问)。 +上面的bank程序例證了一種通用的併發模式。一繫列的導出函數封裝了一個或多個變量,那麽訪問這些變量唯一的方式就是通過這些函數來做(或者方法,對於一個對象的變量來説)。每一個函數在一開始就獲取互斥鎖併在最後釋放鎖,從而保證共享變量不會被併發訪問。這種函數、互斥鎖和變量的編排叫作監控monitor(這種老式單詞的monitor是受"monitor goroutine"的術語啟發而來的。兩種用法都是一個代理人保證變量被順序訪問)。 -由于在存款和查询余额函数中的临界区代码这么短--只有一行,没有分支调用--在代码最后去调用Unlock就显得更为直截了当。在更复杂的临界区的应用中,尤其是必须要尽早处理错误并返回的情况下,就很难去(靠人)判断对Lock和Unlock的调用是在所有路径中都能够严格配对的了。Go语言里的defer简直就是这种情况下的救星:我们用defer来调用Unlock,临界区会隐式地延伸到函数作用域的最后,这样我们就从“总要记得在函数返回之后或者发生错误返回时要记得调用一次Unlock”这种状态中获得了解放。Go会自动帮我们完成这些事情。 +由於在存款和査詢餘額函數中的臨界區代碼這麽短--隻有一行,沒有分支調用--在代碼最後去調用Unlock就顯得更爲直截了當。在更複雜的臨界區的應用中,尤其是必鬚要盡早處理錯誤併返迴的情況下,就很難去(靠人)判斷對Lock和Unlock的調用是在所有路徑中都能夠嚴格配對的了。Go語言里的defer簡直就是這種情況下的救星:我們用defer來調用Unlock,臨界區會隱式地延伸到函數作用域的最後,這樣我們就從“總要記得在函數返迴之後或者發生錯誤返迴時要記得調用一次Unlock”這種狀態中獲得了解放。Go會自動幫我們完成這些事情。 ```go @@ -65,11 +65,11 @@ func Balance() int { } ``` -上面的例子里Unlock会在return语句读取完balance的值之后执行,所以Balance函数是并发安全的。这带来的另一点好处是,我们再也不需要一个本地变量b了。 +上面的例子里Unlock會在return語句讀取完balance的值之後執行,所以Balance函數是併發安全的。這帶來的另一點好處是,我們再也不需要一個本地變量b了。 -此外,一个deferred Unlock即使在临界区发生panic时依然会执行,这对于用recover (§5.10)来恢复的程序来说是很重要的。defer调用只会比显式地调用Unlock成本高那么一点点,不过却在很大程度上保证了代码的整洁性。大多数情况下对于并发程序来说,代码的整洁性比过度的优化更重要。如果可能的话尽量使用defer来将临界区扩展到函数的结束。 +此外,一個deferred Unlock卽使在臨界區發生panic時依然會執行,這對於用recover (§5.10)來恢複的程序來説是很重要的。defer調用隻會比顯式地調用Unlock成本高那麽一點點,不過卻在很大程度上保證了代碼的整潔性。大多數情況下對於併發程序來説,代碼的整潔性比過度的優化更重要。如果可能的話盡量使用defer來將臨界區擴展到函數的結束。 -考虑一下下面的Withdraw函数。成功的时候,它会正确地减掉余额并返回true。但如果银行记录资金对交易来说不足,那么取款就会恢复余额,并返回false。 +考慮一下下面的Withdraw函數。成功的時候,它會正確地減掉餘額併返迴true。但如果銀行記録資金對交易來説不足,那麽取款就會恢複餘額,併返迴false。 ```go // NOTE: not atomic! @@ -83,9 +83,9 @@ func Withdraw(amount int) bool { } ``` -函数终于给出了正确的结果,但是还有一点讨厌的副作用。当过多的取款操作同时执行时,balance可能会瞬时被减到0以下。这可能会引起一个并发的取款被不合逻辑地拒绝。所以如果Bob尝试买一辆sports car时,Alice可能就没办法为她的早咖啡付款了。这里的问题是取款不是一个原子操作:它包含了三个步骤,每一步都需要去获取并释放互斥锁,但任何一次锁都不会锁上整个取款流程。 +函數終於給出了正確的結果,但是還有一點討厭的副作用。當過多的取款操作同時執行時,balance可能會瞬時被減到0以下。這可能會引起一個併發的取款被不合邏輯地拒絶。所以如果Bob嚐試買一輛sports car時,Alice可能就沒辦法爲她的早咖啡付款了。這里的問題是取款不是一個原子操作:它包含了三個步驟,每一步都需要去獲取併釋放互斥鎖,但任何一次鎖都不會鎖上整個取款流程。 -理想情况下,取款应该只在整个操作中获得一次互斥锁。下面这样的尝试是错误的: +理想情況下,取款應該隻在整個操作中獲得一次互斥鎖。下面這樣的嚐試是錯誤的: ```go // NOTE: incorrect! @@ -101,11 +101,11 @@ func Withdraw(amount int) bool { } ``` -上面这个例子中,Deposit会调用mu.Lock()第二次去获取互斥锁,但因为mutex已经锁上了,而无法被重入(译注:go里没有重入锁,关于重入锁的概念,请参考java)--也就是说没法对一个已经锁上的mutex来再次上锁--这会导致程序死锁,没法继续执行下去,Withdraw会永远阻塞下去。 +上面這個例子中,Deposit會調用mu.Lock()第二次去獲取互斥鎖,但因爲mutex已經鎖上了,而無法被重入(譯註:go里沒有重入鎖,關於重入鎖的概念,請參考java)--也就是説沒法對一個已經鎖上的mutex來再次上鎖--這會導致程序死鎖,沒法繼續執行下去,Withdraw會永遠阻塞下去。 -关于Go的互斥量不能重入这一点我们有很充分的理由。互斥量的目的是为了确保共享变量在程序执行时的关键点上能够保证不变性。不变性的其中之一是“没有goroutine访问共享变量”。但实际上对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine获得了一个互斥锁时,它会断定这种不变性能够被保持。其获取并保持锁期间,可能会去更新共享变量,这样不变性只是短暂地被破坏。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一个可以重入的mutex也可以保证没有其它的goroutine在访问共享变量,但这种方式没法保证这些变量额外的不变性。(译注:这段翻译有点晕) +關於Go的互斥量不能重入這一點我們有很充分的理由。互斥量的目的是爲了確保共享變量在程序執行時的關鍵點上能夠保證不變性。不變性的其中之一是“沒有goroutine訪問共享變量”。但實際上對於mutex保護的變量來説,不變性還包括其它方面。當一個goroutine獲得了一個互斥鎖時,它會斷定這種不變性能夠被保持。其獲取併保持鎖期間,可能會去更新共享變量,這樣不變性隻是短暫地被破壞。然而當其釋放鎖之後,它必鬚保證不變性已經恢複原樣。盡管一個可以重入的mutex也可以保證沒有其它的goroutine在訪問共享變量,但這種方式沒法保證這些變量額外的不變性。(譯註:這段翻譯有點暈) -一个通用的解决方案是将一个函数分离为多个函数,比如我们把Deposit分离成两个:一个不导出的函数deposit,这个函数假设锁总是会被保持并去做实际的操作,另一个是导出的函数Deposit,这个函数会调用deposit,但在调用前会先去获取锁。同理我们可以将Withdraw也表示成这种形式: +一個通用的解決方案是將一個函數分離爲多個函數,比如我們把Deposit分離成兩個:一個不導出的函數deposit,這個函數假設鎖總是會被保持併去做實際的操作,另一個是導出的函數Deposit,這個函數會調用deposit,但在調用前會先去獲取鎖。同理我們可以將Withdraw也表示成這種形式: ```go func Withdraw(amount int) bool { @@ -136,8 +136,8 @@ func deposit(amount int) { balance += amount } ``` -当然,这里的存款deposit函数很小实际上取款withdraw函数不需要理会对它的调用,尽管如此,这里的表达还是表明了规则。 +當然,這里的存款deposit函數很小實際上取款withdraw函數不需要理會對它的調用,盡管如此,這里的表達還是表明了規則。 -封装(§6.6), 用限制一个程序中的意外交互的方式,可以使我们获得数据结构的不变性。因为某种原因,封装还帮我们获得了并发的不变性。当你使用mutex时,确保mutex和其保护的变量没有被导出(在go里也就是小写,且不要被大写字母开头的函数访问啦),无论这些变量是包级的变量还是一个struct的字段。 +封裝(§6.6), 用限製一個程序中的意外交互的方式,可以使我們獲得數據結構的不變性。因爲某種原因,封裝還幫我們獲得了併發的不變性。當你使用mutex時,確保mutex和其保護的變量沒有被導出(在go里也就是小寫,且不要被大寫字母開頭的函數訪問啦),無論這些變量是包級的變量還是一個struct的字段。 From ae133636eb0da60b88cd155d3ba3ab30ae9543ea Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 18 Jan 2016 09:53:55 +0800 Subject: [PATCH 15/23] fmt --- ch7/ch7-07.md | 25 +++++++++++++++++++++++++ ch7/ch7-08.md | 13 +++++++++++++ ch7/ch7-09.md | 30 ++++++++++++++++++++++++++++++ ch9/ch9-02.md | 4 ---- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/ch7/ch7-07.md b/ch7/ch7-07.md index 5d38dad..a21d5ff 100644 --- a/ch7/ch7-07.md +++ b/ch7/ch7-07.md @@ -1,5 +1,7 @@ ## 7.7. http.Handler接口 + 在第一章中,我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中,我們會對那些基於http.Handler接口的服務器API做更進一步的學習: + ```go // net/http package http @@ -10,9 +12,11 @@ type Handler interface { func ListenAndServe(address string, h Handler) error ``` + ListenAndServe函數需要一個例如“localhost:8000”的服務器地址,和一個所有請求都可以分派的Handler接口實例。它會一直運行,直到這個服務因爲一個錯誤而失敗(或者啟動失敗),它的返迴值一定是一個非空的錯誤。 想象一個電子商務網站,爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型,我們給這個類型一個ServeHttp方法,這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。 + ```go // gopl.io/ch7/http1 func main() { @@ -32,19 +36,25 @@ func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } ``` + 如果我們啟動這個服務, + ``` $ go build gopl.io/ch7/http1 $ ./http1 & ``` + 然後用1.5節中的獲取程序(如果你更喜歡可以使用web瀏覽器)來連接服務器,我們得到下面的輸出: + ``` $ go build gopl.io/ch1/fetch $ ./fetch http://localhost:8000 shoes: $50.00 socks: $5.00 ``` + 目前爲止,這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL,每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格,像這樣/price?item=socks來指定一個請求參數。 + ```go // gopl.io/ch7/http2 func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { @@ -68,14 +78,18 @@ func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { } } ``` + 現在handler基於URL的路徑部分(req.URL.Path)來決定執行什麽邏輯。如果這個handler不能識别這個路徑,它會通過調用w.WriteHeader(http.StatusNotFound)返迴客戶端一個HTTP錯誤;這個檢査應該在向w寫入任何值前完成。(順便提一下,http.ResponseWriter是另一個接口。它在io.Writer上增加了發送HTTP相應頭的方法。)等效地,我們可以使用實用的http.Error函數: + ```go msg := fmt.Sprintf("no such page: %s\n", req.URL) http.Error(w, msg, http.StatusNotFound) // 404 ``` + /price的case會調用URL的Query方法來將HTTP請求參數解析爲一個map,或者更準確地説一個net/url包中url.Values(§6.2.1)類型的多重映射。然後找到第一個item參數併査找它的價格。如果這個貨品沒有找到會返迴一個錯誤。 這里是一個和新服務器會話的例子: + ``` $ go build gopl.io/ch7/http2 $ go build gopl.io/ch1/fetch @@ -92,12 +106,14 @@ no such item: "hat" $ ./fetch http://localhost:8000/help no such page: /help ``` + 顯然我們可以繼續向ServeHTTP方法中添加case,但在一個實際的應用中,將每個case中的邏輯定義到一個分開的方法或函數中會很實用。此外,相近的URL可能需要相似的邏輯;例如幾個圖片文件可能有形如/images/\*.png的URL。因爲這些原因,net/http包提供了一個請求多路器ServeMux來簡化URL和handlers的聯繫。一個ServeMux將一批http.Handler聚集到一個單一的http.Handler中。再一次,我們可以看到滿足同一接口的不同類型是可替換的:web服務器將請求指派給任意的http.Handler 而不需要考慮它後面的具體類型。 對於更複雜的應用,一些ServeMux可以通過組合來處理更加錯綜複雜的路由需求。Go語言目前沒有一個權威的web框架,就像Ruby語言有Rails和python有Django。這併不是説這樣的框架不存在,而是Go語言標準庫中的構建模塊就已經非常靈活以至於這些框架都是不必要的。此外,盡管在一個項目早期使用框架是非常方便的,但是它們帶來額外的複雜度會使長期的維護更加睏難。 在下面的程序中,我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來,這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。 + ```go // gopl.io/ch7/http3 func main() { @@ -127,13 +143,17 @@ func (db database) price(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "%s\n", price) } ``` + 讓我們關註這兩個註冊到handlers上的調用。第一個db.list是一個方法值 (§6.4),它是下面這個類型的值 + ```go func(w http.ResponseWriter, req *http.Request) ``` + 也就是説db.list的調用會援引一個接收者是db的database.list方法。所以db.list是一個實現了handler類似行爲的函數,但是因爲它沒有方法,所以它不滿足http.Handler接口併且不能直接傳給mux.Handle。 語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用,因爲http.HandlerFunc是一個類型。它有如下的定義: + ```go // net/http package http @@ -144,19 +164,23 @@ func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } ``` + HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這是一個有實現了接口http.Handler方法的函數類型。ServeHTTP方法的行爲調用了它本身的函數。因此HandlerFunc是一個讓函數值滿足一個接口的適配器,這里函數和這個接口僅有的方法有相同的函數籤名。實際上,這個技巧讓一個單一的類型例如database以多種方式滿足http.Handler接口:一種通過它的list方法,一種通過它的price方法等等。 因爲handler通過這種方式註冊非常普遍,ServeMux有一個方便的HandleFunc方法,它幫我們簡化handler註冊代碼成這樣: + ```go // gopl.io/ch7/http3a mux.HandleFunc("/list", db.list) mux.HandleFunc("/price", db.price) ``` + 從上面的代碼很容易看出應該怎麽構建一個程序,它有兩個不同的web服務器監聽不同的端口的,併且定義不同的URL將它們指派到不同的handler。我們隻要構建另外一個ServeMux併且在調用一次ListenAndServe(可能併行的)。但是在大多數程序中,一個web服務器就足夠了。此外,在一個應用程序的多個文件中定義HTTP handler也是非常典型的,如果它們必鬚全部都顯示的註冊到這個應用的ServeMux實例上會比較麻煩。 所以爲了方便,net/http包提供了一個全局的ServeMux實例DefaultServerMux和包級别的http.Handle和http.HandleFunc函數。現在,爲了使用DefaultServeMux作爲服務器的主handler,我們不需要將它傳給ListenAndServe函數;nil值就可以工作。 然後服務器的主函數可以簡化成: + ```go // gopl.io/ch7/http4 func main() { @@ -166,6 +190,7 @@ func main() { log.Fatal(http.ListenAndServe("localhost:8000", nil)) } ``` + 最後,一個重要的提示:就像我們在1.7節中提到的,web服務器在一個新的協程中調用每一個handler,所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。 練習 7.11:增加額外的handler讓客服端可以創建,讀取,更新和刪除數據庫記録。例如,一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(註意:這個脩改會引入變量同時更新的問題) diff --git a/ch7/ch7-08.md b/ch7/ch7-08.md index 5827e79..0971ab2 100644 --- a/ch7/ch7-08.md +++ b/ch7/ch7-08.md @@ -1,11 +1,15 @@ ## 7.8. error接口 + 從本書的開始,我們就已經創建和使用過神祕的預定義error類型,而且沒有解釋它究竟是什麽。實際上它就是interface類型,這個類型有一個返迴錯誤信息的單一方法: + ```go type error interface { Error() string } ``` + 創建一個error最簡單的方法就是調用errors.New函數,它會根據傳入的錯誤信息返迴一個新的error。整個errors包僅隻有4行: + ```go package errors @@ -15,11 +19,15 @@ type errorString struct { text string } func (e *errorString) Error() string { return e.text } ``` + 承載errorString的類型是一個結構體而非一個字符串,這是爲了保護它表示的錯誤避免粗心(或有意)的更新。併且因爲是指針類型*errorString滿足error接口而非errorString類型,所以每個New函數的調用都分配了一個獨特的和其他錯誤不相同的實例。我們也不想要重要的error例如io.EOF和一個剛好有相同錯誤消息的error比較後相等。 + ```go fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false" ``` + 調用errors.New函數是非常稀少的,因爲有一個方便的封裝函數fmt.Errorf,它還會處理字符串格式化。我們曾多次在第5章中用到它。 + ```go package fmt @@ -29,7 +37,9 @@ func Errorf(format string, args ...interface{}) error { return errors.New(Sprintf(format, args...)) } ``` + 雖然*errorString可能是最簡單的錯誤類型,但遠非隻有它一個。例如,syscall包提供了Go語言底層繫統調用API。在多個平台上,它定義一個實現error接口的數字類型Errno,併且在Unix平台上,Errno的Error方法會從一個字符串表中査找錯誤消息,如下面展示的這樣: + ```go package syscall @@ -49,12 +59,15 @@ func (e Errno) Error() string { return fmt.Sprintf("errno %d", e) } ``` + 下面的語句創建了一個持有Errno值爲2的接口值,表示POSIX ENOENT狀況: + ```go var err error = syscall.Errno(2) fmt.Println(err.Error()) // "no such file or directory" fmt.Println(err) // "no such file or directory" ``` + err的值圖形化的呈現在圖7.6中。 ![](../images/ch7-06.png) diff --git a/ch7/ch7-09.md b/ch7/ch7-09.md index 73de2df..939b159 100644 --- a/ch7/ch7-09.md +++ b/ch7/ch7-09.md @@ -1,16 +1,22 @@ ## 7.9. 示例: 表達式求值 + 在本節中,我們會構建一個簡單算術表達式的求值器。我們將使用一個接口Expr來表示Go語言中任意的表達式。現在這個接口不需要有方法,但是我們後面會爲它增加一些。 + ```go // An Expr is an arithmetic expression. type Expr interface{} ``` + 我們的表達式語言由浮點數符號(小數點);二元操作符+,-,\*, 和/;一元操作符-x和+x;調用pow(x,y),sin(x),和sqrt(x)的函數;例如x和pi的變量;當然也有括號和標準的優先級運算符。所有的值都是float64類型。這下面是一些表達式的例子: + ```go sqrt(A / pi) pow(x, 3) + pow(y, 3) (F - 32) * 5 / 9 ``` + 下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。(我們很快會知道爲什麽它可以被輸出。)literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式,這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用;我們限製它的fn字段隻能是pow,sin或者sqrt。 + ```go // gopl.io/ch7/eval // A Var identifies a variable, e.g., x. @@ -37,18 +43,24 @@ type call struct { args []Expr } ``` + 爲了計算一個包含變量的表達式,我們需要一個environment變量將變量的名字映射成對應的值: + ```go type Env map[Var]float64 ``` + 我們也需要每個表示式去定義一個Eval方法,這個方法會根據給定的environment變量返迴表達式的值。因爲每個表達式都必鬚提供這個方法,我們將它加入到Expr接口中。這個包隻會對外公開Expr,Env,和Var類型。調用方不需要獲取其它的表達式類型就可以使用這個求值器。 + ```go type Expr interface { // Eval returns the value of this Expr in the environment env. Eval(env Env) float64 } ``` + 下面給大家展示一個具體的Eval方法。Var類型的這個方法對一個environment變量進行査找,如果這個變量沒有在environment中定義過這個方法會返迴一個零值,literal類型的這個方法簡單的返迴它眞實的值。 + ```go func (v Var) Eval(env Env) float64 { return env[v] @@ -58,7 +70,9 @@ func (l literal) Eval(_ Env) float64 { return float64(l) } ``` + unary和binary的Eval方法會遞歸的計算它的運算對象,然後將運算符op作用到它們上。我們不將被零或無窮數除作爲一個錯誤,因爲它們都會産生一個固定的結果無限。最後,call的這個方法會計算對於pow,sin,或者sqrt函數的參數值,然後調用對應在math包中的函數。 + ```go func (u unary) Eval(env Env) float64 { switch u.op { @@ -96,9 +110,11 @@ func (c call) Eval(env Env) float64 { panic(fmt.Sprintf("unsupported function call: %s", c.fn)) } ``` + 一些方法會失敗。例如,一個call表達式可能未知的函數或者錯誤的參數個數。用一個無效的運算符如!或者<去構建一個unary或者binary表達式也是可能會發生的(盡管下面提到的Parse函數不會這樣做)。這些錯誤會讓Eval方法panic。其它的錯誤,像計算一個沒有在environment變量中出現過的Var,隻會讓Eval方法返迴一個錯誤的結果。所有的這些錯誤都可以通過在計算前檢査Expr來發現。這是我們接下來要講的Check方法的工作,但是讓我們先測試Eval方法。 下面的TestEval函數是對evaluator的一個測試。它使用了我們會在第11章講解的testing包,但是現在知道調用t.Errof會報告一個錯誤就足夠了。這個函數循環遍歷一個表格中的輸入,這個表格中定義了三個表達式和針對每個表達式不同的環境變量。第一個表達式根據給定圓的面積A計算它的半徑,第二個表達式通過兩個變量x和y計算兩個立方體的體積之和,第三個表達式將華氏溫度F轉換成攝氏度。 + ```go func TestEval(t *testing.T) { tests := []struct { @@ -134,13 +150,17 @@ func TestEval(t *testing.T) { } } ``` + 對於表格中的每一條記録,這個測試會解析它的表達式然後在環境變量中計算它,輸出結果。這里我們沒有空間來展示Parse函數,但是如果你使用go get下載這個包你就可以看到這個函數。 go test(§11.1) 命令會運行一個包的測試用例: + ``` $ go test -v gopl.io/ch7/eval ``` + 這個-v標識可以讓我們看到測試用例打印的輸出;正常情況下像這個一樣成功的測試用例會阻止打印結果的輸出。這里是測試用例里fmt.Printf語句的輸出: + ``` sqrt(A / pi) map[A:87616 pi:3.141592653589793] => 167 @@ -154,9 +174,11 @@ pow(x, 3) + pow(y, 3) map[F:32] => 0 map[F:212] => 100 ``` + 幸運的是目前爲止所有的輸入都是適合的格式,但是我們的運氣不可能一直都有。甚至在解釋型語言中,爲了靜態錯誤檢査語法是非常常見的;靜態錯誤就是不用運行程序就可以檢測出來的錯誤。通過將靜態檢査和動態的部分分開,我們可以快速的檢査錯誤併且對於多次檢査隻執行一次而不是每次表達式計算的時候都進行檢査。 讓我們往Expr接口中增加另一個方法。Check方法在一個表達式語義樹檢査出靜態錯誤。我們馬上會説明它的vars參數。 + ```go type Expr interface { Eval(env Env) float64 @@ -164,7 +186,9 @@ type Expr interface { Check(vars map[Var]bool) error } ``` + 具體的Check方法展示在下面。literal和Var類型的計算不可能失敗,所以這些類型的Check方法會返迴一個nil值。對於unary和binary的Check方法會首先檢査操作符是否有效,然後遞歸的檢査運算單元。相似地對於call的這個方法首先檢査調用的函數是否已知併且有沒有正確個數的參數,然後遞歸的檢査每一個參數。 + ```go func (v Var) Check(vars map[Var]bool) error { vars[v] = true @@ -211,7 +235,9 @@ func (c call) Check(vars map[Var]bool) error { var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1} ``` + 我們在兩個組中有選擇地列出有問題的輸入和它們得出的錯誤。Parse函數(這里沒有出現)會報出一個語法錯誤和Check函數會報出語義錯誤。 + ``` x % 2 unexpected '%' math.Pi unexpected '.' @@ -221,11 +247,13 @@ math.Pi unexpected '.' log(10) unknown function "log" sqrt(1, 2) call to sqrt has 2 args, want 1 ``` + Check方法的參數是一個Var類型的集合,這個集合聚集從表達式中找到的變量名。爲了保證成功的計算,這些變量中的每一個都必鬚出現在環境變量中。從邏輯上講,這個集合就是調用Check方法返迴的結果,但是因爲這個方法是遞歸調用的,所以對於Check方法填充結果到一個作爲參數傳入的集合中會更加的方便。調用方在初始調用時必鬚提供一個空的集合。 在第3.2節中,我們繪製了一個在編譯器才確定的函數f(x,y)。現在我們可以解析,檢査和計算在字符串中的表達式,我們可以構建一個在運行時從客戶端接收表達式的web應用併且它會繪製這個函數的表示的麴面。我們可以使用集合vars來檢査表達式是否是一個隻有兩個變量,x和y的函數——實際上是3個,因爲我們爲了方便會提供半徑大小r。併且我們會在計算前使用Check方法拒絶有格式問題的表達式,這樣我們就不會在下面函數的40000個計算過程(100x100個柵格,每一個有4個角)重複這些檢査。 這個ParseAndCheck函數混合了解析和檢査步驟的過程: + ```go // gopl.io/ch7/surface import "gopl.io/ch7/eval" @@ -250,7 +278,9 @@ func parseAndCheck(s string) (eval.Expr, error) { return expr, nil } ``` + 爲了編寫這個web應用,所有我們需要做的就是下面這個plot函數,這個函數有和http.HandlerFunc相似的籤名: + ```go func plot(w http.ResponseWriter, r *http.Request) { r.ParseForm() diff --git a/ch9/ch9-02.md b/ch9/ch9-02.md index 2471040..bbddda1 100644 --- a/ch9/ch9-02.md +++ b/ch9/ch9-02.md @@ -56,7 +56,6 @@ func Balance() int { 由於在存款和査詢餘額函數中的臨界區代碼這麽短--隻有一行,沒有分支調用--在代碼最後去調用Unlock就顯得更爲直截了當。在更複雜的臨界區的應用中,尤其是必鬚要盡早處理錯誤併返迴的情況下,就很難去(靠人)判斷對Lock和Unlock的調用是在所有路徑中都能夠嚴格配對的了。Go語言里的defer簡直就是這種情況下的救星:我們用defer來調用Unlock,臨界區會隱式地延伸到函數作用域的最後,這樣我們就從“總要記得在函數返迴之後或者發生錯誤返迴時要記得調用一次Unlock”這種狀態中獲得了解放。Go會自動幫我們完成這些事情。 - ```go func Balance() int { mu.Lock() @@ -135,9 +134,6 @@ func Balance() int { func deposit(amount int) { balance += amount } ``` - 當然,這里的存款deposit函數很小實際上取款withdraw函數不需要理會對它的調用,盡管如此,這里的表達還是表明了規則。 封裝(§6.6), 用限製一個程序中的意外交互的方式,可以使我們獲得數據結構的不變性。因爲某種原因,封裝還幫我們獲得了併發的不變性。當你使用mutex時,確保mutex和其保護的變量沒有被導出(在go里也就是小寫,且不要被大寫字母開頭的函數訪問啦),無論這些變量是包級的變量還是一個struct的字段。 - - From 7ca7f7a1b4588a0f9104d8f0537b620e2d603878 Mon Sep 17 00:00:00 2001 From: foreversmart Date: Mon, 18 Jan 2016 10:36:12 +0800 Subject: [PATCH 16/23] ch7-10 done --- ch7/ch7-10.md | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/ch7/ch7-10.md b/ch7/ch7-10.md index b1f2868..1b9ff22 100644 --- a/ch7/ch7-10.md +++ b/ch7/ch7-10.md @@ -1,3 +1,45 @@ ## 7.10. 類型斷言 +類型斷言是一個使用在接口值上的操作。語法上它看起來像x.(T)被稱爲斷言類型,這里x表示一個接口的類型和T表示一個類型。一個類型斷言檢査它操作對象的動態類型是否和斷言的類型匹配。 -TODO +這里有兩種可能。第一種,如果斷言的類型T是一個具體類型,然後類型斷言檢査x的動態類型是否和T相同。如果這個檢査成功了,類型斷言的結果是x的動態值,當然它的類型是T。換句話説,具體類型的類型斷言從它的操作對象中獲得具體的值。如果檢査失敗,接下來這個操作會拋出panic。例如: +```go +var w io.Writer +w = os.Stdout +f := w.(*os.File) // success: f == os.Stdout +c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer +``` +第二種,如果相反斷言的類型T是一個接口類型,然後類型斷言檢査是否x的動態類型滿足T。如果這個檢査成功了,動態值沒有獲取到;這個結果仍然是一個有相同類型和值部分的接口值,但是結果有類型T。換句話説,對一個接口類型的類型斷言改變了類型的表述方式,改變了可以獲取的方法集合(通常更大),但是它保護了接口值內部的動態類型和值的部分。 + +在下面的第一個類型斷言後,w和rw都持有os.Stdout因此它們每個有一個動態類型*os.File,但是變量w是一個io.Writer類型隻對外公開出文件的Write方法,然而rw變量也隻公開它的Read方法。 +```go +var w io.Writer +w = os.Stdout +rw := w.(io.ReadWriter) // success: *os.File has both Read and Write +w = new(ByteCounter) +rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method +``` +如果斷言操作的對象是一個nil接口值,那麽不論被斷言的類型是什麽這個類型斷言都會失敗。我們幾乎不需要對一個更少限製性的接口類型(更少的方法集合)做斷言,因爲它表現的就像賦值操作一樣,除了對於nil接口值的情況。 +```go +w = rw // io.ReadWriter is assignable to io.Writer +w = rw.(io.Writer) // fails only if rw == nil +``` +經常地我們對一個接口值的動態類型是不確定的,併且我們更願意去檢驗它是否是一些特定的類型。如果類型斷言出現在一個預期有兩個結果的賦值操作中,例如如下的定義,這個操作不會在失敗的時候發生panic但是代替地返迴一個額外的第二個結果,這個結果是一個標識成功的布爾值: +```go +var w io.Writer = os.Stdout +f, ok := w.(*os.File) // success: ok, f == os.Stdout +b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil +``` +第二個結果常規地賦值給一個命名爲ok的變量。如果這個操作失敗了,那麽ok就是false值,第一個結果等於被斷言類型的零值,在這個例子中就是一個nil的*bytes.Buffer類型。 + +這個ok結果經常立卽用於決定程序下面做什麽。if語句的擴展格式讓這個變的很簡潔: +```go +if f, ok := w.(*os.File); ok { + // ...use f... +} +``` +當類型斷言的操作對象是一個變量,你有時會看見原來的變量名重用而不是聲明一個新的本地變量,這個重用的變量會覆蓋原來的值,如下面這樣: +```go +if w, ok := w.(*os.File); ok { + // ...use w... +} +``` From edd55ba1f32256964ff22aa0c9f10bfde672dcba Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 18 Jan 2016 10:43:22 +0800 Subject: [PATCH 17/23] Update progress.md --- progress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/progress.md b/progress.md index 2022c37..2bb4bf1 100644 --- a/progress.md +++ b/progress.md @@ -60,7 +60,7 @@ - [x] 7.7 The http.Handler Interface - [x] 7.8 The error Interface - [x] 7.9 Example: Expression Evaluator - - [ ] 7.10 Type Assertions + - [x] 7.10 Type Assertions - [ ] 7.11 Discriminating Errors with Type Assertions - [ ] 7.12 Querying Behaviors with Interface Type Assertions - [ ] 7.13 Type Switches From a91355f5f1f24443c47ac7ab2d99cf12d99c84d9 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 18 Jan 2016 10:52:21 +0800 Subject: [PATCH 18/23] fmt code --- ch7/ch7-07.md | 108 ++++++++++---------- ch7/ch7-08.md | 18 ++-- ch7/ch7-09.md | 266 +++++++++++++++++++++++++------------------------- ch7/ch7-10.md | 16 ++- 4 files changed, 210 insertions(+), 198 deletions(-) diff --git a/ch7/ch7-07.md b/ch7/ch7-07.md index a21d5ff..ad02948 100644 --- a/ch7/ch7-07.md +++ b/ch7/ch7-07.md @@ -3,11 +3,11 @@ 在第一章中,我們粗略的了解了怎麽用net/http包去實現網絡客戶端(§1.5)和服務器(§1.7)。在這個小節中,我們會對那些基於http.Handler接口的服務器API做更進一步的學習: ```go -// net/http +net/http package http type Handler interface { - ServeHTTP(w ResponseWriter, r *Request) + ServeHTTP(w ResponseWriter, r *Request) } func ListenAndServe(address string, h Handler) error @@ -18,10 +18,10 @@ ListenAndServe函數需要一個例如“localhost:8000”的服務器地址, 想象一個電子商務網站,爲了銷售它的數據庫將它物品的價格映射成美元。下面這個程序可能是能想到的最簡單的實現了。它將庫存清單模型化爲一個命名爲database的map類型,我們給這個類型一個ServeHttp方法,這樣它可以滿足http.Handler接口。這個handler會遍歷整個map併輸出物品信息。 ```go -// gopl.io/ch7/http1 +gopl.io/ch7/http1 func main() { - db := database{"shoes": 50, "socks": 5} - log.Fatal(http.ListenAndServe("localhost:8000", db)) + db := database{"shoes": 50, "socks": 5} + log.Fatal(http.ListenAndServe("localhost:8000", db)) } type dollars float32 @@ -31,9 +31,9 @@ func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) } type database map[string]dollars func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { - for item, price := range db { - fmt.Fprintf(w, "%s: %s\n", item, price) - } + for item, price := range db { + fmt.Fprintf(w, "%s: %s\n", item, price) + } } ``` @@ -56,26 +56,26 @@ socks: $5.00 目前爲止,這個服務器不考慮URL隻能爲每個請求列出它全部的庫存清單。更眞實的服務器會定義多個不同的URL,每一個都會觸發一個不同的行爲。讓我們使用/list來調用已經存在的這個行爲併且增加另一個/price調用表明單個貨品的價格,像這樣/price?item=socks來指定一個請求參數。 ```go -// gopl.io/ch7/http2 +gopl.io/ch7/http2 func (db database) ServeHTTP(w http.ResponseWriter, req *http.Request) { - switch req.URL.Path { - case "/list": - for item, price := range db { - fmt.Fprintf(w, "%s: %s\n", item, price) - } - case "/price": - item := req.URL.Query().Get("item") - price, ok := db[item] - if !ok { - w.WriteHeader(http.StatusNotFound) // 404 - fmt.Fprintf(w, "no such item: %q\n", item) - return - } - fmt.Fprintf(w, "%s\n", price) - default: - w.WriteHeader(http.StatusNotFound) // 404 - fmt.Fprintf(w, "no such page: %s\n", req.URL) - } + switch req.URL.Path { + case "/list": + for item, price := range db { + fmt.Fprintf(w, "%s: %s\n", item, price) + } + case "/price": + item := req.URL.Query().Get("item") + price, ok := db[item] + if !ok { + w.WriteHeader(http.StatusNotFound) // 404 + fmt.Fprintf(w, "no such item: %q\n", item) + return + } + fmt.Fprintf(w, "%s\n", price) + default: + w.WriteHeader(http.StatusNotFound) // 404 + fmt.Fprintf(w, "no such page: %s\n", req.URL) + } } ``` @@ -115,32 +115,32 @@ no such page: /help 在下面的程序中,我們創建一個ServeMux併且使用它將URL和相應處理/list和/price操作的handler聯繫起來,這些操作邏輯都已經被分到不同的方法中。然後我門在調用ListenAndServe函數中使用ServeMux最爲主要的handler。 ```go -// gopl.io/ch7/http3 +gopl.io/ch7/http3 func main() { - db := database{"shoes": 50, "socks": 5} - mux := http.NewServeMux() - mux.Handle("/list", http.HandlerFunc(db.list)) - mux.Handle("/price", http.HandlerFunc(db.price)) - log.Fatal(http.ListenAndServe("localhost:8000", mux)) + db := database{"shoes": 50, "socks": 5} + mux := http.NewServeMux() + mux.Handle("/list", http.HandlerFunc(db.list)) + mux.Handle("/price", http.HandlerFunc(db.price)) + log.Fatal(http.ListenAndServe("localhost:8000", mux)) } type database map[string]dollars func (db database) list(w http.ResponseWriter, req *http.Request) { - for item, price := range db { - fmt.Fprintf(w, "%s: %s\n", item, price) - } + for item, price := range db { + fmt.Fprintf(w, "%s: %s\n", item, price) + } } func (db database) price(w http.ResponseWriter, req *http.Request) { - item := req.URL.Query().Get("item") - price, ok := db[item] - if !ok { - w.WriteHeader(http.StatusNotFound) // 404 - fmt.Fprintf(w, "no such item: %q\n", item) - return - } - fmt.Fprintf(w, "%s\n", price) + item := req.URL.Query().Get("item") + price, ok := db[item] + if !ok { + w.WriteHeader(http.StatusNotFound) // 404 + fmt.Fprintf(w, "no such item: %q\n", item) + return + } + fmt.Fprintf(w, "%s\n", price) } ``` @@ -155,13 +155,13 @@ func(w http.ResponseWriter, req *http.Request) 語句http.HandlerFunc(db.list)是一個轉換而非一個函數調用,因爲http.HandlerFunc是一個類型。它有如下的定義: ```go -// net/http +net/http package http type HandlerFunc func(w ResponseWriter, r *Request) func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { - f(w, r) + f(w, r) } ``` @@ -170,7 +170,7 @@ HandlerFunc顯示了在Go語言接口機製中一些不同尋常的特點。這 因爲handler通過這種方式註冊非常普遍,ServeMux有一個方便的HandleFunc方法,它幫我們簡化handler註冊代碼成這樣: ```go -// gopl.io/ch7/http3a +gopl.io/ch7/http3a mux.HandleFunc("/list", db.list) mux.HandleFunc("/price", db.price) ``` @@ -182,17 +182,17 @@ mux.HandleFunc("/price", db.price) 然後服務器的主函數可以簡化成: ```go -// gopl.io/ch7/http4 +gopl.io/ch7/http4 func main() { - db := database{"shoes": 50, "socks": 5} - http.HandleFunc("/list", db.list) - http.HandleFunc("/price", db.price) - log.Fatal(http.ListenAndServe("localhost:8000", nil)) + db := database{"shoes": 50, "socks": 5} + http.HandleFunc("/list", db.list) + http.HandleFunc("/price", db.price) + log.Fatal(http.ListenAndServe("localhost:8000", nil)) } ``` 最後,一個重要的提示:就像我們在1.7節中提到的,web服務器在一個新的協程中調用每一個handler,所以當handler獲取其它協程或者這個handler本身的其它請求也可以訪問的變量時一定要使用預防措施比如鎖機製。我們後面的兩章中講到併發相關的知識。 -練習 7.11:增加額外的handler讓客服端可以創建,讀取,更新和刪除數據庫記録。例如,一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(註意:這個脩改會引入變量同時更新的問題) +**練習 7.11:** 增加額外的handler讓客服端可以創建,讀取,更新和刪除數據庫記録。例如,一個形如/update?item=socks&price=6的請求會更新庫存清單里一個貨品的價格併且當這個貨品不存在或價格無效時返迴一個錯誤值。(註意:這個脩改會引入變量同時更新的問題) -練習 7.12:脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。 +**練習 7.12:** 脩改/list的handler讓它把輸出打印成一個HTML的表格而不是文本。html/template包(§4.6)可能會對你有幫助。 diff --git a/ch7/ch7-08.md b/ch7/ch7-08.md index 0971ab2..99772d8 100644 --- a/ch7/ch7-08.md +++ b/ch7/ch7-08.md @@ -4,7 +4,7 @@ ```go type error interface { - Error() string + Error() string } ``` @@ -46,17 +46,17 @@ package syscall type Errno uintptr // operating system error code var errors = [...]string{ - 1: "operation not permitted", // EPERM - 2: "no such file or directory", // ENOENT - 3: "no such process", // ESRCH - // ... + 1: "operation not permitted", // EPERM + 2: "no such file or directory", // ENOENT + 3: "no such process", // ESRCH + // ... } func (e Errno) Error() string { - if 0 <= int(e) && int(e) < len(errors) { - return errors[e] - } - return fmt.Sprintf("errno %d", e) + if 0 <= int(e) && int(e) < len(errors) { + return errors[e] + } + return fmt.Sprintf("errno %d", e) } ``` diff --git a/ch7/ch7-09.md b/ch7/ch7-09.md index 939b159..0aa1885 100644 --- a/ch7/ch7-09.md +++ b/ch7/ch7-09.md @@ -18,7 +18,7 @@ pow(x, 3) + pow(y, 3) 下面的五個具體類型表示了具體的表達式類型。Var類型表示對一個變量的引用。(我們很快會知道爲什麽它可以被輸出。)literal類型表示一個浮點型常量。unary和binary類型表示有一到兩個運算對象的運算符表達式,這些操作數可以是任意的Expr類型。call類型表示對一個函數的調用;我們限製它的fn字段隻能是pow,sin或者sqrt。 ```go -// gopl.io/ch7/eval +gopl.io/ch7/eval // A Var identifies a variable, e.g., x. type Var string @@ -27,20 +27,20 @@ type literal float64 // A unary represents a unary operator expression, e.g., -x. type unary struct { - op rune // one of '+', '-' - x Expr + op rune // one of '+', '-' + x Expr } // A binary represents a binary operator expression, e.g., x+y. type binary struct { - op rune // one of '+', '-', '*', '/' - x, y Expr + op rune // one of '+', '-', '*', '/' + x, y Expr } // A call represents a function call expression, e.g., sin(x). type call struct { - fn string // one of "pow", "sin", "sqrt" - args []Expr + fn string // one of "pow", "sin", "sqrt" + args []Expr } ``` @@ -54,8 +54,8 @@ type Env map[Var]float64 ```go type Expr interface { - // Eval returns the value of this Expr in the environment env. - Eval(env Env) float64 + // Eval returns the value of this Expr in the environment env. + Eval(env Env) float64 } ``` @@ -63,11 +63,11 @@ type Expr interface { ```go func (v Var) Eval(env Env) float64 { - return env[v] + return env[v] } func (l literal) Eval(_ Env) float64 { - return float64(l) + return float64(l) } ``` @@ -75,39 +75,39 @@ unary和binary的Eval方法會遞歸的計算它的運算對象,然後將運 ```go func (u unary) Eval(env Env) float64 { - switch u.op { - case '+': - return +u.x.Eval(env) - case '-': - return -u.x.Eval(env) - } - panic(fmt.Sprintf("unsupported unary operator: %q", u.op)) + switch u.op { + case '+': + return +u.x.Eval(env) + case '-': + return -u.x.Eval(env) + } + panic(fmt.Sprintf("unsupported unary operator: %q", u.op)) } func (b binary) Eval(env Env) float64 { - switch b.op { - case '+': - return b.x.Eval(env) + b.y.Eval(env) - case '-': - return b.x.Eval(env) - b.y.Eval(env) - case '*': - return b.x.Eval(env) * b.y.Eval(env) - case '/': - return b.x.Eval(env) / b.y.Eval(env) - } - panic(fmt.Sprintf("unsupported binary operator: %q", b.op)) + switch b.op { + case '+': + return b.x.Eval(env) + b.y.Eval(env) + case '-': + return b.x.Eval(env) - b.y.Eval(env) + case '*': + return b.x.Eval(env) * b.y.Eval(env) + case '/': + return b.x.Eval(env) / b.y.Eval(env) + } + panic(fmt.Sprintf("unsupported binary operator: %q", b.op)) } func (c call) Eval(env Env) float64 { - switch c.fn { - case "pow": - return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env)) - case "sin": - return math.Sin(c.args[0].Eval(env)) - case "sqrt": - return math.Sqrt(c.args[0].Eval(env)) - } - panic(fmt.Sprintf("unsupported function call: %s", c.fn)) + switch c.fn { + case "pow": + return math.Pow(c.args[0].Eval(env), c.args[1].Eval(env)) + case "sin": + return math.Sin(c.args[0].Eval(env)) + case "sqrt": + return math.Sqrt(c.args[0].Eval(env)) + } + panic(fmt.Sprintf("unsupported function call: %s", c.fn)) } ``` @@ -117,37 +117,37 @@ func (c call) Eval(env Env) float64 { ```go func TestEval(t *testing.T) { - tests := []struct { - expr string - env Env - want string - }{ - {"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"}, - {"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"}, - {"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"}, - {"5 / 9 * (F - 32)", Env{"F": -40}, "-40"}, - {"5 / 9 * (F - 32)", Env{"F": 32}, "0"}, - {"5 / 9 * (F - 32)", Env{"F": 212}, "100"}, - } - var prevExpr string - for _, test := range tests { - // Print expr only when it changes. - if test.expr != prevExpr { - fmt.Printf("\n%s\n", test.expr) - prevExpr = test.expr - } - expr, err := Parse(test.expr) - if err != nil { - t.Error(err) // parse error - continue - } - got := fmt.Sprintf("%.6g", expr.Eval(test.env)) - fmt.Printf("\t%v => %s\n", test.env, got) - if got != test.want { - t.Errorf("%s.Eval() in %v = %q, want %q\n", - test.expr, test.env, got, test.want) - } - } + tests := []struct { + expr string + env Env + want string + }{ + {"sqrt(A / pi)", Env{"A": 87616, "pi": math.Pi}, "167"}, + {"pow(x, 3) + pow(y, 3)", Env{"x": 12, "y": 1}, "1729"}, + {"pow(x, 3) + pow(y, 3)", Env{"x": 9, "y": 10}, "1729"}, + {"5 / 9 * (F - 32)", Env{"F": -40}, "-40"}, + {"5 / 9 * (F - 32)", Env{"F": 32}, "0"}, + {"5 / 9 * (F - 32)", Env{"F": 212}, "100"}, + } + var prevExpr string + for _, test := range tests { + // Print expr only when it changes. + if test.expr != prevExpr { + fmt.Printf("\n%s\n", test.expr) + prevExpr = test.expr + } + expr, err := Parse(test.expr) + if err != nil { + t.Error(err) // parse error + continue + } + got := fmt.Sprintf("%.6g", expr.Eval(test.env)) + fmt.Printf("\t%v => %s\n", test.env, got) + if got != test.want { + t.Errorf("%s.Eval() in %v = %q, want %q\n", + test.expr, test.env, got, test.want) + } + } } ``` @@ -181,9 +181,9 @@ pow(x, 3) + pow(y, 3) ```go type Expr interface { - Eval(env Env) float64 - // Check reports errors in this Expr and adds its Vars to the set. - Check(vars map[Var]bool) error + Eval(env Env) float64 + // Check reports errors in this Expr and adds its Vars to the set. + Check(vars map[Var]bool) error } ``` @@ -191,46 +191,46 @@ type Expr interface { ```go func (v Var) Check(vars map[Var]bool) error { - vars[v] = true - return nil + vars[v] = true + return nil } func (literal) Check(vars map[Var]bool) error { - return nil + return nil } func (u unary) Check(vars map[Var]bool) error { - if !strings.ContainsRune("+-", u.op) { - return fmt.Errorf("unexpected unary op %q", u.op) - } - return u.x.Check(vars) + if !strings.ContainsRune("+-", u.op) { + return fmt.Errorf("unexpected unary op %q", u.op) + } + return u.x.Check(vars) } func (b binary) Check(vars map[Var]bool) error { - if !strings.ContainsRune("+-*/", b.op) { - return fmt.Errorf("unexpected binary op %q", b.op) - } - if err := b.x.Check(vars); err != nil { - return err - } - return b.y.Check(vars) + if !strings.ContainsRune("+-*/", b.op) { + return fmt.Errorf("unexpected binary op %q", b.op) + } + if err := b.x.Check(vars); err != nil { + return err + } + return b.y.Check(vars) } func (c call) Check(vars map[Var]bool) error { - arity, ok := numParams[c.fn] - if !ok { - return fmt.Errorf("unknown function %q", c.fn) - } - if len(c.args) != arity { - return fmt.Errorf("call to %s has %d args, want %d", - c.fn, len(c.args), arity) - } - for _, arg := range c.args { - if err := arg.Check(vars); err != nil { - return err - } - } - return nil + arity, ok := numParams[c.fn] + if !ok { + return fmt.Errorf("unknown function %q", c.fn) + } + if len(c.args) != arity { + return fmt.Errorf("call to %s has %d args, want %d", + c.fn, len(c.args), arity) + } + for _, arg := range c.args { + if err := arg.Check(vars); err != nil { + return err + } + } + return nil } var numParams = map[string]int{"pow": 2, "sin": 1, "sqrt": 1} @@ -255,27 +255,27 @@ Check方法的參數是一個Var類型的集合,這個集合聚集從表達式 這個ParseAndCheck函數混合了解析和檢査步驟的過程: ```go -// gopl.io/ch7/surface +gopl.io/ch7/surface import "gopl.io/ch7/eval" func parseAndCheck(s string) (eval.Expr, error) { - if s == "" { - return nil, fmt.Errorf("empty expression") - } - expr, err := eval.Parse(s) - if err != nil { - return nil, err - } - vars := make(map[eval.Var]bool) - if err := expr.Check(vars); err != nil { - return nil, err - } - for v := range vars { - if v != "x" && v != "y" && v != "r" { - return nil, fmt.Errorf("undefined variable: %s", v) - } - } - return expr, nil + if s == "" { + return nil, fmt.Errorf("empty expression") + } + expr, err := eval.Parse(s) + if err != nil { + return nil, err + } + vars := make(map[eval.Var]bool) + if err := expr.Check(vars); err != nil { + return nil, err + } + for v := range vars { + if v != "x" && v != "y" && v != "r" { + return nil, fmt.Errorf("undefined variable: %s", v) + } + } + return expr, nil } ``` @@ -283,17 +283,17 @@ func parseAndCheck(s string) (eval.Expr, error) { ```go func plot(w http.ResponseWriter, r *http.Request) { - r.ParseForm() - expr, err := parseAndCheck(r.Form.Get("expr")) - if err != nil { - http.Error(w, "bad expr: "+err.Error(), http.StatusBadRequest) - return - } - w.Header().Set("Content-Type", "image/svg+xml") - surface(w, func(x, y float64) float64 { - r := math.Hypot(x, y) // distance from (0,0) - return expr.Eval(eval.Env{"x": x, "y": y, "r": r}) - }) + r.ParseForm() + expr, err := parseAndCheck(r.Form.Get("expr")) + if err != nil { + http.Error(w, "bad expr: "+err.Error(), http.StatusBadRequest) + return + } + w.Header().Set("Content-Type", "image/svg+xml") + surface(w, func(x, y float64) float64 { + r := math.Hypot(x, y) // distance from (0,0) + return expr.Eval(eval.Env{"x": x, "y": y, "r": r}) + }) } ``` @@ -301,10 +301,10 @@ func plot(w http.ResponseWriter, r *http.Request) { 這個plot函數解析和檢査在HTTP請求中指定的表達式併且用它來創建一個兩個變量的匿名函數。這個匿名函數和來自原來surface-plotting程序中的固定函數f有相同的籤名,但是它計算一個用戶提供的表達式。環境變量中定義了x,y和半徑r。最後plot調用surface函數,它就是gopl.io/ch3/surface中的主要函數,脩改後它可以接受plot中的函數和輸出io.Writer作爲參數,而不是使用固定的函數f和os.Stdout。圖7.7中顯示了通過程序産生的3個麴面。 -練習7.13:爲Expr增加一個String方法來打印美觀的語法樹。當再一次解析的時候,檢査它的結果是否生成相同的語法樹。 +**練習 7.13:** 爲Expr增加一個String方法來打印美觀的語法樹。當再一次解析的時候,檢査它的結果是否生成相同的語法樹。 -練習7.14:定義一個新的滿足Expr接口的具體類型併且提供一個新的操作例如對它運算單元中的最小值的計算。因爲Parse函數不會創建這個新類型的實例,爲了使用它你可能需要直接構造一個語法樹(或者繼承parser接口)。 +**練習 7.14:** 定義一個新的滿足Expr接口的具體類型併且提供一個新的操作例如對它運算單元中的最小值的計算。因爲Parse函數不會創建這個新類型的實例,爲了使用它你可能需要直接構造一個語法樹(或者繼承parser接口)。 -練習7.15:編寫一個從標準輸入中讀取一個單一表達式的程序,用戶及時地提供對於任意變量的值,然後在結果環境變量中計算表達式的值。優雅的處理所有遇到的錯誤。 +**練習 7.15:** 編寫一個從標準輸入中讀取一個單一表達式的程序,用戶及時地提供對於任意變量的值,然後在結果環境變量中計算表達式的值。優雅的處理所有遇到的錯誤。 -練習7.16:編寫一個基於web的計算器程序。 +**練習 7.16:** 編寫一個基於web的計算器程序。 diff --git a/ch7/ch7-10.md b/ch7/ch7-10.md index 1b9ff22..1a11578 100644 --- a/ch7/ch7-10.md +++ b/ch7/ch7-10.md @@ -1,16 +1,20 @@ ## 7.10. 類型斷言 + 類型斷言是一個使用在接口值上的操作。語法上它看起來像x.(T)被稱爲斷言類型,這里x表示一個接口的類型和T表示一個類型。一個類型斷言檢査它操作對象的動態類型是否和斷言的類型匹配。 這里有兩種可能。第一種,如果斷言的類型T是一個具體類型,然後類型斷言檢査x的動態類型是否和T相同。如果這個檢査成功了,類型斷言的結果是x的動態值,當然它的類型是T。換句話説,具體類型的類型斷言從它的操作對象中獲得具體的值。如果檢査失敗,接下來這個操作會拋出panic。例如: + ```go var w io.Writer w = os.Stdout f := w.(*os.File) // success: f == os.Stdout c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer ``` + 第二種,如果相反斷言的類型T是一個接口類型,然後類型斷言檢査是否x的動態類型滿足T。如果這個檢査成功了,動態值沒有獲取到;這個結果仍然是一個有相同類型和值部分的接口值,但是結果有類型T。換句話説,對一個接口類型的類型斷言改變了類型的表述方式,改變了可以獲取的方法集合(通常更大),但是它保護了接口值內部的動態類型和值的部分。 在下面的第一個類型斷言後,w和rw都持有os.Stdout因此它們每個有一個動態類型*os.File,但是變量w是一個io.Writer類型隻對外公開出文件的Write方法,然而rw變量也隻公開它的Read方法。 + ```go var w io.Writer w = os.Stdout @@ -18,28 +22,36 @@ rw := w.(io.ReadWriter) // success: *os.File has both Read and Write w = new(ByteCounter) rw = w.(io.ReadWriter) // panic: *ByteCounter has no Read method ``` + 如果斷言操作的對象是一個nil接口值,那麽不論被斷言的類型是什麽這個類型斷言都會失敗。我們幾乎不需要對一個更少限製性的接口類型(更少的方法集合)做斷言,因爲它表現的就像賦值操作一樣,除了對於nil接口值的情況。 + ```go w = rw // io.ReadWriter is assignable to io.Writer w = rw.(io.Writer) // fails only if rw == nil ``` + 經常地我們對一個接口值的動態類型是不確定的,併且我們更願意去檢驗它是否是一些特定的類型。如果類型斷言出現在一個預期有兩個結果的賦值操作中,例如如下的定義,這個操作不會在失敗的時候發生panic但是代替地返迴一個額外的第二個結果,這個結果是一個標識成功的布爾值: + ```go var w io.Writer = os.Stdout f, ok := w.(*os.File) // success: ok, f == os.Stdout b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil ``` + 第二個結果常規地賦值給一個命名爲ok的變量。如果這個操作失敗了,那麽ok就是false值,第一個結果等於被斷言類型的零值,在這個例子中就是一個nil的*bytes.Buffer類型。 這個ok結果經常立卽用於決定程序下面做什麽。if語句的擴展格式讓這個變的很簡潔: + ```go if f, ok := w.(*os.File); ok { - // ...use f... + // ...use f... } ``` + 當類型斷言的操作對象是一個變量,你有時會看見原來的變量名重用而不是聲明一個新的本地變量,這個重用的變量會覆蓋原來的值,如下面這樣: + ```go if w, ok := w.(*os.File); ok { - // ...use w... + // ...use w... } ``` From 884ada9cd099fdd969fb1831e7269da8de6e26cb Mon Sep 17 00:00:00 2001 From: chai2010 Date: Mon, 18 Jan 2016 11:14:19 +0800 Subject: [PATCH 19/23] fmt --- ch5/ch5-02.md | 100 ++++++++++++++++++++++++------------------------ ch5/ch5-05.md | 2 +- ch5/ch5-08.md | 2 +- ch5/ch5-09.md | 8 ++-- ch6/ch6-01.md | 2 +- ch6/ch6-02-1.md | 2 +- ch6/ch6-02.md | 6 +-- ch6/ch6-05.md | 6 +-- ch7/ch7-01.md | 6 +-- ch7/ch7-02.md | 4 +- ch7/ch7-03.md | 28 +++++++++++++- ch7/ch7-07.md | 2 +- ch8/ch8-02.md | 4 +- ch8/ch8-05.md | 4 +- ch8/ch8-06.md | 9 ++--- ch8/ch8-07.md | 2 +- ch8/ch8-08.md | 2 +- ch8/ch8-09.md | 6 +-- ch8/ch8-10.md | 11 ++++-- ch9/ch9-01.md | 2 +- 20 files changed, 116 insertions(+), 92 deletions(-) diff --git a/ch5/ch5-02.md b/ch5/ch5-02.md index 0c0f77c..9a06d21 100644 --- a/ch5/ch5-02.md +++ b/ch5/ch5-02.md @@ -11,25 +11,25 @@ golang.org/x/net/html package html type Node struct { - Type NodeType - Data string - Attr []Attribute - FirstChild, NextSibling *Node + Type NodeType + Data string + Attr []Attribute + FirstChild, NextSibling *Node } type NodeType int32 const ( - ErrorNode NodeType = iota - TextNode - DocumentNode - ElementNode - CommentNode - DoctypeNode + ErrorNode NodeType = iota + TextNode + DocumentNode + ElementNode + CommentNode + DoctypeNode ) type Attribute struct { - Key, Val string + Key, Val string } func Parse(r io.Reader) (*Node, error) @@ -43,21 +43,21 @@ gopl.io/ch5/findlinks1 package main import ( - "fmt" - "os" + "fmt" + "os" - "golang.org/x/net/html" + "golang.org/x/net/html" ) func main() { - doc, err := html.Parse(os.Stdin) - if err != nil { - fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) - os.Exit(1) - } - for _, link := range visit(nil, doc) { - fmt.Println(link) - } + doc, err := html.Parse(os.Stdin) + if err != nil { + fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err) + os.Exit(1) + } + for _, link := range visit(nil, doc) { + fmt.Println(link) + } } ``` @@ -66,17 +66,17 @@ visit函數遍歷HTML的節點樹,從每一個anchor元素的href屬性獲得l ```Go // visit appends to links each link found in n and returns the result. func visit(links []string, n *html.Node) []string { - if n.Type == html.ElementNode && n.Data == "a" { - for _, a := range n.Attr { - if a.Key == "href" { - links = append(links, a.Val) - } - } - } - for c := n.FirstChild; c != nil; c = c.NextSibling { - links = visit(links, c) - } - return links + if n.Type == html.ElementNode && n.Data == "a" { + for _, a := range n.Attr { + if a.Key == "href" { + links = append(links, a.Val) + } + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + links = visit(links, c) + } + return links } ``` @@ -109,21 +109,21 @@ http://www.google.com/intl/en/policies/privacy/ ```Go gopl.io/ch5/outline func main() { - doc, err := html.Parse(os.Stdin) - if err != nil { - fmt.Fprintf(os.Stderr, "outline: %v\n", err) - os.Exit(1) - } - outline(nil, doc) + doc, err := html.Parse(os.Stdin) + if err != nil { + fmt.Fprintf(os.Stderr, "outline: %v\n", err) + os.Exit(1) + } + outline(nil, doc) } func outline(stack []string, n *html.Node) { - if n.Type == html.ElementNode { - stack = append(stack, n.Data) // push tag - fmt.Println(stack) - } - for c := n.FirstChild; c != nil; c = c.NextSibling { - outline(stack, c) - } + if n.Type == html.ElementNode { + stack = append(stack, n.Data) // push tag + fmt.Println(stack) + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + outline(stack, c) + } } ``` @@ -153,10 +153,10 @@ $ ./fetch https://golang.org | ./outline 大部分編程語言使用固定大小的函數調用棧,常見的大小從64KB到2MB不等。固定大小棧會限製遞歸的深度,當你用遞歸處理大量數據時,需要避免棧溢出;除此之外,還會導致安全性問題。與相反,Go語言使用可變棧,棧的大小按需增加(初始時很小)。這使得我們使用遞歸時不必考慮溢出和安全問題。 -練習**5.1** :脩改findlinks代碼中遍歷n.FirstChild鏈表的部分,將循環調用visit,改成遞歸調用。 +**練習 5.1:** 脩改findlinks代碼中遍歷n.FirstChild鏈表的部分,將循環調用visit,改成遞歸調用。 -練習**5.2** : 編寫函數,記録在HTML樹中出現的同名元素的次數。 +**練習 5.2:** 編寫函數,記録在HTML樹中出現的同名元素的次數。 -練習**5.3** : 編寫函數輸出所有text結點的內容。註意不要訪問`