天天看點

《R和Ruby資料分析之旅》—第1章 1.1節Ruby

本節書摘來自異步社群《r和ruby資料分析之旅》一書中的第1章,第1.1節ruby,作者【新加坡】sau sheong chang,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

第1章 握住探險之鞭——認識ruby

r和ruby資料分析之旅

《奪寶奇兵》一直是我最喜歡的系列電影。在我年少時,哈裡森·福特就是我心中的英雄。我一直很喜歡印第安納·瓊斯抽鞭子的樣子。其實正是在《奪寶奇兵》裡,我第一次知道鞭子是什麼東西。

《奪寶奇兵》最早的兩部——《法櫃奇兵》和《魔域奇兵》中,印第安納正值壯年,堅定剛毅,脾氣暴躁。在我看過這兩部之後,心裡就對他标志性的帽子和鞭子産生了疑惑——為什麼一定要戴那樣一頂氈帽,為什麼居然要拿一條鞭子?

最後,所有的答案都在第三部《聖戰奇兵》中揭曉了。這部電影告訴了我們印第安納的身世,解釋了帽子和鞭子的事情,還告訴了我們他為什麼要做所有這一切。那可真是驚喜的一刻——雖說在大背景下其實顯得并不那麼重要。

那麼,這跟一本程式設計書有何幹系呢?正像印第安納離不開帽子和鞭子一樣,我們這本書也将主要使用兩種工具——ruby和r。帽子和鞭子作為野外考察的工具,對考古學教授來說不同尋常,同樣,在探索周圍世界方面,ruby和r也并不尋常。它們能夠讓事情變得更有意思。

1.1 ruby

這兩種工具都各需一章專門介紹。我們首先來介紹ruby,下一章再介紹r。顯然,我肯定無法在書裡僅用一章就把ruby程式設計語言完完整整地解釋一遍。是以,我打算提供足夠的資訊供你開開胃,希望能夠吸引你去閱讀更深入探讨ruby的好書。

1.1.1 為什麼用ruby

你首先可能就會問(除非你已經對ruby充滿熱情,已經知道為什麼了,要是這樣的話,你接下來點點頭就可以了),為什麼在這本書裡我要選擇ruby作為一種主要工具。我有很多很好的理由。不過具體到我們這本書的目标上,我想集中說以下兩點。

第一,ruby是一款非常适合人類的程式設計語言。ruby的創造者松本行弘經常說,他試圖使ruby很自然,而非很簡單,讓它就像能夠反映我們的生活一樣。用ruby程式設計很像是在和你的好朋友——計算機交談。ruby的設計本意就是讓程式設計更有意思,讓人們集中精力在程式設計中埋頭苦幹。例如,要在螢幕上顯示“i love ruby”(我愛ruby)10遍,隻需要告訴計算機:

如果你對c以及它的同族語言(比如java)很熟悉,可能已經知道,要檢查某個變量a_statement是否為真,你需要做這樣的事情(注意,在c中需要使用整數1來代替true,因為c中沒有布爾類型):

而你在ruby中當然也能做同樣的事情,你可以這樣做:

這樣的代碼非常容易閱讀,是以也就很容易維護。盡管ruby有時也會很深奧,但通常情況下,它都是一種使他人易于閱讀和了解的程式設計語言。你也可以想象到,對于本書,這将是一種非常有用的特征。

第二,ruby是一種動态的語言。對于作為讀者的你來說,這一點也就意味着,你可以從本書中複制代碼,扔到檔案(或者是你後面要看到的互動式ruby shell)裡,然後直接運作。不需要對makefile進行亂七八糟的設定,不需要得到庫的正确路徑,也不需要在運作示例之前先編譯編譯器。剪切,粘貼,運作——這就是需要做的一切。

1.1.2 安裝ruby

在使用ruby之前,我們當然要首先把它安裝到機器上。這是個很簡單的準備活動。怎樣安裝主要有三種選擇,選擇哪種取決于你的熱情有多麼高漲。

1.從源代碼安裝

如果你雄心勃勃,可以嘗試編譯ruby。這意味着你的平台上需要有能夠編譯ruby的工具。是以,除非你确實想要嚴肅對待ruby,否則我還是建議你安裝已編譯的:利用第三方工具,或利用你的平台上管理軟體包的工具。

2.利用第三方工具安裝

或者,你也可以選擇那些很流行的第三方工具。我推薦你使用最為流行的工具,在os x或linux上是ruby版本管理器(ruby version manager),在windows上是rubyinstaller。

ruby版本管理器

ruby版本管理器(rvm)可能是非windows平台上最為流行的第三方工具了。使用rvm有一個鮮明的優點,就是能夠安裝各種版本的ruby,并且能夠很容易地在各個版本之間切換。安裝rvm雖然不是非常困難,但也并非一蹴而就。至少在今天來說,安裝rvm要這樣進行。

首先,你必須安裝git和curl。然後,在指令行中輸入以下指令:

然後,用下述指令重新載入shell(或者是類似的其他指令,這取決于你的shell):

這時你就可以運作rvm了。接下來要做的是,檢查是否已經具備了安裝ruby的所有條件:

一旦準備齊全,敲入rvm指令來安裝你想要的ruby版本。我們的示例中将安裝ruby 1.9.3:

這之後,檢查你想要的ruby版本是否已經正确安裝:

你可以看到一系列(或者至少應該有一個)安裝好的rvm ruby。如果這是你第一次安裝,那應該還沒有預設的ruby,是以你需要按下列指令設定一個:

rubyinstaller

3.使用平台上的軟體包管理工具安裝ruby

如果以上方法都不适合你,你可以選擇使用自己系統裡的軟體包管理工具,對于debian系統(也包括ubuntu),你可以使用下面的指令:

$ sudo apt-get install ruby1.9.1

這個指令能夠安裝ruby 1.9.2。的确有點不可思議。

對于mac機器而言,ruby已經包含在os x之中了,但通常是個較老的版本(lion包含了ruby 1.8.7,更早的系統包含的則是更老的ruby版本)。在os x下有一款很流行的軟體包管理工具,名為homebrew,它能夠幫助你替換成最新版的ruby。你可能猜到了,需要首先安裝homebrew。在指令行下輸入以下指令:

然後用這個簡單的指令安裝ruby:

homebrew實際上正是一組ruby腳本的集合。

1.1.3 運作ruby

一旦你按照前面介紹的方法裝好了ruby,就是該開始使用它的時候了!ruby與c、c++、java等需要編譯的語言不同,在運作ruby之前,你無須一個中間步驟來生成可執行檔案。

運作ruby代碼有若幹種方法,但入門的最簡單方法大概就是使用安裝過程中已内置的互動式ruby工具。irb是一種ruby repl(讀入-求值-列印循環)應用,它提供了互動式的程式設計環境,使你能夠鍵入ruby指令并實時地求出結果:

請注意,一旦你輸入了一條ruby語句(在例子中,我們将字元串“hello world!”放到标準輸出中),語句會立即被求值,使得“hello world!”被顯示到螢幕上。在這之後,irb告訴你這條語句的值是空值(nil),因為ruby中的puts語句傳回一個空值。如果輸入了這樣的一條語句:

則會傳回2,這正是它求值的結果。irb是一種能夠快速上手的工具,而且,任何時候你如果不确定結果是什麼,都可以向它尋求幫助。

另外一種運作ruby的常見方式則是将代碼儲存為檔案,之後通過ruby解釋器來運作檔案。例如,你可以将“hello world!”儲存到名為hello_world.rb的檔案中。之後,你可以在指令行裡輸入這樣的指令:

本書中的大部分示例都是通過這種方式來運作的。

1.1.4 引用外部庫

在寫一些簡單的ruby程式時,可能僅用ruby自帶的庫就足夠了;但更多的時候,需要一些外部庫讓開發更輕松。随ruby預裝的庫有以下兩個。

核心。

這是ruby語言預設的類和子產品的集合,包含字元串(string)、數組(array)等。

标準庫。這些庫可以在ruby源代碼包的/lib目錄下找到。它們随ruby釋出,但在運作時并未被預設包含。這些庫包括base64、open uri,以及一些與網絡有關的包(http、imap、smtp等)。

想要使用标準庫或其他ruby核心以外的庫,需要在程式中用require語句來請求它們,例如:

除标準庫外,你将經常用到其他的外部庫,或是由ruby社群開發的,或者幹脆是你自己開發的。最常用的釋出ruby庫的手段是使用rubygems——ruby的包管理器。作為ruby的一部分,它随标準庫一起釋出。是以,你可以在安裝ruby後立即使用它。

就像apt-get和yum管理linux發行版中的軟體包一樣,rubygems允許你輕松地安裝或删除ruby的庫或應用程式。通過rubygems釋出的庫或應用程式,需要被打包成稱作gem的東西,其中包含一系列待安裝的檔案,以及對包進行描述的中繼資料檔案。

gem可以在本地釋出(通過擴充名為_._gem的檔案),也可以通過gem伺服器遠端釋出。過去有一些公用的gem伺服器為gem提供托管服務,包括rubyforge、github和gemcutter。但最近它們不同程度地為rubygems所取代。在rubygems的術語中,gem伺服器又被稱做源(source)。你可以部署自己私人的gem伺服器,在其上釋出預先打包好的、供内部使用的私有gem。

想在安裝好的rubygems中添加源,可以使用這條指令:

若想安裝本地gem,則可在控制台使用如下指令:

可以省略–local選項,但這樣會多花一些時間,因為指令執行後會先搜尋遠端的源,使用“本地”(local)選項會告知rubygems省略這一過程。

若想從遠端的源中安裝gem,通常可以這樣做:

也可以通過下面的指令來安裝某個gem的特定版本:

要列出本地已安裝的所有gem,可以這樣做:

1.1.5 ruby基礎

安裝結束後,我們就可以開始使用ruby了!

1.字元串

處理字元串(string)是程式中通常要做的基本工作之一。任何名副其實的程式設計語言都會提供一些處理字元串的手段,ruby也不例外。實際上,ruby在這方面功能堪稱強大。

ruby中所謂的字元串,就是由字元組成的序列。有若幹種方法來定義字元串。最常見的方法可能是用單引号(')或雙引号(")把它們括起來。如果用雙引号,可以在其中使用轉義序列,或用#``{代碼}表達式來嵌入可執行的ruby代碼,實際包含在字元串中的是代碼的執行結果,而并非代碼本身。在被單引号括起來的字元串中,則不能這樣做:

字元串還可以通過%q和%q來定義。%q與單引号作用相似,%q則與雙引号作用相似,唯一的差別在于,在%q和%q之後可以采用任意成對的界定符來括起字元串,例如:

最後,你還可以使用here-document來定義字元串。here-document是一種在shell指令行(如sh、csh、ksh、bash等)下或一些腳本語言(如perl、php、python,當然,也包括ruby)中界定字元串的方法。here-document會将輸入文本中的換行符和其他空白字元(包括行首的縮進)原樣保留:

注意,here-document中的界定符是第一行中<<以後的字元串,在上例中,乃是end_of_string。

我雖然無法在本節中列出ruby提供的所有字元串操作功能,但是可以給大家略舉以下幾例。

2.數組和散列

與字元串同等重要,甚至可能更重要的,是操縱資料結構的能力。最重要的也是會在本書中(同樣也在任何的ruby程式設計工作中)經常出現的兩種資料結構是數組和散列。

數組是帶下标的資料容器,可存儲一系列的對象。可以用方括号([])或array類來建立數組。數組用以0開頭的整數來索引,用[]操作符實作。

還有一些其他方法來索引數組,包括區間:

為數組的某個元素指派時,我們同樣使用方括号:

數組元素可以是任意的對象,包括其他數組:

如果你對操縱資料結構比較熟悉,你可能會好奇我為什麼在這一節裡僅提及數組和散列。其他那些常見的資料結構(如棧、隊列、集合等)哪兒去了呢?實際上,這些都可以用數組來實作:

還有許多其他方法可以被用在數組上,你可以在ruby網站的參考文檔中找到它們,或者更進一步,打開irb來玩一玩它們。一種常見的疊代周遊數組的手段是使用each方法:

上述程式會把數組的每個元素在标準輸出(即控制台)中輸出一遍。代碼中的循環由do開始,以end結束,它對數組的4個元素各執行一次。這裡,我們選擇名叫item的變量來代表循環中的數組元素,并用豎線将變量名括起來。有時候,為了簡潔,我們用一對大括号{}來替換do…end。上述代碼會産生如下結果:

注意,數組元素是按照定義時的順序輸出的。

數組有衆多的方法,你還應該了解的是,array是enumerable(可枚舉)子產品的一個繼承,該子產品已經實作了那些方法。我們很快會講到enumerable子產品。

散列可以被視為字典或映射,是一種可以索引一組對象的資料結構。與數組的主要不同在于,數組的下标是一個整數,而散列的下标可以是任意對象。散列可用大括号{}或hash類來定義,并用方括号來索引。

為散列的一個元素指派同樣是用方括号:

給散列的鍵(即索引下标)指派的散列火箭(hash rocket)1風格文法在ruby 1.9版本中被做了改進。原有的文法仍然可用,但新的文法更簡單、更清爽。下面的兩行代碼做的是同一件事情:

就像之前我們用豎線将表示數組元素的item變量名括起來一樣,這裡我們用豎線将兩個變量名括了起來。第一個代表每一個散列元素的鍵,第二個則代表該元素的值。上述代碼将産生如下結果:

數組和散列都是從enumerable(可枚舉)子產品繼承而來,換句話說,它們都是enumerable的子類。enumerable是一個提供了具有一系列能力——包括一些周遊、查找和排序方法——的集合類型的子產品。enumerable提供的一個非常有用的方法是map方法,該方法針對集合的每一個元素,執行語句塊中提供的操作,并且傳回由每個操作結果組成的新數組。下例中map方法的輸入是一個數字區間(從1到4),輸出的是每一個輸入資料的平方。

3.符号

ruby包含了符号(symbol)的概念,它們是一些名稱常量。符号由冒号開頭,跟着是一個名字。例如,:north和:counter就是符号。符号在你需要某種類型的辨別符的情形下十分有用。這時如果使用字元串,會造成浪費,因為每生成一個字元串,都要建立一個新的對象。而符号一旦定義,每次用到總會引用起初定義的唯一的那個對象。

4.條件和循環

如果你做過某種程式設計工作,那麼對ruby中的條件和循環就應該不陌生。ruby具有直接或間接的c語言血統,是以它的條件文法跟c的文法十分類似。

if和unless

if表達式和其他語言中的非常像:

在每條語句都獨占一行的情況下,then關鍵字是可選的。if語句的否定和相反的形式是unless語句:

有時,當沒有else語句時,if和unless可以被用作條件修飾符,來表示語句會在條件被滿足的情況下執行。

前述代碼中,wag(搖動)方法在pet(寵物)對象屬于dog(狗)類時才被執行。bark(叫)方法在訪客(visitor)是一個朋友(friend)時才不被執行。

最後,像c語言一樣,ruby可識别三元條件表達式:

case表達式

在ruby中,有兩種使用case表達式的方法。一種是像一系列的if和elsif語句那樣:

第二種是更常見的,由case語句標明一個目标,每個when子句分别跟目标做一次比較:

循環

ruby中兩種主要的循環機制是while和它的否定形式until。while循環執行在條件滿足時執行循環體,這意味着循環體可能被執行0次或以上。until則相反,反複執行循環體直到條件為真為止。

可以看出,兩種形式做了完全相同的事情。那為什麼要同時提供這兩種形式呢?要記得ruby是具有表現力的,并且常常努力讓程式更為智能化。盡管兩種形式是相同的,但有時用其中的某一種會比另一種更自然。

像if和unless一樣,while和until也可以被用作語句修飾符:

1.1.6 一切皆對象

關于ruby,你可能經常耳聞的一個說法就是:在ruby中,一切都是對象。這聽起來有點極端,并且在技術的觀點上不完全正确。顯然,關鍵字,如if-else條件文法,并不是對象。然而你操作的所有東西确實都是對象。就連類也是對象,方法也如此。而且,所有求值的結果也都是對象。下面我們來看看這種機制的運作情況。

1.類和對象

建立對象的經典方法是通過某個類進行執行個體化:

如果你已經用其他語言做過某種形式的面向對象程式設計,這對你而言應該比較熟悉。如果你沒有做過,那麼就可能有一點迷惑,但很容易解釋清楚。上面的代碼定義了一個類,某種程度上好比一個模闆,你基于它來生成執行個體或對象。在本例中,我定義了一個dog(狗)類,它擁有breed(品種)和color(顔色)等屬性,并且每一個類的執行個體擁有一個特定的名字。attr關鍵字是一個方法調用,幫助建立3個執行個體變量(breed、color和name)以及一些通路這些變量的标準方法。ruby中的執行個體變量均以@開頭。

以def開頭的幾行定義了一些方法。方法是屬于對象并且在對象上調用的函數。上例中有兩個方法,即initialize(初始化)和bark(叫)。

initialize是一個便捷方法。每當ruby建立一個新對象時,總是會尋找并調用名為initialize的方法。在我們的initialize方法中,通過參數傳遞的值為每一個執行個體變量指派。

bark方法的功能很簡單——制造一次“喧鬧”。它的定義顯示了如何為參數設定預設值(上例中為softly),預設值在調用時未給該變量傳值的情況下起作用。

那麼我們如何通過dog類建立一個對象呢?

my_dog = dog.new('rover', :brown, 'cocker spaniel')

my_dog是一個變量,包含了由dog類執行個體化的一個對象,括号裡的幾個值則經由initialize方法給出了對象的名字、顔色和品種。

2.方法

前已提及,可以用def關鍵字來定義方法,def後跟方法名。方法定義可以有任意數量的參數,也可以沒有參數。如果你的方法不需要參數,你可以在定義時省略括号:

通過前面的dog類你可能已經注意到,你可以為方法的參數設定預設值時:

在上面的代碼中,volume參數預設值是:softly這一符号量。當你為參數設定了預設值時,你可以在調用方法時包含或省略參數值:

對于有多個參數的方法,通常的做法是将有預設值的參數放在沒有預設值的參數之後。如果沒有預設值的參數在後,設定預設值就變得沒有意義了,因為每次調用時,所有參數都必須指派。

方法總要傳回一個值,該值可以是一個數組,這樣就可以實作多個值的合并傳回。傳回值可以用return關鍵字來指定。如果到方法結束都沒有遇到return,傳回的将是方法中最後一個被求出的值。

3.類方法和變量

目前為止我們讨論了類的執行個體。前面的一個例子展現了從dog類到my_dog對象的執行個體化。其中的變量和方法實際上都是屬于my_dog對象的,并且也隻能被my_dog對象所調用。舉例來說,基于前面dog類的定義,你不能這樣做:

dog.bark

從邏輯上講,因為dog類是創造各種狗對象的模闆,調用dog類的bark方法,意味着讓所有的狗一起叫!然而,在某些情況下(如果你以前做過面向對象程式設計,你應該明白我在指什麼),我們需要使用屬于類而不是對象的方法甚至是變量。如何實作這個需求呢?

之前我提到,連類本身也是對象。我們下面要做的無非就是将一個類當做對象對待。要定義一個類方法,在方法名前面加上self即可:

self是一個代表目前對象的關鍵字(與c++或java類似)。當我們定義一個類的時候,目前對象就是這個正在定義的類。我們用self定義類中方法的時候,意味着将這個方法添加給類本身,而非類的某個執行個體。此時,我們是在為一個“類對象”,也即class類的一個執行個體,添加方法。當我們需要定義作用在類本身上的方法時,将會經常用到這一手段。

定義類變量是一件很直接的事情,在變量名前面加上@@即可:

注意,@@count這個類變量在類定義中被初始化為0,這項工作隻被執行一次。通常,在initialize方法中初始化類變量是錯誤的,因為initialize方法在每個新的對象被建立時都要被執行一次。這意味着,每建立一個新對象,類變量的值都要被重置一次!

4.繼承

繼承是面向對象程式設計的基石之一。ruby中的繼承十分簡便。要把一個類聲明為另一個類的子類,隻要在類定義中這樣做:

這樣就建立了名叫spaniel的子類,它繼承了dog類的一切,包括方法和成員變量。有一個問題:如果spaniel類是dog類的一個子類,那麼dog類又是誰的子類呢?你可以通過在dog類上調用superclass方法來找到答案。别忘了dog類本身也是一個對象,是以可以在其上直接調用某個方法:

可以看到,dog類是object類的一個子類(object類本身也是一個對象,你是不是感覺有點混亂?),而object類又是basicobject類的一個子類。basicobject則是繼承關系的終點,烏龜下面并非總有别的烏龜!2

我們已定義了spaniel類,當調用它的bark方法時會發生什麼呢?因為bark方法在spaniel類中并未定義,系統将會上溯到它的超類,也就是dog類,調用其中的同名方法。當然,如果ruby在dog類中也找不到bark方法,則會沿繼承層次繼續上溯,直到basicobject類,如果仍未找到,則抛出一個nomethoderror(找不到方法)異常。

你不能用一個子類同時繼承多個超類。有些程式設計語言支援多重繼承,但ruby僅支援單繼承。然而,ruby提供了模拟多重繼承的機制——mixin機制,使用子產品來實作。

子產品是一種将一些方法、類和常量組合在一個名字空間裡,避免命名沖突的手段。如果在ruby的類中包含了子產品,那麼就意味着讓mixin機制成為可能。因為當類中包含多個子產品時,我們就可以模仿多重繼承的效果。

讓我們繼續dog類的例子,為dog類定義一個名叫canine(犬科動物)的超類:

我們知道,狗也是寵物,那麼如果我們想将一些方法和變量組合成一個pet(寵物)類,我們怎樣能讓dog類繼承這些方法和變量呢?在ruby中,我們不能直接這樣做,因為它隻支援單繼承,但我們可以将pet變成一個子產品:

用這種手段,dog類就可以在不違背單繼承機制的情況下,同時繼承pet和canine的方法了。

關于mixin機制還有另外一個例子,記不記得在1.1.5節的“數組和散列”部分,我們說過array類和hash類都包含了enumerable子產品。

5.“像鴨子一樣程式設計”

ruby和python、php、smalltalk等語言,都是為人熟知的動态類型語言,而c和java等則是靜态類型語言。本質上,如果一個語言需要程式員在代碼中指定資料類型,并且編譯時會進行類型檢查,在類型不比對時報錯,那麼它就是靜态類型語言。與此相反,動态類型語言不需要在代碼中指定資料類型,并将類型檢查留到運作時進行。

例如,在java中,你需要先聲明變量再給它指派。

int count = 100;

然而,在ruby中,你隻需要:

count = 100

此時,你應當自覺地正确使用這個變量,這意味着,當你為變量賦了整數值時,你應當在代碼中把它作為整數來用。當你使用count變量時,ruby明白它是一個整數,并且你也應當把它當整數使用。如果你不這樣用,ruby會自動将它轉換成你試圖去把它視為的任何類型。這個機制被稱作鴨子類型。

鴨子類型背後的思想來自所謂的鴨子測試:如果一個東西走路像鴨子,叫聲也像鴨子,那麼它就是一隻鴨子。這意味着,對象的類型并不由對象所屬的類決定,而是由該對象可以做什麼來決定。

舉一個簡單的例子,我們定義名叫op的方法:

這個方法接受兩個參數,并傳回單個值。方法中既未指定參數類型,也未指定傳回值類型。這樣做是否會帶來潛在的bug呢?我們來看看如何使用這個方法。如果x和y都是字元串,傳回值也将是一個字元串,此時沒有問題:

當x是個數組、y是個字元串時,方法将y追加到x末尾,進而傳回一個新的數組:

當x和y都是整數時,方法會完成一個左移位操作,将二進制的1向左移動兩位,進而得到4(二進制的100):

這意味着什麼呢?鴨子類型機制是瑕瑜互見的。最明顯的缺點是使方法缺乏一緻性:在傳入不同的值時,方法的結果會大相徑庭,這一點在程式實際運作之前都不會被檢查。

一個主要的好處則是帶來了更簡單的代碼。如果你明确知道你在做什麼,它将帶來更易讀和易維護的代碼。

最後我們要說,鴨子類型不僅僅是ruby中一種固定的程式設計機制,它更像是一種哲學。如果你想確定op方法隻能在參數是字元串時才可用,你可以這樣做:

如果a或b并非字元串,程式将抛出一個異常。

1指派符号=>形似火箭——譯者注

2出自史蒂芬·霍金的《時間簡史》(bantam出版社):

一個著名的科學家(有人說是伯特蘭·羅素),曾經作過一次關于天文學的公開演講。他描述了地球如何圍繞太陽公轉,而太陽又是如何圍繞由衆多星體組成的銀河系的中心公轉。在演講結束的時候,一位坐在房間後方的老太太站起來說:“你講的都是胡說八道,我們的世界是一個馱在巨龜背上的平闆”。科學家莞爾一笑,回答說:“那麼那隻烏龜又站在什麼上面呢?”“你很聰明,年輕人,很聰明。”老太太說,“但烏龜下面還有烏龜!”

本文僅用于學習和交流目的,不代表異步社群觀點。非商業轉載請注明作譯者、出處,并保留本文的原始連結。

繼續閱讀