python, ruby, javascript 浅析
admin
2023-07-31 01:45:43
0

本文原发于个人博客

最近一直在看红宝石(ruby)语言,到现在为止,算是对其设计有一些了解。作为一动态语言,ruby 经常会拿来与 python 对比,确实这两门语言在语法层面、实现层面有很多共同的地方,但是它们也在很多设计理念上存在重要差异,通过对比这些相同点、异同点,更加有助于理解这两门语言。同时,Node.js、React Native 的出现,将 javascript 这门“前端”语言推向了全栈,同样作为一门动态语言,javascript 与 ruby、python 在很多概念上也存在很多相同点、异同点。

本篇文章着重从编程语言设计的角度进行阐述,并不会涉及三门语言性能、使用场景相关的内容。
希望对编程语言爱好者理解这三门语言有所帮助,做到融会贯通。

讨论范围

Python、Ruby、Javascript(ECMAScript) 准确说是一种语言规范,规范可以有多种实现,这体现在不同的解释器上。

  • Python 的解释器主要有 CPython、IronPython、Jython、PyPy

  • Ruby 的解释器主要有 Ruby MRI(CRuby)、JRuby、MacRuby、IronRuby

  • Javascript 的解释器主要有 Chakra, SpiderMonkey, V8

本文主要讨论的是 CPython、CRuby,它们是其语言作者亲自设计的,也是应用场景最广的。
Javascript 在语言设计之初根本没考虑到其应用范围会如此之广,所以相比其他语言,它语言内置的功能要弱很多,ES6 的出现就是为了解决这个问题,本文所涉及的 javascript 运行在基于 V8 引擎的 Node.js 中,且具备 ES6 语法。

语言定义

首先看一下 wikipedia 上对这三门语言的定义:

  • Python is a widely used high-level, general-purpose, interpreted, dynamic programming language

  • Ruby is a dynamic, reflective, object-oriented, general-purpose programming language.

  • JavaScript is a high-level, dynamic, untyped, and interpreted programming language.

其实上面标红的关键字对于这三门语言来说都适用,只是每个语言的强调点不一样而已。

通常会称这三门语言为动态语言,支持函数式面向对象两种编程范式,这两点其实是最重要的。

设计理念

既轻量又强大是大多数动态语言相通的设计理念,关于 javascript 设计理念更多的介绍可以参考我的这篇文章介绍。至于 Python 与 Ruby 设计理念的区别,一句话即可概括:

  • Python: 一件事情只有一种方法做

  • Ruby: 一件事情有多种方法做

比如,Python 中 Tuple, Array, String 没有相应获取大小的方法,而是提供了统一的len来解决这个问题

>>> len([1,2])
2
>>> len(\"hello world\")
11
>>> len((1,2))
2

至于 Ruby 的一件事情有多种方法做的理念,后面我在讲解 lambda 时再介绍。

语法

如果你之前没接触过 ruby、python 的语法,推荐先去了解下:

  • Ruby Essentials,两个小时绝对看完了

  • python 最佳实践,应该用不了半个小时

javascript 实在是太简单了,就不用特别看了。

综合来说,python、javascript 还是比较中规中矩的,即使 ES6 里面加了很多花哨的语法糖衣,但是也比较直观,但是 ruby 这个语言就比较变态了,各种符号,像class Son < Father表示类的基础,\"hello\" << \" world\"表示字符串的拼接,@var表示对象的成员变量,@@var表示类的成员变量,$var表示全局变量。

而且在 ruby 中,方法调用时的括号可有可无,即使有参数也可以省略:

> def add(a, b)
>     a + b
> end
>
> add 1, 2
=> 3

如果你对 Scheme 熟悉,上面的代码还能像下面这么写,是不是很亲切

> (def add a, b
>     a + b
> end)

> (add 1, 2)
=> 3

这也就是充分说明,括号在 ruby 中只是起到了“分割”的作用,并没有什么语法含义。

面向对象

面向对象主要的核心是用对象来达到数据封装的目的。

  • javascript 基于原型链实现面向对象,更详细的介绍可以参考《javascript中的面向对象编程》

  • python、ruby 基于类来实现面向对象,和 java 类似,但是更纯粹些。

$ python
>>> def func(): return 1
>>> type(func)

>>> func2 = lambda x: x
>>> type(func2)

>>> type(1)

>>> dir(1)
[\'__abs__\', \'__add__\', .....]
#--------------------------------------------------#
$ irb
> def add(a, b)
>    a + b
> end
> method(:add)
=> #
# 上面 ruby 的例子中,使用了 Symbol 来表示 add 方法,这是由于 ruby 中直接写 add 表示函数调用
> 1.methods
=> [:%, :&, :*, :+, :-, :/, .....]

可以看到,在 python、ruby 中,像1这样的数字字面量也是对象。

lambda 表达式

lambda 表达式表示的是匿名函数,也就是我们通常说的闭包。由于在这三门语言中,函数均是一等成员,所以可以很方便的进行函数式编程

$ node
> [1,2,3].map((x) => x + 1)
[ 2, 3, 4 ]
#--------------------------------------------------#
$ python
>>> map(lambda x: x+1, [1,2,3])
[2, 3, 4]
#--------------------------------------------------#
$ irb
> [1,2,3].map &(lambda {|x| x+1})
 => [2, 3, 4]

Python 的 lambda 表达式是这三者中最弱的一个,只能包含一个表达式,javascript 与 ruby 的则没有这种限制。

细心的读者会发现上面 ruby 版本的 lambda 前有个&,这是必须的,否则会报下面的错误

ArgumentError: wrong number of arguments (given 1, expected 0)

这是因为在 ruby 中,方法除了接受参数外,还可以接受一个代码块(block),代码块在 ruby 中有两种写法:

  • 一行的话用{}

  • 多行的话用do ... end

> [1,2,3].each { |num| print \"#{num}! \" }
1! 2! 3!
=>[1,2,3]
> [1,2,3].each do |num|
>    print \"#{num}!\"
> end
1! 2! 3!
 =>[1,2,3]         # Identical to the first case.

& 的作用是告诉解释器,现在传入的不是正常的参数,而是一个代码块。这个传入的代码块在方法内通过yield进行调用。这里可以做个演示:

class Array
  def my_each
    i = 0
    while i < self.size
        yield(self[i])  
        i+=1      
    end
    self
  end
end

> [1,2,3].my_each { |num| print \"#{num}!\" }
1! 2! 3!
=> [1,2,3]

Ruby 中 lambda 表达式属于 Proc 类型,

> lambda {|x| x}.class
=> Proc

这里可以看到,只是对于闭包的支持,Ruby 就提供了多种方案。更多可以参考:

  • Ruby Explained: Blocks, Procs, and Lambdas, aka \”Closures\”

  • Weird Ruby Part 4: Code Pods (Blocks, Procs, and Lambdas)

yield

就像上面说的,ruby 中 yield 就是表示代码块的调用,没有其他含义。而在 python 与 javascript yield 是用来构造生成器(generator)的,都是用来控制程序运行流程,相当于用户态的“线程”:

$ python
def iter():
    for x in xrange(10):
        yield x

foo = iter()
print next(foo)
print next(foo)
#--------------------------------------------------#

$ node
function* iter() {
 for (var i = 0; i < 10; i++)
    yield i
}
var foo = iter()
console.log(foo.next().value)
console.log(foo.next().value)

上面两份代码都依次打印出0, 1

关于生成器的更多资料,可以参考:

  • Generators in Node.js: Common Misconceptions and Three Good Use Cases

  • More details on Python generators and coroutines(强烈推荐 Python 读者看)

在 ruby 中,与生成器对应的概念是 Fiber,例如:

iter = Fiber.new do
  (0..10).each do |x|
    Fiber.yield x
  end
end

puts iter.resume
puts iter.resume

上面的代码也依次打印出0, 1

关于生成器与 Fiber 的关系,可以参考:

  • Overview of Modern Concurrency and Parallelism Concepts (需翻墙,强烈推荐读者看)

  • http://merbist.com/2011/02/22/concurrenc…

其实,生成器、Fiber 以及相关概念背后的理论基础是 continuation,continuation 的应用场景非常广泛,各种编程语言中的异常处理也是基于它来实现的。鉴于这个话题比较大,这里不再展开叙述,感兴趣的读者可以参考这篇文章,后面我也会单独再写一篇文章进行介绍。
这里仅仅给出 continuation 的一个简单示例以飨读者:

; Scheme 语言中没有 return 语句,利用 continuation 可以模拟 return
(define (f return)
  (return 2)
  3)

(display (f (lambda (x) x))) ; displays 3
(display (call-with-current-continuation f)) ; displays 2

关于这里例子详细的解释可以参考WIKI Call-with-current-continuation。

字符串

不可变性

字符串作为对字符的一种抽象,在大部分语言实现中都是不可变对象。在 Python 可以通过id()函数证明,在 Ruby 中可以通过__id__ 函数证明,javascript 中无类似方法。

$ python
>>> str = \'a\'
>>> id(str)
4351841824
>>> str = \'b\'
>>> id(str)
4351841864
#------------------------------#
$ irb
> str = \'a\'
> str.__id__
=> 70116314615020
> str = \'b\'
> str.__id__
=> 70116318601060

拼接

大多数语言都可以直接通过+进行字符串的拼接,但是这样做既不优雅,效率也低,所以一些语言会有些替代方案。
Ruby 与 Python 中对这块的支持比较强大,ES6 中借鉴了以上两门语言的语法,引入了 template_string,这在极大程度上方便了字符串的拼接。

$ node
> var a = 5;
> var b = 10;
> console.log(`Fifteen is ${a + b} and\\nnot ${2 * a + b}.`);
// \"Fifteen is 15 and
// not 20.\"
#--------------------------------------------------#
$ python
> long_string = \"\"\"
> my name is {username},
> my age is {age}
> \"\"\".format(username=\"zhangsan\", age=10)
#--------------------------------------------------#
$ irb
> long_string = \"\"\"
> my name is %{username},
> my age is %{age}
> \"\"\" % {username: \"zhangsan\", age:10}

查看值类型

动态语言最主要的特点就是变量无类型,利用反射机制可以查看运行时变量的值的类型

$ node
> str = \"hello world\"
> typeof str     
\'string\'
#------------------------------#
$ irb
> str = \"hello world\"
> str.class
String
#------------------------------#
$ python
> str = \"hello world\"
> type(str)

包管理

  • Python,PyPI,多版本兼容推荐使用 virtualenv 管理

  • Ruby, GEMS,多版本兼容,推荐使用 rvm + bundler 管理

  • Node.js,由于 Node.js 出现较晚,它避免了Python、Ruby 包全局污染的问题,而是选择将第三份模块安装在项目内的node_modules文件夹内

总结

经过上面简短的介绍,我相信大家对这三门语言有了全面的理解,多了解一门语言,也就是多个解决问题的思路。

个人感觉,Python、Javascript 的语法比较中规中矩,适合大部分程序员学习。Ruby 更适合 geek 去学,因为它的很多奇特语法会让你思考语言的设计细节,而不仅仅是使用这么简单。

最近我在看Ruby元编程,里面的很多内容就很有意思,一些内容在看 SICP 时就已经遇到,这种似曾相识的感觉很棒,我相信对编程语言的了解又加深了一步。谢谢 Yukihiro Matsumoto大叔,带给我们 ruby 这么美妙的语言。

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
项目管理和工程管理的区别 项目管理 项目管理,顾名思义就是专注于开发和完成项目的管理,以实现目标并满足成功标准和项目要求。 工...