私は他の言語経験があります。最近Rubyを勉強して、「初めてのRuby」の本を読んでいると、不思議な疑問が出てきました。勉強しつつ分かった内容をまとめます:
1. Rubyは関数型言語ですか?
Javascriptなどの関数は言語のファーストクラスであり、呼び出すことだけではなく、値として引き渡すこと、オブジェクトとしてその関数の属性を引用することも出来ます。Rubyはそうではないでしょうか?
自分の答えは:
- そうです。
- 関数の道具は
ブロック
、lambda
、proc
、Object#method
など幾つかあります。
ただし、Rubyの関数を呼び出す際、括弧を省略出来るルールがあります。また、関数の括弧を省略しても、関数の値が取れません。method(:name)
/obj.method(:name)
などを利用すれば、関数の値が取れます。
print("abc") # print "abc" and return nil
print "abc" # print "abc" and return nil
print() # print "" and return nil
print # print "" and return nil
# 括弧がなくてもprint()と同じで、print関数の値を取れません
method(:print) # => #<Method: Object(Kernel)#print>
ちなみに、関数の括弧を省略することはよく覚えなければなりませんね。私は下記の魔術をよくわかりませんでした:
{a:1, b:2}.map.with_index do |(key,value), index| "#{index} : #{key} => #{value}" end
# => ["0 : a => 1", "1 : b => 2"]
実はmap
関数の括弧が省略されています。そしてmap
の戻り値はEnumeratorのインスタンスであり、with_index
はEnumeratorのメソッドです。
enumerator = {a:1, b:2}.map()
# => #<Enumerator: {:a=>1, :b=>2}:map>
enumerator.method(:with_index)
# => #<Method: Enumerator#with_index>
# with_indexはEnumeratorのメソッドです
enumerator.with_index() {|(key,value), index|
"#{index} : #{key} => #{value}"
}
# => ["0 : a => 1", "1 : b => 2"]
2. blockは関数の最後の引数か?
違いますよ。ブロックは関数の引数ではなく、引数リストの後の特別なしっぽです。
マニュアル≫ブロック付きメソッド呼び出しによると:
文法:
method(arg1, arg2, ...) do [`|' 式 ... `|'] 式 ... end
method(arg1, arg2, ...) `{' [`|' 式 ... `|'] 式 ... `}'
method(arg1, arg2, ..., `&' proc_object)
特に&proc_object
の形は少し変なものです:
["A","B","C"].map{|a| a.downcase}
# => ["a", "b", "c"]
["A","B","C"].map(&:downcase)
# => ["a", "b", "c"]
# &:downcaseは式の形ですけど、実は評価出来ないものです。
&:downcase
# SyntaxError: (irb):21: syntax error, unexpected &
# &:downcase
# ^
# from /usr/local/bin/irb:11:in `<main>'
&:downcase
は式
ではない、値
を評価出来ない、ただの記号
みたいものです。
上記と似ている&proc
の形も、ただの記号
みたいです:
proc = Proc.new { |a| a.downcase }
["A","B","C"].map &proc
# => ["a", "b", "c"]
&proc
# SyntaxError: (irb):31: syntax error, unexpected &
# &proc
# ^
# from /usr/local/bin/irb:11:in `<main>'
#
上記のコードもブロック
は関数の引数ではなく、引数の値に評価出来ない、ただの記号
である証拠です。
3. なぜFile.openの呼び方は、C言語と似ている伝統的なスタイルとブロックスタイルと両方で出来るのか?
file_contents = ''
f = File.open('.vimrc', 'r')
file_contents = f.read
f.close
file_contents = File.open('.vimrc', 'r') {|f| f.read }
File.openは不思議な関数ですね:
- どうして2種類のスタイル両方で出来るのか?
- ブロックスタイルだとどうして自動的に
f.close
を呼び出してリソースリークを避けられるのか?
答えは3つのポイントです: block_given?
、yield
、ensure
def my_fopen(name, mode)
f = File.open(name, mode)
return f unless block_given?
begin
yield f
ensure
p "ensure : f.close"
f.close
end
end
# style 1:
file_contents = ''
f = my_fopen('.vimrc', 'r')
file_contents = f.read
f.close
p file_contents
# style 2:
file_contents = my_fopen('.vimrc', 'r') {|f| f.read }
p file_contents
4. Sinatraのhelpers
ブロックの中でsuper
を呼び出せることが不思議です!
# Sinatra helpers例
helpers do
def find_template(views, name, engine, &block)
....
super(folder, name, engine, &block)
end
end
ここの秘密はclass_eval
です。簡単な例を作ってみます:
class MyBase
def foo()
p 'MyBase::foo'
end
end
class MyTest < MyBase
def helpers(&proc)
self.class.class_eval(&proc)
#selfはMyTestのインスタンスです
#self.classはMyTestです
end
end
my_test = MyTest.new
my_test.helpers {
def foo()
p "my_test.foo"
super
end
}
my_test.foo
# output :
# "my_test.foo"
# "MyBase::foo"
5.なぜSinatraのget/postブロックの中でSinatra::Requestのparams属性にアクセス出来るのか?
require 'sinatra'
get '/hello/:name' do
"Hello #{params[:name]}!"
# ここのparamsは不思議なものです
# なぜブロックの中にSinatra::Requestのparams属性がアクセス出来ます
end
ここのポイントはinstance_eval
です。簡単に一番小さいRackフレームワークを作ってみます。そのフレームワークはSinatraのようなget/post DSLを提供します:
# app を実装.
# get/post ブロックの中でRack::Requestの属性にアクセス出来ます
get do
[200, {}, ["hello world // Get : params : " + params.to_s + "\n"]]
end
post do
[200, {}, ["hello world // POST : body : " + body.read + "\n"]]
end
下記は全ての実装コードです:
# library を実装
require 'rack'
class MyRackApp
@@process_map = {}
def self.register(method, proc)
@@process_map[method] = proc
end
def call(env)
request = Rack::Request.new(env)
proc = @@process_map[request.request_method]
if proc.nil?
[500, {}, ["未実装"]]
else
request.instance_eval(&proc)
end
end
end
def get(&proc)
MyRackApp.register('GET', proc)
end
def post(&proc)
MyRackApp.register('POST', proc)
end
# app を実装
get do
[200, {}, ["hello world // Get : params : " + params.to_s + "\n"]]
end
post do
[200, {}, ["hello world // POST : body : " + body.read + "\n"]]
end
# library を実装
rack_app = MyRackApp.new
Rack::Handler::WEBrick.run rack_app, :Port=>3000