瞎扯淡 Ruby 的方法调用好像野马

ssqq · 2013年05月30日 · 最后由 luikore 回复于 2013年05月31日 · 6460 次阅读

方法从类中孕育出来后,出现的时候,就已经狂奔了。因为没有调用一个方法的限制。

object_name.method_name

在其他语言中

object_name.method_name() 才是策马扬鞭

Lamda 似乎是语言特性中后加上去的,感觉很别扭。

代码块,似乎为了好看才加上去的。

Ruby 太自由了,自由是有代价的。

恩,确实,ruby 的 method 并不是对象,需要变成 Proc,不过这么做可以大幅度简化敲代码的活动,ruby 比较重视节省程序员劳动

这个其实还是 Ruby 特色吧。追求 DSL 式的代码。 当然 Ruby 其实也为此付出代价了。没有函数是第一公民这个重要的特性了。

看完了没明白 LZ 说的代价是什么。。

object_name.method_name()才是策马扬鞭 这段不明白楼主想说明什么

$('body').position().top
$('body').scrollTop()

每次调用我都要去查文档哪个是属性,哪个是方法,麻烦死了。

- -。调用方法时可以省去括号有时的确会把人搞糊涂,但是它的好处在写 DSL 是体现的很明显,看起来非常直接,就像语言内置了这类关键字一样。而且这点不是 Ruby 的特有的,lua,coffe script 等也是这样的。。。

代码块还不是为了好看设计的,否则用 proc 替代就行,因为块不是各对象,所以没有讨厌的 self 问题。

要是用面向过程的方式来理解 OO, 那太累了,不要老想着 方法调用,理解成 发消息就好多了,发消息要什么括号啊! 当然有参数的情况下,我觉得加括号好一点。

#8 楼 @zgm 这个和面向什么没关系,楼主的问题在于去掉括号有可能让人混淆方法本身方法的一次调用,不过 ruby 里面是不存在方法本身这个对象的,所以在这个地方就没法函数第一性,这是它为此付出的代价

对的,这是 Ruby 不好的地方。

  1. 首先函数不是第一等公民了。要通过 to_proc 才可被传递
  2. 应该学习 CoffeeScript,在函数无参数调用时,需要显式使用 ();函数后面跟参数时,不必显式使用 ()。如果函数后来不接参数且没有 () 时,此时可取出函数对象来。

#10 楼 @alsotang 总有取舍的,不过这也谈不上不好,并不是说 FP 就天生高人一等

我就喜欢不好的地方...

ruby 里的 . 总是发消息而不是取成员, 没有二义性, 是否带括号完全不重要. javascript 里的 . 总是取成员而不是调用, 如果一个成员是 function, 然后忘记写括号就败了. 所以 coffee 里才不得不对无参函数调用加空括号. 我觉得, 强迫人写括号就是法西斯... 还有一些语言里 . 是带二义性的, 只能靠后面的括号区分, 这才是"野马".

lambda 是后来加上的, 然后呢... 用 lisp 会感觉 lambda 比 ruby 好写么?

代码块是控制流不是 lambda. 你可以详细学学 smalltalk 是怎么整的就明白 block 的强大之处了...

当然代码块和 lambda 有相似之处, 因为 closure 和 object 殊途同归. 但是写久了以后就会发现是没有 silver bullet 的, 二者在不同场景各有其用处...

@alsotang 我不同意。在平时的开发中,把函数当做参数传递的情况比较少,函数调用的情况很多。 并且显式使用 () 会有一个问题,忘记打 () 造成返回函数而不是调用函数,产生 bug。在 coffeescript 里面经常出现这样的状况。

#10 楼 @alsotang coffee 这个'特性'只是为了兼容 js.. ruby 中不能在类外访问 attribute,所以只要.必然是方法调用,非常的清晰

#12 楼 @luikore

于是 +1 那里就坑了

有人对带不带括号感到无所适从, 大概是被类似这样的 java / c# 代码坑过:

public class C {
    public int foo = 3;
    public int foo() {
        return 4;
    }
}

名字都是 foo, 类型也一样, 带和不带括号却竟然是两种东西, WTF! 一不小心多写了或者少写括号还不容易找出来. ruby 里就没这种坑, 不管带不带括号, 名字相同的就是一个东西.

#15 楼 @bhuztez +1 坑在哪里...

只能说 LZ 的比喻有点没读懂.

#9 楼 @fsword 我觉得还是有的:

class Foo
  def bar
    puts 'bar'
  end
end

f = Foo.new
f.method(:bar).call
Foo.instance_method(:bar).bind(f).call

#12 楼 @luikore 我也就喜欢不好的地方,世界上本没有好,喜欢的人多了不好也就变成好了。

#17 楼 @5long 你这个代码正是 Ruby 的弱点所在 Python 在这方面好很多

#19 楼 @iBachue ruby 很少需要去取 method, 还有 block, yield, proc 和 lambda 可以用. python 就是有时直接写 lambda 只能一个表达式不如写 def 方便, 所以才 def method 再取出来当 lambda 的...

#19 楼 @iBachue 我不清楚你所说的 “弱点” 和 “这方面” 具体是指什么. 所以我无话可说...

#17 楼 的本意还是想告知 #7 楼, 方法(包括未绑定的和已绑定的)作为对象,作为一等公民(可以被传递,被返回,储存在局部变量/实例变量等储值机制里),是存在的。

想想,其实可以把属性获取也当成一个方法,返回一个值的方法。这样,也挺好的

#21 楼 @5long 以你

Foo.instance_method(:bar).bind(f).call

为例,Python 对应的写法是:

Foo.bar(f)

你说那种才是简洁的?

#23 楼 @iBachue c -> i++ ruby -> i += 1 => C 简洁

#23 楼 @iBachue 无法否认, 这种情形下 Python 的写法打字更少.

但是在现实的充分 OO 的代码中, 使用协作者对象, 要比使用从协作者对象身上"取下来"的已绑定的方法直观得多. 更不要说取未绑定的方法, 然后再伺机把未绑定方法所属的类的实例传进去调用 (你看用文字描述就这么费劲, 更不要说后来人阅读代码时会有多大的认知障碍了).

如果你确实面对着需要频繁使用方法对象的场景, 那么我无可辩驳. 对我来说, 目前能迅速回想起来的, Ruby 的方案对我而言更优越的用例有这么两个:

Getter API 演变.

比如说我们有个 UserPresenter 类 (在 fat model 的风格下很可能会是 User 类), 需要提供 avatar 属性返回头像 URL. 习惯上会觉得"啊既然是返回个 URL 字符串那么就用属性 getter 好了". 接下来需求变更, avatar 可能会有需要多个不同的大小. 之前所需的尺寸只是最常用的而已. 对 Ruby 而言这个状况很容易解决: 把 avatar 方法实现成多接受一个参数来制定尺寸即可. 而反观 Python, 假如把属性 getter 换成方法, 旧的代码都要逐个改掉. 如果不换, 就没法制定尺寸参数. 这个问题在我现在面对的 codebase 里就真实地存在着.

Stub

写隔离测试时, stub 是个常用的解除依赖的手段. Python 对象想要做 stub, 就要考虑"这个要 stub 的东西的获取方式是属性还是方法?". 而且在面对这种情况时:

class Foo(object):
    @property
    def bar(self):
        return 'bar'

def test_foo(monkeypatch):
    foo = Foo()
    monkeypatch.setattr(foo, 'bar', 'bar')
    assert foo.bar == 'bar'

monkeypatch.setattr(foo, 'bar', 'bar') 处会抛异常. 虽说这种情形 stub 的方法也不是彻底没有, 我正在用这个库. 但瞄了一眼具体实现, 发现背后全是黑魔法. 项目也不再维护了. 用着多少有点担心. 以及, 这个库在 stub 时, 所使用的预设返回值不能是 Mock 的实例. 全是令人头疼的因素. 而这些问题在 Ruby 的设计中根本不存在.

以及可能还有更多例子, 但一时也想不起来了. 就写这么两个吧.

不过, 我依然无法下结论说 Python 和 Ruby 谁的方案更"好". 我只能说对于我个人的编程习惯和面对的问题领域而言, Ruby 的设计更适合我而已.

EDIT: 忘了说, 前面 Python 代码示例中的 monkeypatch 是来自 py.test 测试框架的一个自带的插件 (在 pytest 的术语中属于 fixture 插件): http://pytest.org/latest/monkeypatch.html

从 FP 的角度来看,无括号的方法调用相当于取值的过程,中间没有副作用改变改变程序的状态,所以不需要分辨取值还是调用方法,因为结果都是一样的,但是 ruby 并没有这种约定。这方面 scala 就做得不错,有副作用的函数约定加上括号。

object_name.method_name()才是策马扬鞭 看不懂,楼主是吐槽 () 可以省略会跟对象的 “属性” 混淆吗?ruby 没有 “属性” 这一说法,只有方法,看似属性的,其实都只是其成员变量的读方法。另外,如果是要得到该方法的对象,可以用object_name.method(:method_name),这个只是语言特性,适应就好。

#24 楼 @jjym i++那种例子还是不要拿出来比较好 那个叫有诟病。。。 #25 楼 @5long 我只是就 Ruby 没有函数是第一公民这个特性的结果提供点证据而已,您别激动。。

#28 楼 @iBachue "函数是第一公民"我还是觉得有. 写 func.call() 和写 func.() 虽然都不及 func() 简洁, 但我觉得不能说没有.

PS. 你早把这个意图说出来我就可以少打点字了 - -

顺便, 我觉得还是得稍微说明下: 把前面 #17 楼 的代码换成 Python 的大致等价物:

class Foo(object):
    def bar(self):
        return 'bar'

接下来 Foo.bar 返回的不是函数, 是未绑定的方法 Foo.bar(1) 并不会返回 'bar', 而是会抛异常.

Foo().bar 返回的也不是函数, 是已绑定的方法.

所以我所理解的 Python 在"函数是一等公民"这个特性上更优秀的地方, 仅在于函数/绑定方法/未绑定的方法的调用方式是与常规的方法调用的语法完全相同的, 都是后面直接加一对括号. 而 Ruby 更加地以对象为中心, 就得用 func.call()func.().

#29 楼 @5long

"函数是第一公民"我还是觉得有. 写 func.call() 和写 func.() 虽然都不及 func() 简洁, 但我觉得不能说没有.

个人觉得"函数是第一公民"的特性首先体现在取出函数对象并赋值上,Python 的写法是foo.bar或是Foo.bar,而 Ruby 的那种手法,调方法才能取出函数,和 Python 比起来,额,差太远了,我因此就不觉得 Ruby 还能被称为"函数是第一公民"的语言了,只能算是一等半吧。。。。

#26 楼 @saiga

scala 有没有括号和有没有副作用完全没联系吧...

object O {
  def prop = {
    println("side effect")
    2
  }

  def main(xs: Array[String]) {
    O.prop
    O.prop
  }
}

另外 scala 函数后面的括号又和其他语言不同, 它其实是 tuple 来的...

object O {
  def f(x:Int) = {
    x
  }

  def main(xs: Array[String]) {
    O.f(1)
    val x = (1)
    O f x
  }
}

这时候你甚至会怀念 java... 至少 O.fO.f() 还能看出区别, 但 scala 里就看不出来了...


隔离了副作用的只有 haskell 能做到, scala 那种大杂烩是不可能语法隔离副作用的...

再另外 gcc 有告诉编译器函数无副作用的 attribute, 例如一个求平方的函数可以这么标记

int square (int) __attribute__ ((pure));

那编译器就知道它是 pure 函数, 下面的代码

int x = square(3);
int y = square(3);

就会被优化成类似

int x = square(3);
int y = x; // pure 函数无副作用, 相同参数结果也相同, 不需要再调用一次了

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

#31 楼 @iBachue 我在 #30 楼 刚好回复了. Foo.barfoo.bar 取出的不是函数, 分别是未绑定方法和已绑定方法.

#23 楼 @iBachue

the python way

def f(g):
  g()

def bar():
  print("hello world")

f(bar)

the ruby way

def f
  yield
end

def bar
  print "hello world"
end

f { bar }

括号加冒号字符数刚好等于 end ...

#32 楼 @luikore 事实上 Scala 只是在 约定 上推荐有副作用的一律带括号,编译器本身并没有作任何限制的。

#34 楼 @luikore 这个都被你发现了。。

#34 楼 @luikore 这个呢?

def f(a, b): 
  a()
  b()

def foo(): 
  print('foo')

def bar(): 
  print('bar')

f(foo, bar)

#37 楼 @reus

你这个是 corner case ... 可以用 closure = object 的哲♂学变换嘛

def f a, b
  send a
  send b
end

def foo
  print 'foo'
end

def bar
  print 'bar'
end

f :foo, :bar
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册