Oh!Coder

Coding Life

读《Ruby编程语言》笔记(三)

| Comments

继续阅读。

这部分是本书的第六章,因为这部分内容不是很熟,所以记录的内容也比较多,就单独拿出来作为一篇笔记了。好,还是老样子,每条记录后面会标出书中对应的页码。

一、两种Proc对象(P.176)

  • 有两种方式的Proc对象,一种被称为proc,另一种被称为lambda,它们有一些细微的差别。proclambda的行为更像是函数而非方法,它们的一个重要特性是它们都是闭包(closure):它们会保持定义时所在范围内的局部变量,即使在另外的范围内被调用时,它们也可以对这些变量进行访问。

二、FixnumSymbol(P.179)

  • 在现实中,Ruby一般把FixnumSymbol的值看作立即值(immediate value)而非真正的对象引用,因此,FixnumSymbol对象无法定义单键方法。为了保持一致性,其他的Numeric对象也不能定义单键方法。

#### 三、取消方法定义(P.179~P.180)
* 用def语句定义的方法可以用undef语句取消定义:
* def sum(x, y); x+y; end # Define a method puts sum(1, 2) # Use it undef sum * 有趣的是,undef也可以用于取消继承方法的定义,而不影响所继承的类中该方法的定义。假设类A定义了一个方法m,而类B是类A的子类,于是类B也继承了m方法。如果不想让类B调用方法m,可以在子类的代码中用undef取消m的定义。 * 注意,undef语句必须紧跟一个表示方法名的标识符。对于用def语句定义的单键方法,不能用它来取消定义。 * 在一个方法或模块中,还可以用undef_method方法(它是Module的私有方法)来取消方法定义,采用此方式,需要把代表要取消方法名字的符号(symbol)传给undef_method方法。

四、exit全局函数(P.181)

  • 考查一下exit这个全局函数,它使Ruby程序以一种可控的方式结束运行。这个方法有一个名为exit!的变体,它让程序立刻结束,而不运行任何END块和任何用at_exit注册的钩子(hook)方法。exit!方法并非一个修改器方法,而是exit方法的一个“危险”变体,用“!”可以提醒程序员谨慎地使用这个方法。

五、别名不是重载(overload)(P.182)

  • 一个Ruby方法可以有两个名字,但是两个方法不能共享一个名字。在静态语言中,可以通过参数数目和类型区分方法,因此只要两个方法有不同数目或类型的参数,它们就可以共享同一个方法名,不过这种方式的重载在Ruby中是不可能的。
  • 不过,方法重载对Ruby并非必需。方法的参数可以接受各种类型的值;另外,Ruby方法的参数可以被声明为默认值,这些参数在方法调用时可以被省略。这使得可以用不同数目的参数调用同一个方法。

六、可变长度参数列表(P.186~P.187)

  • 有时我们想编写可以接受任意数目参数的方法。要做到这点,需要在某个参数前放上一个\*符。在方法的代码中,这个参数表示一个数组,它包含给这个位置上的零个或多个参数。比如:
  • \# Return the largest of the one or more arguments passed def max(first, \*rest) \# Assume that the require first argument is the largest max = first \# Now loop through each of the optional arguments looking for bigger ones rest.each{ |x| max = x if x > max } \# Return the largest one we found max end
  • \*打头的参数不能超过一个。在Ruby1.9中,用\*打头的参数仍然要放在带有默认值参数的后面,其后可以再指定普通参数,但是该普通参数仍然需要放在\&打头的参数之前。

七、给方法传递数组参数(P.187)

  • 我们已经看到在方法定义时,如何用\*使多个参数被放入到一个数组中。我们也可以用它把一个数组(或范围、枚举)参数中的每个元素分散、扩展为独立的方法参数。\*符有时被称为splat操作符,尽管它实际上并非一个操作符。例如:
  • data = [3, 2, 1] m = max(\*data) # first = 3, rest=[2, 1] => 3

八、显示传递Proc对象(P.190)

  • 如果创建了一个Proc对象,并且希望把它显示传递给一个方法,你可以像传递其他值一样,将其传递给这个方法——Proc对象与其他对象没什么不同,也是一个对象。在这种情形下,方法定义中不应该使用\&符号:
  • \# This version expects an explicitly-created Proc object, not a block def sequence4(n, m, c, b) \# No ampersand used for argument b i = 0 while(i < n) b.call(i\*m + c) i += 1 end end p = Proc.new{ |x| puts x } sequence4(5, 2, 2, p)

九、在方法调用时使用\&(P.191~P.192)

  • 考虑如下对两个数组进行求和的代码:
    a, b = [1, 2, 3], [4, 5] \# Start with some data. sum = a.inject(0){ |total, x| total+x } \# => 6. Sum elements of a. sum = b.inject(sum){ |total, x| total+x } \# => 15. Add the elements of b in.
  • 要注意的是这段代码中的两个代码块是一样的,与其让Ruby解释器将同样的代码块解析两次,不如为这个代码块创建一个Proc对象,然后把它使用两次:
    a, b = [1, 2, 3], [4, 5] \# Start with some data. summation = Proc.new{ |total, x| total+x } \# A Proc object for summations. sum = a.inject(0, \&summation) \# => 6 sum = b.inject(sum, \&summation) \# => 15
  • 如果在方法调用中使用\&,它必需被用于修饰方法的最后一个参数。代码块可以与任何方法调用关联起来,即使这个方法并不需要一个代码块,而且也没有yield语句。任何方法调用都可以用一个\&参数作为最后一个参数。
  • 在方法调用中,\&通常出现在一个Proc对象之前,其实它能出现在所有支持to_proc方法的对象之前。Method类就有这个方法,所以Method对象可以像Proc对象一样被传给迭代器。
  • 在Ruby 1.9中,Symbol类也定义了to_proc方法,使符号也可以用\&修饰并传给迭代器。

十、ProcLambda(P.192)

  • 代码块是Ruby的一种句法结构,它们不是对象,也不能像对象一样操作,不过,可以创建来表示一个代码块。根据对象的创建方式,它被称作一个proc或一个lambdaproc的行为与代码块类似,而lambda的行为则与方法类似,不过,它们都是Proc类的实例。

十一、Proc.new(P.193)

  • 如果调用Proc.new时没有关联一个代码块,它返回的proc会指向所在方法关联的代码块。这种方式可以作为\&做前缀的代码块参数的一种替代方式。比如,下面的两个方法是等价的:
  • def invoke(\&b) b.call end
  • def invoke Proc.new.call end

十二、Kernel.lambda(P.193)

  • 另一种创建Proc对象的方式是使用lambda方法,它是Kernel模块的一个方法,所以看起来像是一个全局函数。就像名字所暗示的,lambda方法返回的Proc对象是一个lambda而非proclambda方法不带参数,但是在调用时必须关联一个代码块:
  • is\_positive = lambda{ |x| x > 0 }

十三、Lambda字面量(P.194)

  • Ruby 1.9支持一种全新的句法,把lambda定义为字面量:
  • lambda方法名替换成\-\>符号。
  • 把大括号内的参数列表移到大括号之前。
  • 把参数列表的分隔符从\|\|变为\(\)
  • 做了这些改变后,我们就得到一个Ruby 1.9的lambda字面量:
  • succ = \-\>\(x\){ x+1 }
  • succ现在容纳了一个Proc对象,我们可以像其他方式一样使用它:
  • succ.call(2) \# => 3

十四、lambda字面量的参数列表(P.194)

  • lambda字面量的参数列表可以包括一个代码块内局部变量的声明,这样可以防止覆盖包含代码块的代码中的同名变量。在参数列表后,放在一个分号后面的就是局部变量列表:
  • \# This lambda takes 2 args and declares 3 local vars f = \-\>(x, y; i, j, k){...}

十五、把lambda传递给代码块(P.195)

  • Lambda字面量产生的Proc对象跟代码块不同,如果想把lambda传给一个期望获得代码块的方法,可以像对待其他Proc对象一样,用\&修饰这个字面量。下面演示如何用代码块和lambda字面量将一个数字数组以降序排列:
  • data.sort{ \|a, b\| b-a } \# The block version
  • data.sort \&\-\>(a, b){ b-a } \# The lambda literal version

十六、调用Proclambda(P.195)

  • Proclambda是对象而非方法,它们不能像方法一样被调用。如果p表示一个Proc对象,不能像方法一样调用p。但是因为p是一个对象,因此可以调用p的方法。
  • 我们已经提到过Proc类定义了一个call方法,调用这个方法会执行原始代码块的代码。传给call方法的参数成为代码块的参数,而代码块的返回值将作为call方法的返回值:
  • f = Proc.new{ |x, y| 1.0/(1.0/x + 1.0/y) } z = f.call(x, y)
  • Proc类还定义了数组访问操作符,它的工作方式与call一样,这意味着可以用与方法调用类似的方式调用proclambda,只是圆括号被换成了方括号。比如,上面的proc调用可以替换成如下方式:
  • z = f[x,y]
  • Ruby 1.9还提供了一种方式来调用Proc对象,可以用圆括号前面加上一个句点符号来替代方括号:z = f.(x,y)
  • .()看起来像一个缺少方法名的方法调用,这并非一个可以定义的操作符,它只是一个调用call方法的语法糖,可以用于任何定义了call方法的对象,而不限于Proc对象。

十七、Proc相等性(P.197)

  • Proc类定义了==方法来判定两个Proc对象是否相等。不过,要记住两个proclambda拥有同样的代码并不意味着它们是相等的:
  • lambda{ |x| x\*x } == lambda{ |x| x\*x } # => false
  • 只有当一个Proc对象是另一个Proc对象的克隆(clone)或复制品(duplicate) 时,==方法才会返回true
  • p = lambda{ |x| x\*x } q = p.dup p == q # => true: the two procs are equal p.object_id == q.object_id # => false: they are not the same object

十八、LambdaProc的区别(P.197~P198)

  • proc是代码块的对象形式,它的行为就像一个代码块。Lambda的行为略有不同,它的行为更像方法而非代码块。调用一个proc则像对代码块进行yield,而调用一个lambda则像调用一个方法。在Ruby 1.9中,可以通过Proc对象的实例方法lambda?来判定该实例是一个proc还是lambda,如果返回值为真,那么它是一个lambda,否则为一个proc
  • proc与代码块类似,因此如果调用的proc执行一个return语句,它会试图从代码块(这个代码块被转换为一个proc)所在的方法中返回。
  • Lambda中的return仅仅从lambda自身返回,而不会跳出调用它的方法。
  • break语句在proclambda的行为,与return语句类似,break语句会跳出调用代码块和proc的方法,而不会跳出调用lambda的方法。

十九、proclambda的其它控制流语句(P.199~P.200)

  • 顶级的next语句在代码块、proclambda中有相同的行为:它使调用代码块、proclambdayield语句或call方法返回。如果next语句后面跟一个表达式,那么这个表达式会成为代码块、proclambda的返回值。
  • redo语句在proclambda中也有相同的行为:它让控制流转向该proclambda的开始处。
  • proclambda中,retry是被禁止使用的:使用它永远会导致一个LocalJumpError异常。
  • raise语句在代码块、proclambda中有相同的行为:异常总是向调用堆栈的上层传播。如果raise异常的代码块、proclambda中没有rescue语句,该异常会首先传播到yield该代码块的方法,或者用call调用该proclambda的方法。

二十、传给proclambda的参数(P.200)

  • yield调用一个代码块与调用一个方法类似,但并不相同。在使用参数的方式上,两者的处理并不相同。yieldinvocation的语义不同,yield语义更类似于并行赋值。调用proc使用的是yield语义,而调用lambda使用的是invocation语义:
  • p = Proc.new{ |x,y| print x, y } p.call(1) # x,y=1: nil used for missing rvalue: Prints 1nil p.call(1,2) # x,y=1,2: 2 lvalues, 2 rvalues: Prints 12 p.call(1,2,3) # x,y=1,2,3: extra rvalue discarded: Prints 12 p.call([1,2]) # x,y=[1,2]: array automatically unpackd: Prints 12
  • 上面的代码显示了proc对参数处理的灵活性:可以安静地抛弃多余参数,将nil赋给遗漏的参数,甚至可以拆开数组。(如果proc需要的是单个参数,而给出的是多个参数时,它可以把这些参数打包成一个数组传给这个proc。)
  • lambda在这方面就没有那么灵活了,跟方法一样,我们必须用与声明时同样多的参数对它进行调用:
  • l = lambda{ |x,y| print x,y } l.call(1,2) # This works l.call(1) # Wrong number of arguments l.call(1,2,3) # Wrong number of arguments l.call([1,2]) # Wrong number of arguments l.call(\*[1,2]) # Works: explicit splat to unpack the array

二十一、闭包(P.200)

  • 在Ruby中,proclambda都是闭包(closure)。“闭包”这个术语源自初期的计算机科学,它表示一个对象既是一个可调用的函数,同时也是绑定在这个函数上的一个变量。当创建一个proclambda时,得到的Proc对象不仅包含了可执行的代码块,也绑定了代码块中所使用的全部变量。

二十二、闭包和共享变量(P.201)

  • 需要注意的是闭包并不持有它引用的变量,而确实持有所使用的变量,并延长了其生命周期,或者说lambdaproc在创建时并不静态绑定使用的变量,相反,其绑定是动态的,在lambdaproc运行时才去查找变量的值。

二十三、闭包和绑定(P.202)

  • Proc定义了一个名为binding的方法,调用这个方法会返回一个Binding对象,它表示该闭包所使用的绑定。
  • 目前为止,我们讨论的闭包绑定就好像是变量名和变量值的映射。其实,绑定并不限于变量,它包含所有与执行方法相关的信息。比如self的值及将被yield调用的代码块(如果有的话)。
  • 绑定并不是闭包的专有特性。Kernel.binding方法也可返回一个Binding对象,它表示调用此方法时的那些有效绑定。

二十四、Method对象(P.203~P.204)

  • Object类定义了一个名为method的方法,它接受一个用字符串或符号表示的方法名,返回的Method对象表示在接收者对象中相应的方法(如果没有这个方法,会抛出一个NameError异常)。例如:
  • m = 0.method(:such) # A Method representing the such method of Fixnum 0
  • 在Ruby 1.9中,还可以通过public\_method方法获得一个Method对象,它与method方法类似,但是忽略被保护(protected)方法和私有(private)方法。
  • Proc对象一样,Method对象也用call方法(或\[ \]操作符)调用;而且,Method类也定义了arity方法,它与Proc类的arity方法类似。
  • Method对象的call方法使用的是方法调用(method-invocation)语义,而非yield语义,因此Method对象更像lambda而非proc

二十五、Method对象与Proc对象转换(P.204)

  • 在需要使用Proc的地方,可以用Method.to\_proc方法把Method对象转换为Proc对象。这就是为什么Method对象可以用一个&做前缀,然后用这个对象取代代码块作为参数传递的原因。例如:
  • def square(x); x\*x; end puts (1..10).map(\&method(:square))
  • 除了可以把一个Method对象转换为一个Proc对象,还可以反过来把一个Proc对象转换为一个Method对象。(Module的)define\_method方法要求一个Symbol对象作为参数,它把这个符号作为方法名,以关联的代码块为方法的主体来创建一个方法。如果不用代码块,也可以以一个Porc对象或Method对象为方法的第二个参数。
  • Method对象和Proc对象的一个重要区别是Method对象不是闭包。Ruby的方法是完全自包含的,它们从不会试图访问其范围外的任何局部变量,因此,唯一一个Method对象绑定的值是self——该方法的宿主对象。
  • 在Ruby 1.9中,Method类定义了三个新方法:name方法返回方法的名字(用字符串方式);owner方法返回这个方法被定义的类;receiver方法返回该方法绑定的对象。对于任何Method对象mm,m.receiver.class必须是m.owner或其子类。

二十六、无绑定的Method对象(P.204~P.205)

  • 除了Method类,Ruby还定义了一个UnboundMethod类。就像其名字所暗示的,UnboundMethod对象代表一个没有绑定对象的方法。因为UnboundMethod对象没有绑定对象,它不能被调用,因此也没有定义call\[ \]方法。
  • 要得到一个UnboundMethod对象,我们可以对任意类或模代码块使用instance_method方法:
  • unbound_plus = Fixnum.instance\_method("+")
  • 在Ruby 1.9中,我们也可以通过public\_instance\_method方法来获得一个UnboundMethod对象。它的工作方式与instance_method方法类似,但是忽略被保护(protected)方法和私有(private)方法。
  • 在Ruby 1.9中,跟Method类一样,UnboundMethod也有类似的nameowner方法。

二十七、局部应用函数(P.207)

  • 在函数式编程中,局部应用是指用一个函数和部分参数值产生一个新的函数,这个函数等价于用某些固定参数调用原有的函数。

二十八、缓存(Memoizing)函数(P.208)

  • Memoization是函数式编程的一个术语,表示缓存函数调用的结果。如果一个函数对同样的参数输入总是返回相同的结果,另外出于某种需要我们认为这些参数会不断使用,而且执行这个函数比较耗费资源,那么memoization可能是一个有用的优化。

二十九、符号、方法和Proc(P.209)

  • Ruby 1.9为Symbol类增加了一个有用的to\_proc方法,这个方法允许用\&打头的符号作为代码块被传入到一个迭代器中。在这里,这个符号被假定为一个方法名。在用to\_proc方法创建的Proc对象被调用时,它会调用第一个参数所表示的名字的方法,剩下的参数则作为参数传递给这个方法。

Comments