100 "sự thật" thú vị về Ruby (Part 1)
Bài đăng này đã không được cập nhật trong 6 năm
Sau 2 năm làm việc với ruby, quả thật ngôn ngữ này còn chứa nhiều thứ hay ho mà bản thân mình chưa biết. Trong quá trình tìm hiểu, mò mẫm, mình có sưu tầm dược một bài viết khá hay. Mình xin phép được giới thiệu lại với mọi người. 100 "sự thật" nhỏ nhỏ mà đầy thứ vị.
- Method
methods
Bởi vì hầu hết tất cả mọi thứ trong Ruby đều là Object vậy nên bạn có thể dùng .methods
để xem tất cả các method của Object
4.methods - Object.methods
# => [:-@, :+, :-, :*, :/, :div, :%, :modulo, :divmod, :fdiv, :**, :abs, :magnitude, :~, :&, :|, :^, :[], :<<, :>>, :to_f, :size, :bit_length, :zero?, :odd?, :even?, :succ, :integer?, :upto, :downto, :times, :next, :pred, :chr, :ord, :to_i, :to_int, :floor, :ceil, :truncate, :round, :gcd, :lcm, :gcdlcm, :numerator, :denominator, :to_r, :rationalize, :singleton_method_added, :coerce, :i, :+@, :remainder, :real?, :nonzero?, :step, :quo, :to_c, :real, :imaginary, :imag, :abs2, :arg, :angle, :phase, :rectangular, :rect, :polar, :conjugate, :conj, :between?]
- Biến
_
Trong IRB nếu ta sử dụng biến _
sẽ giữ giá trị output của code mà đôi khi giá trị của nó ta không dùng tới hoặc ví dụ đơn giản chưa biết đặt tên như nào:
numbers = [4, 1, 2, 7]
a, _, b, c = numbers
puts "a=#{a}, b=#{b}, c=#{c}" #=> a=4, b=2, c=7
Một ví dụ khác là biến _
giúp ta bỏ qua giá trị phần tử tại vị trí được asign tới nó:
arr = [["John", "Doe", 15], ["Jane", "Doe", 28]]
arr.reduce("") do |acc, (name, _, age)|
acc << "#{name} (#{age}) "
end #=> Doe (15) Doe (28)
instance_exec
instance_exec
method có thể dùng với mọi Object, à mà mọi thứ trong Ruby đều là Object mà :v
Các sử dụng tương tự với làm việc với singleton_class (defind method cho duy nhất một object). Nghe có vẻ giống tạo instance method cho class, nhưng đơn giản hơn ko phải viết define class mà khai báo luôn và ngay như sau:
num = Object.new
num.instance_exec {
def == other
other == 3
end
}
num == 4
# => false
num == 3
# => true
Enumerator::Lazy
Một Enumerator::Lazy
object sẽ trả lại cho bạn một object tại thời điểm mà đoạn code thực thi tới từng item trong nó.
def do_the_lazy(array_input)
Enumerator::Lazy.new(array_input) do |yielder, value|
yielder << value
end
end
x = do_the_lazy([1,2,3,4,5,6])
# => #<Enumerator::Lazy: [1, 2, 3, 4, 5, 6]:each>
x.next
# => 1
x.next
# => 2
x.next
# => 3
x.force
# => [1, 2, 3, 4, 5, 6]
- Struct kế thừa từ Enumerable
Bởi vì Struct kế thừa từ Enumerable vậy nên bạn có thể sử dụng bất kì method nào của Enumrable.
class Pair < Struct.new(:first, :second)
def same?
inject(:==)
end
def add
reduce(:+)
end
end
a = Pair.new(4,4)
# => #<struct Pair first=4, second=4>
a.same?
# => true
a.add
# => 8
b = Pair.new(5,6)
# => #<struct Pair first=5, second=6>
b.same?
# => false
b.add
# => 11
$:
Biến $:
là đường dẫn cho Ruby gem, hoặc Ruby có định nghĩa sẵn có thể dùng $LOAD_PATH. Bạn có thể add đường dẫn thư mục gem cần load tới load path bằng cú pháp:
$: << '.'
inspect
The inspect method là default method của Object khi bạn gọi Class#new.
class Human < Struct.new(:name, :age)
def inspect
"#{name} is a human of age #{age}"
end
end
joe = Human.new("Joe", 43)
# => Joe is a human of age 43
Hash#invert
Đảo ngược cặp Hash key-value:
{a: 1, b: 2}.invert
# => {1=>:a, 2=>:b}
Method#to_proc
Bạn có thể convert method sang proc
def plus_one(x)
x + 1
end
proc_increment = method(:plus_one).to_proc
proc_increment.call(4)
# => 5
[1,3,5,9].map(&proc_increment)
# => [2, 4, 6, 10]
module_function
module_function
là một module private với class. Được gọi tới bằng include.
module Mod
def one
"This is one"
end
module_function :one
end
class Cls
include Mod
def call_one
one
end
end
Mod.one #=> "This is one"
c = Cls.new
c.call_one #=> "This is one"
module Mod
def one
"This is the new one"
end
end
Mod.one #=> "This is one"
c.call_one #=> "This is the new one"
require_relative
require_relative
là cách tiện dụng để load các file ruby khác để đặt vào location của file hiện tại.
instance_methods
Với mỗi class bạn có thể gọi .instance_methods
để tìm tất cả các instance method mà được định nghĩa của class.
Enumerable
Enumerable là một module bao gồm các định nghĩa về định dạng cơ bản như Array, Hash, Struct. Vì vậy tất cả những tất các các định dạng đó đều có các instance method từ Enumrable.
defined?
defined?
method giúp bạn kiểm tra bất kể thứ gì là module, class, method..... Với class hay module bạn còn có thể kiểm tra method_defined?, public_method_defined?, private_method_defined?, and protected_method_defined?
–noprompt
Là 1 option nhỏ khi bạn viết code trong IRB (console), dòng lệnh với flag -noprompt
sẽ tạo IRB session, không có kí tự bên trái terminal. Dễ dàng hơn cho việc kiểm tra code cũng như copy & paste code chứ?
string % value(s)
Bạn có thể insert định dạng vào string bằng cách dùng method %
"Number: %f %f" % [1,2]
# => Number: 1.000000 2.000000
"Number: %e" % "6"
# => "Number: 6.000000e+00"
- ternary operator
_ ? _ : _
Toán tử Ternary thì khỏi cần nói tới nữa rồi nhỉ?
true ? 10 : 20
# => 10
false ? 10 : 20
# => 20
false ? 10 : 20 ? 30 : 40
# => 30
false ? 10 : !20 ? 30 : 40
# => 40
false ? 10 : 20 ? 30 ? 50 : 60 : 40
# => 50
false ? 10 : 20 ? !30 ? 50 : 60 : 40
# => 60
false ? 10 : !20 ? 30 ? 50 : 60 : 40
# => 40
ruby -e “#code”
Ta có thể chạy code ruby ngay trong command line
home:~$ ruby -e "puts 1 + 1"
2
%[]
%[]
cho phép bạn sử dụng ngoặc đơn, ngoặc kép cùng cả nhúng #{}
vào string
%[Hello #{ "World!" } "Quoted" and 'quoted!']
# => "Hello World! \"Quoted\" and 'quoted!'"
- erb
ERB là thư viện quá quen thuộc được tích hợp Ruby, bạn có thể sử dụng để sử dụng để thực thi code ruby:
require "erb"
ERB.new(%[<html><body>Hello <%= "Wor" + "ld!" %></body></html>]).result
# => "<html><body>Hello World!</body></html>"
- undefined Class instance variables không báo lỗi.
Khi một biến chưa được định nghĩa và bạn sử dụng class instance variable, thực ra tên gọi cao siêu mà nó đơn giản là cái biến @ đấy, Ruby sẽ trả về nil chứ ko phải “undefined” errors.
@a
# => nil
@a.to_i
# => 0
class A
end
A.new.instance_eval {@a}
# => nil
def count_from_one()
@num = @num.to_i + 1
end
count_from_one
# => 1
count_from_one
# => 2
count_from_one
# => 3
count_from_one
# => 4
- UnboundMethod
Bạn có thể trích xuất một instance method của một class và sử dụng như một Proc độc lập với bind
.
split = String.instance_method(:split)
# => #<UnboundMethod: String#split>
class String
undef :split
end
"asdf".split("s")
#NoMethodError: undefined method `split' for "asdf":String
split.bind("asdf").call("s")
# => ["a", "df"]
ObjectSpace
Bạn có thể truy xuất bất cứ thứ gì được cấp phát trong bộ nhớ thông qua ObjectSpace
class A
end
3.times do
A.new
end
ObjectSpace.each_object(A).count
# => 3
ObjectSpace.each_object(A).to_a
# => [#<A:0x0000000204cc00>, #<A:0x00000002244800>, #<A:0x00000002254430>]
freeze
Có một mẹo đơn giản để khoá một object, như tên gọi của hàm vậy, freeze
một object sẽ không thể chỉnh sửa được.
module Test
def self.example
"Hello World!"
end
end
Test.freeze
Test.example
# => "Hello World!"
module Test
def self.asdf
123
end
end
#RuntimeError: can't modify frozen Module
self
có thể dùng để thay thế object name
module Apple
def Apple.chew
"munch munch"
end
end
Apple.chew
# => "munch munch"
def Apple.cut
"chop chop"
end
Apple.cut
# => "chop chop"
class A
def A.foo
"bar"
end
end
A.foo
# => "bar"
- Bạn có thể truy cập scope có level cao nhất với
::
module A
def self.test
"FOO"
end
end
module Thing
module A
def self.test
"BAR"
end
end
def Thing.inner
A.test
end
def Thing.outer
::A.test
end
end
Thing.outer
# => "FOO"
Thing.inner
# => "BAR"
prepend
prepend
thêm một module vào chuỗi class mà nó kế thừa. Từ đó ta có thể gọi các methods ra. Lưu ý đặt tên method thật cẩn thận để tránh gây sai xót không đáng có nhé.
module A
def split
self.upcase
end
end
String.prepend A
String.ancestors
# => [A, String, Comparable, Object, Kernel, BasicObject]
"asdf".split
# => "ASDF"
super
super
hiểu đơn giản là method gọi tới parent class method.
module A
def split(*_)
super("a")
end
end
class B < String
def split
super("b")
end
end
b = B.new("123abc")
# => "123abc"
b.split
# => ["123a", "c"]
B.ancestors
# => [B, String, Comparable, Object, Kernel, BasicObject]
String.prepend A
b.split
# => ["123", "bc"]
B.ancestors
# => [B, A, String, Comparable, Object, Kernel, BasicObject]
arity
arity
giúp bạn biết được có bao nhiêu parameter mà một method cần.
->{}.arity
# => 0
->_{}.arity
# => 1
->_,_{}.arity
# => 2
->*_{}.arity
# => -1
"".method(:upcase).arity
# => 0
String.instance_method(:upcase).arity
# => 0
- cloning Arrays
Khi bạn dùng method Array#clone
bạn có được một array khác giống hệt các phần tử chưa trong array cũ. Tuy nhiên sẽ không có thêm phần bộ nhớ nào được cấp phát cho các phẩn tử trong array mới, chúng chỉ khác nhau ở con trỏ trỏ tới các phần tử (mình nói nôm na định nghĩa bên C/C++ là thế :v). Vì vậy clone sẽ hữu hiệu khi bạn xử lý cần tính toán tiết kiệm bộ nhớ. Array#dup
cũng là một method tương tự.
class Thing
end
a = [Thing.new, Thing.new]
# => [#<Thing:0x000000017eba48>, #<Thing:0x000000017eba20>]
b = a.clone
# => [#<Thing:0x000000017eba48>, #<Thing:0x000000017eba20>]
a.object_id
# => 12541180
b.object_id
# => 12522640
a.map(&:object_id)
# => [12541220, 12541200]
b.map(&:object_id)
# => [12541220, 12541200]
Nếu bạn chỉnh sửa Array bạn không cần lo về array được clone bị ảnh hưởng, tuy nhiên nếu bạn chỉnh sửa một phần tử trong array thì cả 2 phần tử trong 2 array sẽ thay đổi.
- Gía trị mặc định của Hash
Như ví dụ trên về duplicate một array nhưng lại tạo ra array mà chung các phần tử trong bộ nhớ, chúng ta cũng có cùng các phần tử được trả về khi ta set giá trị mặc định cho Hash.
h = Hash.new
# => {}
h.default = []
# => []
a = h[:c]
# => []
b = h[:d]
# => []
a[0] = 1
# => 1
h[:z]
# => [1]
Để khắc phục điều trên, tốt hơn ta nên dùng default_proc để tạo new Array.
h = Hash.new
# => {}
h.default_proc = ->hsh,key{ hsh[key] = [] }
# => #<Proc:0x00000001f66598@(irb):8 (lambda)>
a = h[:c]
# => []
b = h[:d]
# => []
a[0] = 1
# => 1
h[:z]
# => []
class_eval
cùng vớiincluded
Sử dụngclass_eval
khi viết thêm method include vào class là một cách tự nhiên để update class.
class A
end
module Example
def self.included(base)
base.class_eval do
def example
"instance method"
end
def self.example
"class method"
end
end
end
end
A.include Example
A.example
"class method"
A.new.example
"instance method"
inherited
Khi bạn có một class kế thừa 1 class khác như kiểu "mixin" bạn có thể sử dụng như sau:
class Foo
def self.inherited(base)
base.class_eval do
def bar
"Drinking at the bar!"
end
end
end
def foo
"bar"
end
end
class A < Foo
end
A.new.foo
# => "bar"
A.new.bar
# => "Drinking at the bar!"
Foo.new.foo
# => "bar"
Foo.new.bar
#NoMethodError: undefined method `bar' for #<Foo:0x00000001916378>
String#chars
Split từng kí tự trong string
"asdf".chars
# => ["a", "s", "d", "f"]
break :value
Tương tự như các ngôn ngữ khác, break phá vòng lặp và bạn có thể return giá trị tại vòng lặp đó.
x = loop do
break 9
end
x
# => 9
- Toán tử
&.
độc lập Thực ra cách dùng của nó chính là tương tự nhưtry.
thôi, bạn có thể dùng nó thay thế cho đơn giản dễ viết hơn.
@a&.size
# => nil
@a = [1,2,3]
@a&.size
# => 3
Hash#to_proc
hsh = {a: 1, b: 2, c: 3}
[:a, :b, :c].map(&hsh)
# => [1, 2, 3]
Bạn có thể đặt một dấu & trước object như là một parameter, nó được gọi là method to_proc
.
retry
Trong bất kì một blockbegin/rescue/end
, bạn có thể dùng retry để lặp lại đoạn code thực thi mỗi khi error được raise lên.
begin
@x = @x.to_i + 1
raise "Error" if @x < 5
rescue
puts "We're rescuing!"
retry
end
We're rescuing!
We're rescuing!
We're rescuing!
We're rescuing!
# => nil
raise
raise
có thể có 3 tham số, text, error class, where error from.
raise "Hello World!"
#RuntimeError: Hello World!
raise StandardError, "Hello World!"
#StandardError: Hello World!
class DigestionError < StandardError
end
raise DigestionError, "stomach hurts", "bad food"
#DigestionError: stomach hurts
# from bad food
- FILE Tên file hiện tại
# dir2/test.rb
puts __FILE__
test.rb
/full/path/to/dir2/test.rb
- LINE Dòng hiện tại
class LineNumber
def self.before lineno, param
puts "line number is #{lineno}"
end
def self.after param, lineno
puts "line number is #{lineno}"
end
end
print "before with __LINE__: "
LineNumber.before __LINE__, <<TEXT
This is some text.
As is this.
This, also, is some text.
TEXT
print "after with __LINE__: "
LineNumber.after <<TEXT, __LINE__
This is some text.
As is this.
This, also, is some text.
TEXT
print "after with __LINE__ + 0: "
LineNumber.after <<TEXT, __LINE__ + 0
This is some text.
As is this.
This, also, is some text.
TEXT
# fin
Output:
before with __LINE__: line number is 14
after with __LINE__: line number is 25
after with __LINE__ + 0: line number is 28
Hash.[]
Hash[:array, :of, :key, :value, :pairs, "."]
# => {:array=>:of, :key=>:value, :pairs=>"."}
- Biến toàn cục
$
định nghĩa biến toàn cục, tuy nhiên bạn không nên sử dụng chúng. Hằng số có thể sử dụng thay thế, bạn cũng có thể định nghĩa biến toàn cục bằng các sử dụng ::
trước tên biến.
module Kludge
def Kludge.ugly
$marco = :polo
end
end
$marco
# => nil
Kludge.ugly
$marco
# => :polo
module Nice
def self.thing
::Marco = :polo
end
end
Marco
#NameError: uninitialized constant Marco
Nice.thing
Marco
# => :polo
$0
$0
là file gốc được thực thi trong ruby. Giống như python, name == “main”
để chỉ chạy code trong file có tên là "main", tránh rung code từ các file ko cần thiết.
if __FILE__ == $0
puts "You ran this #{__FILE__} directly! :-)"
end
case
permits then
case 1
when 1
then
puts "yes"
end
#yes
# => nil
case
không cần value
y = 4
case
when y == 4 then puts "yes"
end
#yes
# => nil
case
then
là tuỳ chọn
case 4
when 4
puts "yes"
end
#yes
# => nil
- case
===
method
module Truth
def self.===(thing)
puts "=== has been called!"
true
end
end
case :pizza_is_delicious
when Truth
puts "yummy food for my tummy"
end
#=== has been called!
#yummy food for my tummy
# => nil
tail conditions
Bạn có thể đặt if
recuse
sau code nhé. Đôi lúc sẽ giúp code trông thoáng và dễ hiểu hơn nhiều.
@x = begin
5
end if false
@x
# => nil
raise "Error" rescue :all_clear
# => :all_clear
if true
puts "Hello World!"
end if false
# => nil
- sử dụng
return
Ruby sẽ tự động trả về object cuối cùng, tuy nhiên return sẽ trả về giá trị và dừng thực thi code tại vị trí bạn muốn.
@x = 3
def a
return true if @x == 3
false
end
a
# => true
@x = 5
a
# => false
All rights reserved