0%

七周七语言之-Ruby

最近突然有兴趣翻开了队友从队里順来的一本书Bruce.A.Tate的《七周七语言 理解多种编程范式》,正好实训划水无事可干,打算写一个系列,涉及Ruby Lo Prolog Scala Erlang Clojure Haskell,对他们的特性,编程模型做一个简要的理解。语法一笔带过,日后详细学习的日后再补上

开始

安装Ruby

安装ruby:sudo pacman -S ruby
安装irb交互shell:sudo pacman -S irb
安装成功后,查看版本号,执行第一个hello world程序:

$ruby -v
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
$irb
irb(main):002:0> puts 'hello world'
hello world
=> nil

和python类似,ruby有解释模式和交互模式,ruby脚本常以*.rb命名,和python一样需要指定编码,否则中文输入会出现问题:
#!/usr/bin/ruby -w
# -*- coding: UTF-8 -*-
puts "你好,世界!";

存为hello.rb可以用ruby hello.rb解释执行

基本语法

BEGIN END代码块

#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
puts "这是主 Ruby 程序"
END {
puts "停止 Ruby 程序"
}
BEGIN {
puts "初始化 Ruby 程序"
}

BEGIN,END会分别在主程序的执行前/后执行,输入结果如下:

初始化 Ruby 程序
这是主 Ruby 程序
停止 Ruby 程序

Here Document

详细见:菜鸟教程

注释

类似python等脚本语言,ruby的行注释也是#

# i am comment

但ruby支持块注释
=begin
i am comment
=end

容器

数组

ary = [ "fred", 10, 3.14, "This is a string", "last element", ]
ary[0]
ary[-1]
ary[0..2]

ruby的数组是广义表,类似js,创建非常方便,且支持poppush
ruby的数组访问和python,R一样灵活,支持副负数下标逆序访问,也支持区间访问。

Map

irb(main):035:0> dic = {"me"=>"XUranus","renying"=>"thankod"}
=> {"me"=>"XUranus", "renying"=>"thankod"}

类似py的字典,语法糖更好看

判断

irb(main):037:0> puts 'awesome man' unless 1==2
awesome man
=> nil
irb(main):038:0> puts 'take a look' if 1<2
take a look

ruby的条件判断语法糖极其丰富,接近自然语言甚至支持倒装句!但是要注意if后面的判断除了nilfalse全是true即使是0也是true!!!

类 方法

一个Tree容器

class Tree #类名首字母大写
attr_accessor :children, :node_name #定义实例成员变量

def initialize(name,children=[]) #类似py的optional param 和py的区别在于没有:
@children=children
@node_name=name #成员变量赋值用@代替this
end

def visit(&block) #&不是表示去引用,而是表示这个是一个代码快
block.call self
end

def visit_all(&block)
visit(&block)
children.each {|x|x.visit_all &block}
end
end

ruby_tree = Tree.new("Ruby",[Tree.new("scala"),Tree.new("java")])
ruby_tree.visit_all {|node|puts node.node_name}

打印出
Ruby
scala
java

特性

Ruby 可以用来编写通用网关接口(CGI)脚本,可以被嵌入到超文本标记语言(HTML),可以轻易链接数据库,有丰富的内置函数和GUI工具。

真正面向对象

Ruby是一门真正面向对象的语言,java在基本类型(double,float…)还是用了过程语言的写法,有时需要Double来解包。而ruby即使是基本类型也是对象:

irb(main):015:0> 4.class
=> Integer
irb(main):016:0> (2==3).class
=> FalseClass
irb(main):004:0> 4.class.superclass
=> Numeric

在全局环境中,默认当前是main函数
irb(main):007:0> self
=> main
irb(main):008:0> self.class
=> Object
irb(main):009:0> self.class.superclass
=> BasicObject
irb(main):010:0> self.class.superclass.superclass
=> nil

强类型

尽管ruby是一门脚本语言,但ruby是强类型的!这一点和js的不一样,相比用不报错的js这一点的确很好。脚本语言需要在运行时候做类型检查,ruby充分的平衡了运行时类型检查和语言灵活性。

irb(main):017:0> a = 'hello '
=> "hello "
irb(main):018:0> b = 1
=> 1
irb(main):019:0> a+b
Traceback (most recent call last):
5: from /usr/bin/irb:23:in `<main>'
4: from /usr/bin/irb:23:in `load'
3: from /usr/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
2: from (irb):19
1: from (irb):19:in `+'
TypeError (no implicit conversion of Integer into String)

可见不同的类型是不能操作的

代码块与函数

Ruby中函数也是以及公民,可以被当作参数传递,写个遍历容器的例子:

irb(main):001:0> ary = [ "fred", 10, 3.14, "This is a string", "last element", ]
=> ["fred", 10, 3.14, "This is a string", "last element"]
irb(main):002:0> ary.each{|x|puts x}
fred
10
3.14
This is a string
last element

这种类似js的arr.foreach(x=>console.log(x)),然而我觉得这种=>要好看的多,|x|的写法很丑,但是Rust好像也用了这种语法糖!
下面这种写法也等价,(看似这种以do end封装的代码快类似一种匿名函数?且享有执行区的作用域?)
irb(main):009:0> ary.each do |i|
irb(main):010:1* puts i
irb(main):011:1> end
fred
10
3.14
This is a string
last element
=> ["fred", 10, 3.14, "This is a string", "last element"]

鸭子类型

上个例子中["fred", 10, 3.14, "This is a string", "last element"],我们可以发现这个容器是一个广义表序列,ruby不需要容器具有相同的类型,实际上此处并不是执行了动态类型推断,而是得易于ruby的面向对象机制,实际上此处的对象都有着共同的基类Object。

当我们执行力puts输出后,其实是执行了他们的共有的to_s()方法提取了字符串化的输出信息,“只要他走路像鸭子,他能嘎嘎叫,他就是鸭子”,这就是ruby的鸭子类型。ruby的数组支持poppush,所以就可以当作栈来用。

ruby是在运行时进行动态类型推导的,但是这也带来了问题

irb(main):012:0> def hello
irb(main):013:1> 'hello'+42
irb(main):014:1> end
=> :hello
irb(main):015:0> hello
Traceback (most recent call last):
6: from /usr/bin/irb:23:in `<main>'
5: from /usr/bin/irb:23:in `load'
4: from /usr/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
3: from (irb):15
2: from (irb):13:in `hello'
1: from (irb):13:in `+'
TypeError (no implicit conversion of Integer into String)

这个例子中,方法定义时没有做类型检查,只有运行时才发现错误,导致了ruby不能有效的抛出错误。

运算符

执行基本的加法运算4+3,因为ruby是面向对象语言,所以这里+是一种add方法,他的名字叫+,实际上调用是调用的4.+(3)

irb(main):032:0> 4.+(3)
=> 7

可以用.methods查看一个类型的方法
irb(main):031:0> 4.methods
=> [:-@, :**, :<=>, :upto, :<<, :<=, :>=, :==, :chr, :===, :>>, :[], :%, :&, :inspect, :*, :+, :ord, :-, :/, :size, :succ, :<, :>, :to_int, :coerce, :to_s, :to_i, :to_f, ....]

开放类

class NilClass #NillClass和String都是ruby的内建基类
def blank? #这里如果是java也许是isBlank(),允许?可以说是ruby的一个feature了
true #如果最后一行表达式的值为返回值,不必return
end
end

class String
def blank?
self.size == 0
end
end
irb(main):025:0> nil.blank?
=> true
irb(main):026:0> "".blank?
=> true
irb(main):027:0> "person".blank?
=> false

ruby不像java,需要继承一个类,并重写某个方法来改变类的特性。ruby支持开放类,class XXX申明某个类,如果没有被定义则定义该类,如果定义了就增加/重写这个类的某个方法。

Mixin

面向对象复用代码的一个典型方法就是继承,但是要同时继承多个类的行为要用到多继承,事实证明这会带来很多问题,所以java采用了单继承+接口的策略,而ruby则是采用模块。模块是函数和常量的集合,这点很像scala的trait

module ToFile 
def file
"object_#{self.object_id}.txt"
end

def to_f
File.open(filename,'w') {|f|f.write(to_s)} #注意这里的to_s
end
end

class Person
include ToFile
attr_accessor:name

def initialize(name)
@name=name
end

def to_s
name
end
end

Person.new('matz').to_f

这里我们发现模块ToFile的to_f中调用了to_s,但是to_s是在调用了他的类里之后定义的,然而这里并不会报错,这说明了模块中可以调用可能会使用他的类的未实现的方法!!!然而这里定义模块的时候ruby显然是没有进行静态检查的,体现了ruby的鸭子类型,运行时才会进行检查。

总结

Ruby的优点在与灵活性,提升了程序员的编程体验。和大多脚本语言一样,Ruby适用与做胶水语言,适合做爬虫。Ruby的web框架Rail也取得了巨大的成功。但Ruby的不足主要表现在:

  • 性能:ruby最大的弱点就是性能,但Robinius虚拟机已经用上了JIT,ruby1.9已经比上个版本快了十倍多。
  • 并发:面向对象的通病,面向对象的有状态性导致在并发条件下有很大的问题。
  • 类型安全:ruby是鸭子类型的支持者,无法在运行前做类型检查。例如mixin可以在对象没有创建时就调用其方法,这导致了一个问题就是ruby的IDE很难编写,市面上少有人开发ruby的IDE。
Disqus评论区没有正常加载,请使用科学上网