Baby steps to migratory bird

元半導体系エンジニア、今Webエンジニアの雑記

【Ruby】putsの返り値はnilらしいと聞いて色々みてみた。

きっかけ

Railsチュートリアルを眺めててたら

4.2.4 メソッドの定義の演習3で

palindrome_tester("racecar")に対してnil?メソッドを呼び出し、
戻り値がnilであるかどうかを確認してみてください 
(つまりnil?を呼び出した結果がtrueであることを確認してください)。
(以下略)
def palindrome_tester(s)
  if (条件。問題なので省略)
    puts "It's a palindrome!"
  else
    puts "It's not a palindrome."
  end
end

と書いてあり、なんでだろ?と思ったので調べてみた。

また、 注記13にもこんなことが書いてある。

実際には些細な違いがあり、pメソッドは画面出力だけでなく戻り値も
オブジェクトになります。
しかし、putsメソッドの場合は引数によらず必ずnilが戻り値になります。
(以下略)

リファレンスを見てみる

module function Kernel.#putsより

puts(*arg) -> nil

引数と改行を順番に 標準出力 $stdout に出力します。 引数がなければ改行のみを出力します。

引数が配列の場合、その要素と改行を順に出力します。 
配列や文字列以外のオブジェクトが引数として与えられた場合には、 
当該オブジェクトを最初に to_ary により配列へ、 
次に to_s メソッドにより文字列へ変換を試みます。
 末尾が改行で終っている引数や配列の要素に対しては puts 自身 は改行を出力しません。

なるほど標準出力されるのか〜

元々標準出力に向けてアウトプットするためのメソッドであり、返り値を得るようにはできてないですよってことなのだろうか。

似ているメソッドとして、module function Kernel.#pも見てみる。

p(*arg) -> object | Array

引数を人間に読みやすい形に整形して改行と順番に標準出力 $stdout に出力します。
主にデバッグに使用します。

引数の inspect メソッドの返り値と改行を順番に出力します。つまり以下のコードと同じです。

print arg[0].inspect, "\n", arg[1].inspect, "\n", ...
整形に用いられるObject#inspectは普通に文字列に変換すると
区別がつかなくなるようなクラス間の差異も表現できるように工夫されています。

p に引数を与えずに呼び出した場合は特に何もしません。

こっちは出力した情報を色々抜き出したりできるよう、返り値がオブジェクトになっているっぽい。

試してみる

Rails上で比較してみた。

  • メソッドではメッセージ作成のみ。viewで<%= %>
# 例えばapplication_helper.rb
def thanks_message(user_name = "ユーザー")
  "#{user_name}さん、ありがとうございます"
end

# 例えばthanks.html.erb
<div class="thanks_message"
  <%= thanks_message(@user.name) %>
<div>

<結果> 当たり前だけど(ユーザー名)さん、ありがとうございますと表示される。

  • メソッド内でメッセージをputsして、それを<% %>viewに埋め込む
# 例えばapplication_helper.rb
def thanks_message(user_name = "ユーザー")
  puts "#{user_name}さん、ありがとうございます"
end

# 例えばthanks.html.erb
<div class="thanks_message"
  <% thanks_message(@user.name) %>
<div>

<結果> viewには何も表示されない! 一方、rails sしてるconsoleの方を見てみるとこっちに出力されてた。

色々試したが整理するとこんな感じ↓

メソッド内の出力メソッド↓/viewの表記→ <% %> <%= %>
なし 非表示 表示
puts 非表示 非表示
p 非表示 表示

(pでも表示できるけど無駄感ハンパない。。)

余談

そもそも<%= %>は何を出力してくれるのか

結果を出力

なるほどわからん。

  #   <%# WRONG %>
  #   Hi, Mr. <% puts "Frodo" %>

はい、ごめんなさい。。

ここらへんだろうか https://github.com/jeremyevans/erubi/blob/master/lib/erubi.rb#L129-L133

when '='
          rspace = nil if tailch && !tailch.empty?
          add_text(lspace) if lspace
          add_expression(indicator, code)
          add_text(rspace) if rspace
# Add the given ruby expression result to the template,
    # escaping it based on the indicator given and escape flag.
    def add_expression(indicator, code)
      if ((indicator == '=') ^ @escape)
        add_expression_result(code)
      else
        add_expression_result_escaped(code)
      end
    end

    # Add the result of Ruby expression to the template
    def add_expression_result(code)
      @src << " #{@bufvar} << (" << code << ').to_s;' // to_sで文字列に変換
    end

    # Add the escaped result of Ruby expression to the template
    def add_expression_result_escaped(code)
      @src << " #{@bufvar} << #{@escapefunc}((" << code << '));'
    end

結局の所to_sで文字列に変換してるということらしい。

to_sはObject classのメソッドなので、何でも文字列として返す(はず)。

例えば対象がarrayでも

[1, 2, 3].to_s
=>"[1, 2, 3]" # 返り値はこんば文字列になる

まとめ

  • putsの返り値はnil, pの返り値はarrayオブジェクト
  • viewに出力するときはオブジェクトを用意して素直に<%= %>で書こう