7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Ruby初心者の不思議な経験

Last updated at Posted at 2015-01-25

私は他の言語経験があります。最近Rubyを勉強して、「初めてのRuby」の本を読んでいると、不思議な疑問が出てきました。勉強しつつ分かった内容をまとめます:

1. Rubyは関数型言語ですか?

Javascriptなどの関数は言語のファーストクラスであり、呼び出すことだけではなく、値として引き渡すこと、オブジェクトとしてその関数の属性を引用することも出来ます。Rubyはそうではないでしょうか?

自分の答えは:

  • そうです。
  • 関数の道具はブロックlambdaprocObject#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?yieldensure

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
7
7
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?