LJZN

每天更新Rails练习项目到Github~

» Home
» Category
» About Me
» Github

(翻译)规则从何来,梦想因何生

19 Mar 2016 » why

Why the Lucky Stiff

很多人都说可汗博士是个疯子。他曾试图活埋自己,还电死了自己的外甥女,事实上,他把一个退休老人炸上天。但是我认为他做这些都有很好的理由。

我敢肯定你的观点和大多数人一样,但是在他教你Ruby的类定义和混合之后,你或许会有一点点敬佩他。在本章结束后,我们可以忘记博士伤心的过去,并且不再叫他疯子。

1.一个被剥夺了选举权的人

让我们先来看看可汗博士的生平。

def dr_chams_timeline( year )
  case year
  when 1894
    "Born."
  when 1895..1913
    "Childhood in Lousville, Winston Co., Mississippi."
  when 1914..1919
    "Worked at a pecan nursery; punched a Quaker."
  when 1920..1928
    "Sailed in the Brotherhood of River Wisdomming, which journeyed \
     the Mississippi River and engaged in thoughtful self-improvement, \
     where he finished 140 credit hours from their Oarniversity."
  when 1929
    "Returned to Louisville to pen a novel about time-travelling pheasant hunters."
  when 1930..1933
    "Took up a respectable career insuring pecan nurseries.  Financially stable, he \
     spent time in Brazil and New Mexico, buying up rare paper-shell pecan trees.  Just \
     as his notariety came to a crescendo: gosh, he tried to buried himself alive."
  when 1934
    "Went back to writing his novel.  Changed the hunters to insurance tycoons and the \
     pheasants to Quakers."
  when 1935..1940
    "Took Arthur Cone, the Headmaster of the Brotherhood of River Wisdomming, as a \
     houseguest.  Together for five years, engineering and inventing."
  when 1941
    "And this is where things got interesting."
  end
end

def是一个核心方法,用于定义方法。我们可以到处乱用。

puts dr_chams_timeline( 1941 )

我们用case搭配when来实现选择,也可以用if和else来实现,看看。

case year
when 1894
  "Born."
when 1895..1913
  "Childhood in Lousville, Winston Co., Mississippi."
else
  "No information about this year."
end

等同于

if 1894 === year
  "Born."
elsif (1895..1913) === year
  "Childhood in Lousville, Winston Co., Mississippi."
else
  "No information about this year."
end

三个等号和两个等号的区别在于它更长,所以更有弹性。不需要完全等于,只需要包含于。

刚传来新闻,经过被炸上天的退休老人的孙女的调查,发现那个老人作恶多端罪有应得。

但可汗博士真的正常吗

他电死了自己的外甥女,但据我调查那是一次意外事故。

opus_magnum = true
def save_hannah
  success = opus_magnum
end

我们能拯救Hannah吗,Ruby会警告我们:这里没有opus_magnum。

在Ruby中,每个方法和块都有它的范围。

方法的定义范围就像是它的视野,在其中可以定义变量。你可以通过参数把数据传送给方法,方法也可以返回数据,但是方法里面的名字只在范围内有效。

一些变量有更广阔的范围,比如以$开头的全局变量,可以在任何地方使用。以@开头的实例变量和@@的类变量,都可以在一个类的范围中使用。

块也有范围,但更复杂。

verb = 'rescued'
['sedated', 'sprinkled', 'electrocuted'].
each do |verb|
  puts "Dr. Cham " + verb + " his niece Hannah."
end
puts "Finally, Dr. Cham " + verb + " his niece Hannah."

我们会看到

Dr. Cham sedated his niece Hannah. Dr. Cham sprinkled his niece Hannah. Dr. Cham electrocuted his niece Hannah. Finally, Dr. Cham rescued his niece Hannah.

是的,块变量有自己的范围,当块关闭了,块变量也随之消失。

['sedated', 'powdered', 'electrocuted'].
each do |verb|
  puts "Dr. Cham " + verb + " his niece Hannah."
end
puts "Yes, Dr. Cham " + verb + " his niece Hannah."

这里会出现错误,提示verb未定义。范围里的变量不可以用在外面。

出于对Hannah的愧疚,可汗博士乘坐他制造的飞行器离开了地球。降落在一个荒芜人眼的星球上。他在荒漠中行走了三天,终于发现了一片开满苹果花的花园,以及一座美丽的城堡。

2.有电脑的城堡

城堡看上去还有一段距离,可汗博士对星球许愿:请给我一匹马。但什么也没有出现。看来这个星球并不会读取和实现他的愿望。

不,这个星球可以读取思想,也可以回应愿望。只是不能同时做到。

我曾见过一个许愿机器。

require 'endertromb'
class WishMaker
  def initialize
    @energy = rand( 6 )
  end
  def grant( wish )
    if wish.length > 10 or wish.include? ' '
      raise ArgumentError, "Bad wish."
    end
    if @energy.zero?
      raise Exception, "No energy left."
    end
    @energy -= 1
    Endertromb::make( wish )
  end
end

对Ruby来说,这只是一个类定义,描述了一个特定的对象是如何工作的。

每天清晨,一个新的许愿机都被制造出来。

todays_wishes = WishMaker.new

new方法是一个类方法,它用于创造崭新的对象。它也会自动调用对象的initialize方法。在WishMaker的初始化定义中,你会看到一行@energy = rand( 6 )。

rand(6)会随机选取一个0到5的数字。这个数字代表今天的愿望数量。所以有时会没有愿望可以许。

这个随机数被赋值到一个实例变量之中,只可以在class之内使用。

实例变量通常用来存储一些有关于类的信息。

todays_wishes = WishMaker.new
todays_wishes.grant( "antlers" )

好吧,这并不是真的许愿机。只是一个例子,告诉你这类魔法机器是如何工作的。我不想听见你真的用它实现了愿望。

之前我们说过:Ruby的工作由两部分组成

1,定义东西

2,让东西动起来

什么是动作?方法。定义方法用def。定义类用class。

这有助于我们理解:Ruby中的所有东西都是对象。

number = 5
print number.next                   # prints '6'

phrase = 'wishing for antlers'
print phrase.length                 # prints '19'

todays_wishes = WishMaker.new
todays_wishes.grant( "antlers" )

自然地,每个对象都有它的类

print 5.class                       # prints 'Integer'
print 'wishing for antlers'.class   # prints 'String'
print WishMaker.new.class           # prints 'WishMaker'

在愿望制造机旁边,我发现了一张读心机的设计图

require 'endertromb'
class MindReader
  def initialize
    @minds = Endertromb::scan_for_sentience
  end
  def read
    @minds.collect do |mind|
       mind.read
    end
  end
end

你可以看到当新的读心机被创造时,会自动收集mind。这些mind似乎是存在一个数组里,因为之后调用了collect方法来进行迭代。

读心机和许愿机都提到了一个类Endertromb,它存储在endertromb.rb中。使用require ‘endertromb’来调用它。本书之后有一半的部分都在介绍各种有用的可以调用的类。

可汗博士的冒险

终于,可汗博士走到了城堡门口,敲了敲门。一只鲸鱼宝宝打开了门,可汗博士在城堡里和各种动物一起吃过饭之后,探索了城堡。在城堡的最底层,发现了一台电脑,以及一本书。书名是“Why的碉堡了的Ruby书”,他看完了自己的整个故事,甚至学习了Ruby。

在一台电脑上,他发现了irb。所以他开始输入:

irb> Object::constants
  => ["Marshal", "String", "Dir", "LoadError", "Float", ... and so on ]

这里显示了所有的顶级常量。类名也列在其中,所以这很方便于查看Ruby当前载入了什么。

他开始许找列表中不熟悉的东西

… “Struct”, “Values”, “Time”, “Elevator”, “Range” …

Elevator?没见过。

irb> Elevator::methods
  => ["method", "freeze", "allocate", ... another long list ... ]
irb> Elevator::class_variables
  => ['@@diagnostic_report', '@@power_circuit_active', '@@maintenance_password']
irb> Elevator::constants
  => []

看起来电梯类有一大堆的方法,大多是所有Ruby类都有的。比如freeze和allocate。freeze可以让这个类不被改变,而allocate可以在不调用初始化的情况下创造一个新的类对象。

类变量看上去很有趣,而常量显示在电梯类之中没有类。

他试着创建一个电梯对象。

irb> e = Elevator::new
ArgumentError: wrong number of arguments (0 for 1), requires a password
        from (irb):2:in `initialize'
        from (irb):2:in `new'
        from (irb):2
        from :0

他试了几个密码。

irb> e = Elevator::new( "going up" )
AccessDeniedError: bad password
irb> e = Elevator::new( "going_up" )
AccessDeniedError: bad password
irb> e = Elevator::new( "stairs_are_bad" )
AccessDeniedError: bad password
irb> e = Elevator::new( "StairsAreBad" )
AccessDeniedError: bad password

不起作用。喔,等等,在类变量的列表里有维护密码。

irb> Elevator::maintenance_password
NoMethodError: undefined method `maintenance_password' for Elevator:Class
        from (irb):1
        from :0

嗯,实例变量只在一个对象有用。而类变量只在一个类里有用。如何获取密码呢?

irb> class Elevator
irb>   def Elevator.maintenance_password
irb>     @@maintenance_password
irb>   end
irb> end
  => nil
irb> Elevator::maintenance_password
  => "stairs_are_history!"

好了!他得到了密码,你看见了吗?

他添加了一个类方法给电梯类。你只需要一个新的类定义就可以将你对类的改变添加到已有的定义中。

类方法的双冒号只是为了方便阅读。

密码大概是这样工作的:

class Elevator
  def initialize( pass )
    raise AccessDeniedError, "bad password" \
      unless pass.equals? @@maintenance_password
  end
end

给一个类加密似乎是没用的。因为Ruby中的任何东西都可以被调用和覆盖以及重塑。可汗博士创造了一个新的电梯:

irb> e = Elevator.new( "stairs_are_history!" )
#<Elevator:0x81f12f4 @level=4>
irb> e.level = 1

屏幕后面打开了一扇电梯门,可汗博士走进了电梯,按下了4.

3.关于我女儿的手风琴教师的故事

“瞎说!你根本没有女儿。”好吧,放轻松,我的确没有女儿。但这不影响我重视她的音乐教育。

他来自于Endertromb星球。他叫Paij-ree。他说话时总会带着家乡方言,比如:“你(tyrr)好,我是(gdfg)Paij-ree(gfh)。”

我给他看了我写的一段关于他的程序

def wipe_mutterings_from( sentence )
  while sentence.include? '('
    open = sentence.index( '(' )
    close = sentence.index( ')', open )
    sentence[open..close] = '' if close
  end
end

“这可以帮你把句子中的方言去掉。”我说。

what_he_said = "But, strangely (em-pithy-dah),
  I learned upon, played upon (pon-shoo) the
  organs on my home (oth-rea) planet."
wipe_mutterings_from( what_he_said )
print what_he_said

然后我们得到了

But, strangely , I learned upon, played upon the organs on my home planet.

“我想这个程序还不够优雅。”他说。是的,这里有几处不完善的地方。第一:这个方法是整理一个字符串,如果输入了一个数字会发生什么?

NoMethodError: undefined method `include?' for 1:Fixnum
        from (irb):2:in `wipe_mutterings_from'
        from (irb):8

幸运的是,我们可以用异常通知来告诉用户他们犯了什么错误。

def wipe_mutterings_from( sentence )
  unless sentence.respond_to? :include?
    raise ArgumentError,
      "cannot wipe mutterings from a #{ sentence.class }"
  end
  while sentence.include? '('
    open = sentence.index( '(' )
    close = sentence.index( ')', open )
    sentence[open..close] = '' if close
  end
end

这时对于输入的数字会返回

ArgumentError: cannot wipe mutterings from a Fixnum
        from (irb):3:in `wipe_mutterings_from'
        from (irb):12

respond_to?方法会检查对象是否具有特定的方法。

接着,第二:你注意到了我们的方法是如何改变句子的吗?

something_said = "A (gith) spaceship."
wipe_mutterings_from( something_said )
print something_said

直接改变而不做备份是很不好的习惯。我们只需要复制一下字符串:

def wipe_mutterings_from( sentence )
  unless sentence.respond_to? :include?
    raise ArgumentError,
      "cannot wipe mutterings from a #{ sentence.class }"
  end
  sentence = sentence.dup
  while sentence.include? '('
    open = sentence.index( '(' )
    close = sentence.index( ')', open )
    sentence[open..close] = '' if close
  end
  sentence
end

这样,我们就可以不改变原来的字符串。 复制是如何工作的?还记得变量名只是一个昵称吗?我们这里有许多例子说明这一点:

x = 5
x = x + 1
# x now equals 6

y = "Endertromb"
y = y.length
# y now equals 10

z = :include?
z = "a string".respond_to? z
# z now equals true

如果你不能通过一个变量名找到一个对象,那么Ruby就会把它送到垃圾回收处清理掉。

有些东西不能被dup。数字,符号(:death)。它们是独特的。还有nil,true,false.

也许第三是最简单的。字符串的.[]方法有很多用法:

str = "A string is a long shelf of letters and spaces."
puts str[0]       # prints 'A'
puts str[0..-1]   # prints 'A string is a long shelf of letters and spaces.'
puts str[1..-2]   # prints ' string is a long shelf of letters and spaces'
puts str[0, 3]    # prints 'A s'
puts str['shelf'] # prints 'shelf'

最后的第四:这个方法有可能进入死循环。

def wipe_mutterings_from( sentence )
  unless sentence.respond_to? :include?
    raise ArgumentError,
      "cannot wipe mutterings from a #{ sentence.class }"
  end
  sentence = sentence.dup
  while sentence.include? '('
    open = sentence.index( '(' )
    close = sentence.index( ')', open )
    sentence[open..close] = '' if close
  end
  sentence
end

让我们试试这个输入:

muddy_stick = "Here's a ( curve."
wipe_mutterings_from( muddy_stick )

看看我是怎么解决这个问题的:

def wipe_mutterings_from( sentence )
  unless sentence.respond_to? :gsub
    raise ArgumentError,
      "cannot wipe mutterings from a #{ sentence.class }"
  end
  sentence.gsub( /\([-\w]+\)/, '' )
end

用一个枚举,配合正则表达式很方便。

总结一下:

1,当用户给你的方法输入了奇怪的东西,快报警。

2,修改东西不要用破坏性方法,可以用dup备份一个。

3,[]可以对字符串进行各种操作。

4,尽量不要用if和until。

叫名机

我的外星朋友Paji-ree的名字有很多叫法

class String

  # The parts of my daughter's organ
  # instructor's name.
  @@syllables = [
    { 'Paij' => 'Personal',
      'Gonk' => 'Business',
      'Blon' => 'Slave',
      'Stro' => 'Master',
      'Wert' => 'Father',
      'Onnn' => 'Mother' },
    { 'ree'  => 'AM',
      'plo'  => 'PM' }
  ]

  # A method to determine what a
  # certain name of his means.
  def name_significance
    parts = self.split( '-' )
    syllables = @@syllables.dup
    signif = parts.collect do |p|
      syllables.shift[p]
    end
    signif.join( ' ' )
  end

end

我们居然做了一件大部分编程语言都不会允许的事,修改了String类!

我在String里添加了两样东西:一个类变量,一个普通的实例方法。

print "Paij-ree".name_significance
#=> Personal AM

很好,这个方法可以使用在任何字符串中。

class String
  def dash_split
    self.split( '-' )
  end
end

再一次,我们为String添加了新方法。

"Gonk-plo".dash_split
#=> ['Gonk', 'plo']

还能这样写

class String
  def dash_split; split( '-' ); end
end

回到之前的代码,我们仔细看一下

signif = parts.collect do |p|
  syllables.shift[p]
end

collect方法与each的不同之处在于,它会将块的结果保存到一个新的数组中。

catsandtips = [0.12, 0.63, 0.09].collect { |catcost| catcost + ( catcost * 0.20 ) }

4.看电影的山羊

可汗博士到达了4楼,遇见了一只外星山羊,他们一起看了一部关于安不螺丝的纪录片。

在Ruby中,Object是非常中心的东西。是万物之源。

class ToastyBear < Object; end

小于号代表继承。父类的所有方法和常量都可以被子类继承。不过,,,上面的代码等于

class ToastyBear; end

我们用特定的格式来判断邮件,因为它们都具有相同特点。

def mail_them_a_kit( address )
  unless address.is_a? Address
    raise ArgumentError, "No Address object found."
  end
  print address.formatted
end

我们建立一个自己的数组类

class ArrayMine < Array
  # Build a string from this array, formatting each entry
  # then joining them together.
  def join( sep = $,, format = "%s" )
    collect do |item|
      sprintf( format, item )
    end.join( sep )
  end
end

我们可以确认一下它的父类

irb> ArrayMine.superclass
  => Array

完美,现在我们可以将我们的数组设置成房间大小,让它们在一个句子里输出。

rooms = ArrayMine[3, 4, 6]
print "We have " + rooms.join( ", ", "%d bed" ) + " rooms available."

结果是“We have 3 bed, 4 bed, 6 bed rooms available.”

让我们来看一下Ruby中的等级关系:

irb> Class.superclass
  => Module
irb> Kernel.class
  => Module
irb> Module.superclass
  => Object
irb> Object.superclass
  => nil

让我们来比喻一下,Object是国王,Module是城主,Kernel是上校,Class是老师。

伟大的城主母亲给了所有人一个容身之所:

# See, here is the module -- where else could our code possibly stay?
module WatchfulSaintAgnes

 # A CONSTANT is laying here by the doorway.  Fine.
  TOOTHLESS_MAN_WITH_FORK = ['man', 'fork', 'exposed gums']

  # A Class is eating, living well in the kitchen.
  class FatWaxyChild; end

  # A Method is hiding back in the banana closet, God knows why.
  def timid_foxfaced_girl; {'please' => 'i want an acorn please'}; end

end

来通过SaintAgnes找到他们:

>> WatchfulSaintAgnes::TOOTHLESS_MAN_WITH_FORK
=> ["man", "fork", "exposed gums"]
>> WatchfulSaintAgnes::FatWaxyChild.new
=> #<WatchfulSaintAgnes::FatWaxyChild:0xb7d2ad78>
>> WatchfulSaintAgnes::instance_methods
=> ["timid_foxfaced_girl"]

Module没有自我意识,所以不能够新建。

>> WatchfulSaintAgnes.new
NoMethodError: undefined method `new' for WatchfulSaintAgnes:Module
        from (irb):2

但我们还可以使用extend方法把一个module的所有方法放到一个class里:

>> class TheTimeWarnerAolCitibankCaringAndLovingFacility; end
>> TheTimeWarnerAolCitibankCaringAndLovingFacility.extend WatchfulSaintAgnes
>> TheTimeWarnerAolCitibankCaringAndLovingFacility::instance_methods
=> ["timid_foxfaced_girl"]

并没有偷走,而是借走。这些方法现在有两个地址。

追寻声音

可汗博士和山羊穿过魔法门找到了死去的汉娜。为了复活汉娜,博士必须找到一台可以编程的电脑。

5,彩票之都的小偷

让我们回到Paji-ree的星球。有一天,他的爸爸决定开始卖彩票。

class LotteryTicket

  NUMERIC_RANGE = 1..25

  attr_reader :picks, :purchased

  def initialize( *picks )
    if picks.length != 3
      raise ArgumentError, "three numbers must be picked"
    elsif picks.uniq.length != 3
      raise ArgumentError, "the three picks must be different numbers"
    elsif picks.detect { |p| not NUMERIC_RANGE === p }
      raise ArgumentError, "the three picks must be numbers between 1 and 25"
    end
    @picks = picks
    @purchased = Time.now
  end

end

在初始化参数中的星号意思是任何参数都会以数组的形式传送。所以之后我们才可以使用数组特有的方法,例如uniq,detect。

这个类里包含两个定义:方法定义def,属性定义attr_reader。其实它们都是方法定义。

attr_reader的完整代码是:

class LotteryTicket
  def picks; @picks; end
  def purchased; @purchased; end
end

这样我们就可以方便地在外部调用实例变量。

我们来随机创造一组彩票号码:

ticket = LotteryTicket.new( rand( 25 ) + 1,
            rand( 25 ) + 1, rand( 25 ) + 1 )
p ticket.picks

然而,我们不能从外部直接修改彩票号码。

ticket.picks = [2, 6, 19]

出现了一个错误:未定义的方法‘picks=’。这是因为attr_reader之天添加了一个可读方法,没有可写方法。没关系,毕竟我们不希望数字或者日期被更改。

所以我们为LotteryTicket类添加了新的方法方便生成随机号码:

class LotteryTicket
  def self.new_random
    new( rand( 25 ) + 1, rand( 25 ) + 1, rand( 25 ) + 1 )
  end
end

如果两个数碰巧相同,程序因该自动重新生成:

class LotteryTicket
  def self.new_random
    new( rand( 25 ) + 1, rand( 25 ) + 1, rand( 25 ) + 1 )
  rescue ArgumentError
    retry
  end
end

彩票之城为每个人保存彩票购买记录

class LotteryDraw
  @@tickets = {}
  def LotteryDraw.buy( customer, *tickets )
    unless @@tickets.has_key?( customer )
      @@tickets[customer] = []
    end
    @@tickets[customer] += tickets
  end
end

很快迎来了第一位客户

LotteryDraw.buy 'Yal-dal-rip-sip',
    LotteryTicket.new( 12, 6, 19 ),
    LotteryTicket.new( 5, 1, 3 ),
    LotteryTicket.new( 24, 6, 8 )

开奖系统

class LotteryTicket
  def score( final )
    count = 0
    final.picks.each do |note|
      count +=1 if picks.include? note
    end
    count
  end
end

它是这样使用的

irb> ticket = LotteryTicket.new( 2, 5, 19 )
irb> winner = LotteryTicket.new( 4, 5, 19 )
irb> ticket.score( winner )
  => 2

全自动的开奖系统

class << LotteryDraw
  def play
    final = LotteryTicket.new_random
    winners = {}
    @@tickets.each do |buyer, ticket_list|
      ticket_list.each do |ticket|
        score = ticket.score( final )
        next if score.zero?
        winners[buyer] ||= []
        winners[buyer] << [ ticket, score ]
      end
    end
    @@tickets.clear
    winners
  end
end
双小于号的意思是将方法直接添加到类中。双竖线代表or,   =[]意思是如果不存在就为空数组。

让我们输出最后的结果

irb> LotteryDraw.play.each do |winner, tickets|
irb>   puts winner + "won on " + tickets.length + " ticket(s)!" 
irb>   tickets.each do |ticket, score|
irb>     puts "\t" + ticket.picks.join( ', ' ) + ": " + score
irb>   end
irb> end

Gram-yol won on 2 ticket(s)!
    25, 14, 33: 1
    12, 11, 29: 1
Tarker-azain won on 1 ticket(s)!
    13, 15, 29: 2
Bramlor-exxon won on 1 ticket(s)!
    2, 6, 14: 1

之前我们提到了读取属性,下面我们来讲一下写入属性:

irb> ticket = LotteryTicket.new
irb> ticket.picks = 3
NoMethodError: undefined method `picks=' for #<LotteryTicket:0xb7d49110>

我们使用attr_accessor来定义读写属性

class LotteryTicket
  attr_accessor :picks, :purchased
end

这段代码等同于

class LotteryTicket
  def picks;           @picks;            end
  def picks=(var);     @picks = var;      end
  def purchased;       @purchased;        end
  def purchased=(var); @purchased = var;  end
end

看看这些方法,picks=和purchased=,他们拦截了外部对实例变量的赋值。通常我们不会用到完整代码,但我们可以用它来检查赋值的合法性。

class SkatingContest
  def the_winner; @the_winner; end
  def the_winner=( name )
    unless name.respond_to? :to_str
      raise ArgumentError, "The winner's name must be a String,
        not a math problem or a list of names or any of that business."
    end
    @the_winner = name
  end
end

用更少的手指赌博

许多年后,我们发现了新的彩票机,我们来看看它的程序

class AnimalLottoTicket

  # A list of valid notes.
  NOTES = [:Ab, :A, :Bb, :B, :C, :Db, :D, :Eb, :E, :F, :Gb, :G]

  # Stores the three picked notes and a purchase date.
  attr_reader :picks, :purchased

  # Creates a new ticket from three chosen notes.  The three notes
  # must be unique notes.
  def initialize( note1, note2, note3 )
    if [note1, note2, note3].uniq!
      raise ArgumentError, "the three picks must be different notes"
    elsif picks.detect { |p| not NOTES.include? p }
      raise ArgumentError, "the three picks must be notes in the chromatic scale."
    end
    @picks = picks
    @purchased = Time.now
  end

  # Score this ticket against the final draw.
  def score( final )
    count = 0
    final.picks.each do |note|
      count +=1 if picks.include? note
    end
    count
  end

  # Constructor to create a random AnimalLottoTicket
  def self.new_random
    new( NOTES[ rand( NOTES.length ) ], NOTES[ rand( NOTES.length ) ],
         NOTES[ rand( NOTES.length ) ] )
  rescue ArgumentError
    retry
  end

end

我们可以更改常量,但Ruby会发出警告

irb> AnimalLottoTicket::NOTES = [:TOOT, :TWEET, :BLAT]
(irb):3: warning: already initialized constant NOTES
  => [:TOOT, :TWEET, :BLAT]

6.规则从何来

让我们回到可汗博士,山羊还有汉娜身边。博士终于找到了一台电脑,进入了irb。

irb> require 'rbconfig'
  => true
irb> RbConfig::CONFIG
  => {"abs_srcdir"=>"$(ac_abs_srcdir)", "sitedir"=>"bay://Ruby/lib/site_ruby", ... }

这里有太多的信息,包括Ruby中所有的环境设置。而博士需要的是Ruby核心库之外的由别人安装的库。所以博士检查了一些全局变量:

irb> $"
  => ["irb.rb", "e2mmap.rb", "irb/init.rb", ... "rbconfig.rb"]
irb> $:
  => ["bay://Ruby/lib/site_ruby/1.9", "bay://Ruby/lib/site_ruby/1.9/i686-unknown",
      "bay://Ruby/lib/site_ruby", "bay://Ruby/lib/1.9",
      "bay://Ruby/lib/1.9/i686-unknown"]

哈,变量$”返回了一个包含有所有使用require载入的库名的数组。变量$:代表$LAOD_PATH是一个包含所有Ruby在你试图用require载入文件时会检查的地址的列表。这些地址很可能是绝对地址。在windows中,绝对地址以盘符开头。在linux中以斜杠开头。

irb> Dir.chdir( "bay://Ruby/lib/site_ruby/1.9/" )
  => 0
irb> Dir["./*.{rb}"]
  => ['endertromb.rb', 'mindreader.rb', 'wishmaker.rb']

可汗博士用chdir把当前的工作地址换到了$LOAD_PATH列表中的第一个地址,这个地址通常用来存储自定义类。

可汗博士找到了读心机和许愿机。“让我们试试能不能让他们同时起作用。”

7.梦想因何生

可汗博士编写了一个愿望扫描器。

require 'endertromb'
module WishScanner
  def scan_for_a_wish
    wish = self.read.detect do |thought|
      thought.index( 'wish: ' ) == 0
    end
    wish.gsub( 'wish: ', '' )
  end
end

“用module而不是class是因为更简单,module中只有方法。我要把它混合到读心机里。”

require 'mindreader'
class MindReader
  include WishScanner
end

“注意到我在之前用了self吗?因为混合后方法之间就可以互相调用了。”

require 'wishmaker'
reader = MindReader.new
wisher = WishMaker.new
loop do
  wish = reader.scan_for_a_wish
  if wish
    wisher.grant( wish )
  end
end

程序开始运作,博士闭上眼睛,默念自己的愿望:“鲸鱼。”

最后一只回地球的鲸鱼

博士和汉娜回家了。


Refference: