やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

Rubyの出力関数(p pp puts print printf)の違い

Rubyには標準出力するための関数が複数、用意されています。
今更感はありますが、自分が把握しているだけでも5つも関数がなり、一体、何が違うのやら...。

  • puts
  • p
  • pp
  • print
  • printf

それぞれの出力結果を見比べながら、どのように使い分けるべきかを考察してみたいと思います。

puts

最もよく使われている標準出力がputsではないでしょうか。
putsは指定された値を出力しつつ、改行(\n)を出力してくれます。またドキュメントを見て分かるようにputsは引数を複数(*args)、指定することが可能です。

puts "hello"
# hello

puts "hello", "world", "unagi"
# hello
# world
# unagi

Kernel.#puts (Ruby 3.2 リファレンスマニュアル)

p

最初はpputsのアライアスだと思っていたのですが、全く別の関数でした。
putsとは異なりpは引数に指定された値をそのまま返してくれます。型もそのままなので、処理の途中にpを挟んで中間結果を出力する...というような使い方が出来るのではないでしょうか。

r1 = p "hello"
# "hello"

puts r1
# hello

r2 = p "hello", "world", "unagi"
# "hello"
# "world"
# "unagi"

puts r2
# hello
# world
# unagi
lst = (1..10).to_a
lst.map { |e| e * 2 }.map { |e| p e }.filter { |e| e.odd? }
# 2
# 4
# :
# 20

putsとは地味に出力形式が異なり、Stringの値を出力する際は"を出力してくれます。

Kernel.#p (Ruby 3.2 リファレンスマニュアル)

pp(pretty print)

個人的にはあまり使ったことがなかったのですがppという関数もあります。
pppretty printの略称だそうで、putspがIOに定義された関数であるのに対して、ppはライブラリに定義された関数です。ppは複雑な情報をいい感じに出力してくれるとのことで、クラスや構造体の値を確認する際は重宝しそうです。

普段使いする感じだとpと変わらない。

pp "hello"
# "hello"

pp "hello", "world", "unagi"
# "hello"
# "world"
# "unagi"

試しにRuboCopでAST::Tokenの出力を見比べてみると、圧倒的にppの方が見やすい。
(情報量が多いため、一部の情報を抜粋しています)

# ppで出力
[#<RuboCop::AST::Token:0x0000000104790888
  @pos=#<Parser::Source::Range /Users/takamizawa46/workspace/ruby/rubocop-reading/main.rb 0...29>,
  @text="# frozen_string_literal: true",
  @type=:tCOMMENT>,
 #<RuboCop::AST::Token:0x0000000104790838
  @pos=#<Parser::Source::Range /Users/takamizawa46/workspace/ruby/rubocop-reading/main.rb 31...32>,
  @text="p",
  @type=:tIDENTIFIER>,
  :
]

# putsで出力
[[1, 0], tCOMMENT, "# frozen_string_literal: true"]
[[3, 0], tIDENTIFIER, "p"]
:

library pp (Ruby 3.2 リファレンスマニュアル)

print

最初にprintを見た時は「Pythonと間違えて書いてる人いるやん...」と思ったのですが、間違っているのは自分の方でした。こちらもIOに定義された関数でputsと同じ引数をとりますが、出力形式が異なっておりputsでは改行を出力してくれるのに対してprintは改行を出力してくれません。

print "hello"
print "hello", "world", "unagi"
# hellohelloworldunagi

あまりユースケースが思いつきませんが、複数の文字列を結合した状態で画面に出力したい場合は楽ですね。

Kernel.#print (Ruby 3.2 リファレンスマニュアル)

printf

ログの出力形式を指定する際に、少しだけ使ったことがあります。
フォーマットが指定できたり、出力先のポート(デフォルトでは$stdout)が指定できたり...とC言語のprintfに近いものだそうです。自分はgolangでprintfを多用しているので、そちらのイメージの方が近かったです。

printf "%s %s!", "hello", "world"
# hello world!

出力形式が複雑になるようなケースだとprintfがピッタリだと思います。

class Log
  DEFULT_FORMAT = "[%s][%s]: %s\n"
  
  def output(level, message)
    now = Time.now
    printf DEFULT_FORMAT, level, now, message 
  end
end

logger_ = Log.new
logger_.output('INFO', 'ping')
# [INFO][2023-07-10 13:47:06 +0000]: ping

ポートの指定も可能でした。
実行するとhello world!が書き出しされたsample.txtが生成されます。

f = open('sample.txt', 'w')
printf f, "%s %s!", "hello", "world"

Kernel.#printf (Ruby 3.2 リファレンスマニュアル)

まとめ

  • puts: 値と改行を出力する。最も広く認知されている出力関数かも
  • p: ほぼputsと同じだが、指定した値が戻り値となる
  • pp: pretty printの略で、いい感じに情報を整形して出力してくれる
  • print: putsとは異なり、改行を出力しない
  • printf: printにフォーマットとポートが指定可能な出力関数

普段使いはputsで十分かなと思います。
他の出力関数はユースケースに合わせて使う機会があるかもね...というぐらいですが、違いを知っておくことで表現のレシピが増えます。

少しでも「ええな〜」と思ったらイイネ!・シェア!・はてなブックマークを頂けると励みになります。