Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Ruby: "uselessシンタックスシュガー"シリーズ「キーワード引数やハッシュの値省略」(翻訳)

概要

原著者の許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。段落編成を若干変更し、訳文の一部に強調を加えています。

Ruby: "uselessシンタックスシュガー"シリーズ「キーワード引数やハッシュの値省略」(翻訳)

本記事は、最近のRubyで出現した"無用な"(さもなければ物議を醸す)構文要素を扱うシリーズ記事の一環です。本シリーズの目的は、そうした機能を擁護することでも批判することでもなく、その機能が導入された理由、設計、そして新構文を使うコードに与える影響を分析するための一種の「思考のフレームワーク」を皆さんと共有することです。本シリーズのあらまし記事もご覧ください。

今回取り上げるトピックは小さなものですが、ハッシュ1 やキーワード引数の値を省略可能にする機能は、複雑な心境を大いにかきたてました。

🔗 値省略とは何か

Ruby 3.1から以下のコードが動くようになりました。

x = 5
# ハッシュを構築する
h = {x:}  # `h = {x: x}`と同じ
#=> {x: 5}

def distance_to(x:, y:)
  # ...
end

# `x: 5`を暗黙で渡す
distance_to(x:, y: 10)

これは、メソッドや定数といったローカルで利用可能な名前についても同様です。

def m = "method result"
C = 3

{C:, m:}
# {:C=>3, :m=>"method result"}

🔗 値省略が導入された理由

この機能が求められた表面上の理由は、ある程度明らかになっています。{id: id, user: user, payload: payload}のようなものをいくつも書いていると、無駄なことを書いているのではとバカバカしい気持ちになるかもしれません。こんなわかりきったことを何度も何度も書かなければいけない理由がどこにあるのでしょうか?コンピュータならズバリ推測できるのではないでしょうか?

ただし、ここで興味深い問いかけは「この種の冗長なコードはたくさんあるだろうか?」「たくさんあるとすればどんな理由でそうなるのか?(もしかすると構文が最適化されておらず、除去すべき何らかの"臭い"が立ち込めているのでしょうか?)」の方です。

問いかけ1の答えはシンプルです。「はい、どっさりあります」。Rubyの標準ライブラリ(CSVFileUtils)でも、Railsでも、あのRubocopでも(なおRubocopのメンテナーはこの省略機能がお気に召さないようです)、私が目にしたあらゆるproductionコードベースにも、ハッシュの受け渡しや、キーワード引数によるメソッド呼び出しがわんさか見つかるのが普通です。

これらのハッシュやキーワード引数は、個別のキーとマッチする名前を持つ、ローカルで利用可能な値からかき集められます(大胆なことを言わせていただければ、構成要素のこのような不在は、普通なら「そのコードがレガシーである」か「コンテキストが極めて特殊(多くの場合低レベル)」というシグナルか、さもなければ「個人が意識的に選んだスタイル」でしょう)。

しかし、キーと値に同じ名前を書くことがこれほど頻発する理由は何でしょうか?外部的な理由や内部的な理由については、ある程度特定できそうです。

主な外部的理由: 現在のRubyはWebアプリケーションやある種のデータ処理(ほとんどはWebアプリケーションで必要な範囲で)に使われることが多いので、「JSONの受け取り」「JSONの解析」「JSONの受け渡し」「JSONのビルド」を大量に行うことになり、そのほとんどはキーが文字列2になっている辞書です。JSONだけではありません、現代のWebアプリケーションはやや形式化された巨大ネステッドツリー構造をあちこちで使いまわしていることで知られています。

しかし内面的理由の方がずっと興味をそそり、かつ多面的です。

Rubyにキーワード引数が導入された経緯は非常に興味深いものです(他の言語の場合とかなり異なっていたようですが、これについては後述します)。

Ruby 2.0より前には、キーワード引数というものはありませんでした。しかしRubyの構文の寛容さを利用すれば、呼び出し側でキーワード引数的なものを模倣可能でした。末尾の引数がハッシュの場合は、以下のようにハッシュの{}を省略できます。

File.open('data.bin', mode: 'b')

上の書き方は、「位置引数」と、mode:という「名前付き引数」の2種類を渡して呼んでいるように見えますが、実は以下とまったく同等なのです。

File.open('data.bin', {mode: 'b'})

このopenメソッドは以下のように定義できます。

def open(path, options)
  # `options` will be a hash `{mode: 'b'}`

Rubyでは呼び出し側をとてもエレガントに書けるので、その時点で既に以下のように書けるDSLが人気を集めていました。

validate :field, presence: true

さまざまなオプション値の詰め合わせ(HTTPリクエストのパラメータなど)は、このように受け渡しできます。

余分なものを一切削ぎ落としたこのソリューションは魅力的です("巨大"言語の機能でありながら、そのための明示的な概念を追加せずに利用できるソリューションなのです)。そういうわけで、2013年にRuby 2.0から2.1にかけて「本物の」キーワード引数が導入されたとき、多くのRubyistたちが(私もその1人でした!)、キーワード引数が何だか冗長に感じられたものです。「Rubyには前からその機能があるのに!」(今でもそう考える人を見かけることは珍しくありません)。

しかし、適切な名前付き引数は、メソッド定義の使い勝手を大きく改善してくれます。メソッド定義で「どの引数が必須か」「どの引数が省略可能か」「デフォルト値はどの部分か」がひと目でわかるようになり、イントロスペクションが使いやすくなって言語でサポートできるようになります。

名前付き引数(Rubyではキーワード引数と呼ばれます)はできる限り時間をかけて穏やかにRubyに導入され3、Rubyユーザーの既存の直感4にも沿っていました。

呼び出し側では、引数がキーワード引数であってもなくても書き方は同じです。上で説明したようなメソッドを呼び出すときの書き方は、以下のように外見がまったく変わらなかったのです。

File.open('data.bin', mode: 'r')

定義側の構文は、できる限り「呼び出し側と同じように書けばよい」形に近づけられました。

# `mode:` (必須のキーワード引数)
# `chomp:`(デフォルト値ありのキーワード引数)
def open(path, mode:, chomp: false)
  # ここでは`mode`と`chomp`に個別の変数としてアクセス可能
end

こうして現在のRubyでは、すべての必須キーワード引数に値が渡されているかどうかをチェックし、オプショナルなキーワード引数にデフォルト値を提供して、それぞれの引数を独自のローカル変数に入れるようになりました。

ここで重要な点は、Rubyの名前付き引数の外見が、「辞書のデータ構造」や「一般のデータ構造」と非常によく似た形としてユーザーに認識されることです。

この感覚は、Rubyがサポートしているシンボル(Symbol)によって裏打ちされます。シンボルとは、「内部名」の表現に用いる専用クラスであり、ユーザーが提供する文字列から内部名を分離します。シンボルは、よく制御された辞書キー(およびキーワード引数名)と密接に関連します。

キーワード引数とハッシュは、どちらも外見がデータ構造のキーの外見に近く、構文がコンパクトで使いやすいおかげで、大規模システムにおけるデータ受け渡しの各ステップで引数のセットを調整・拡張・スライスするときに広く用いられるようになったのは自然な流れです。このような手法はRubyではよくあることです5

def track_time(user:, project:, start_at:, duration:, task: nil,
               allow_overwrite: true,
               # `project`引数を参照する
               enforce_limit: project.has_limits?,
               # 現在のクラスで定義されているものを参照する
               context: current_context)

上のようなコードは「引数が多すぎて"コードの臭い"がする」と不満を口にする人がいるかもしれませんが、私はこれをアドホック型とみなすことを提案します。このデータはすべて引数に渡す必要があり、「普通のオブジェクト指向の方式」ではさまざまなラッピング型を作成することになるでしょう(そのような型がどんどん増えていく可能性がありますが、どれも1〜2回しか使われません)。しかしハッシュ引数やキーワード引数によって、それが使われる場所にかかわらず、期待される構造型をその場で宣言できるようになります。

この点において、キーワード引数はパターンマッチングのプロトタイプ的なものと認識できそうです("正式な"パターンマッチングよりもずっと深いレベルでRuby言語に統合されています)。

名前付き値のセットを何度も何度も受け渡ししていると、キー名が値のローカル名とまったく同じになる状況が頻繁に発生します。上述のtrack_timeメソッド呼び出しでは、以下のように同じ名前が何度も出現する可能性があります。

time_record = create_time_record(user: user, start_at: start_at,
                duration: duration,
                enforce_limit: enforce_limit, allow_overwrite: allow_overwrite)
log_project_time(project: project, context: context)
schedule_recalculation(time_record: time_record, project: project)

こんなコードを毎日扱っていれば、多くの人が繰り返しを減らしたいと願うのも"無理はない"でしょう

🔗 値省略が導入されるまでの経緯

(シリーズのいつものチャプターですが、いつもとおもむきを変えました)

「値は省略し、キーは省略しない」という設計に残されている選択の余地はかなり少なくなっています。変更後のコードの外見ができるだけ変更前のコードと近くなるようにする必要があるなら、キーはそのままにして値を省略しなければなりません。この構文が人間にとってもパーサーにとっても曖昧にならないようにする必要があります。

そういうわけで、実際に選べる方法は以下しかなかったようです。

# 変更前
h = { val1: val1, val2: val2 }
# 値のみを省略した場合
h = { val1:, val2: }

# 変更前
call_something(val1: val1, val2: val2)
# 値のみを省略した場合
call_something(val1:, val2:)

JavaScriptの{val1, val2}にはコロン:がありませんが、この構文は上の2番目では使えない点にご注意ください。さらに上の1番目(ハッシュ)は、procの構文とも競合します({ val1 }は「メソッド名または変数名を1個だけ含んでいるproc」である可能性もあるのです)。
実を言うと私は、これについてはRubyの構文の方がより明確であると信じています。Rubyの構文は「少なくともキーがそこにある」ことを強く示唆しているのに対し、JavaScriptでは別のデータ構造リテラル(セットやタプルなど)と取り違えられる可能性が残っています。

14579#note-14で「この機能はぜひとも必要である」とMatzをようやく説得することに成功した後(既に知られているようにこれは簡単ではありませんでした)、構文はほぼ即座に決定されました。値のないキーは奇妙なものに見えるかもしれません(多くのベテランRubyistたちが「バグのように見えてしまう」と不満を述べていました)が、これと同じように見える構文は他にもRubyにあるのです。

# 必須のキーワード引数(Ruby 2.1〜)
def my_method(x:, y:)
  # ...

# パターンマッチング(Ruby 2.7〜)
hash => {x:, y:}

Matzはこの機能の導入に同意するとともに、「私たちが考えるときのマインドセットは(主に必須キーワード引数によって)一新された」とも述べていました(上述のリンクを参照)。このことは、マインドセットというものがこのようにして移り変わり、「有機的な感覚らしきもの」が(キーワード引数が導入されて普及したときに)このように微調整されるという興味深いデモンストレーションであり、そしてここから新機能(つまりパターンマッチングや値省略)という機能への扉が開かれたのです。

そのとき議論されたのは、「この省略構文のサポート対象はローカル変数にとどめるべきか、それとも現在のコンテキストでシンボルとして利用が許されているあらゆる名前をサポートすべきか」というものでした。メソッド呼び出しでは丸かっこ()を省略可能というRubyの寛容な構文によって、これには明らかなメリットが生じているのです。
たとえば以下のように書くことが可能です。

class TimeTracker
  attribute :user

  def context
    Context.create(...)
  end

  def track(project:, start_at:, duration:)
    date = Time.now.to_date

    create_time_record(
      user:, # これはクラス属性を参照する
      project:, starts_at:, duration:, # これは引数を参照する
      date:, # これはローカル変数を参照する
      context:, # これはクラスのメソッドを参照する
    )
  end

これは「barewords」と呼ばれるテクニックで、あらゆる内部名の外見が似たような形になります(@nameのようにもname()のようにもなりません)。そのおかげで、たとえばローカルで算出したシンプルな値を外部メソッドに変えるリファクタリングも、その逆のリファクタリングも手軽に行えるようになります。

🔗 値省略のむずかゆい点

ただし、寛容な構文がいいことばかりとは限りません!

この新機能の意外に便利な活用場所は、いわゆる「printデバッグ」です(pはRubyのデバッグプリント用メソッドです)。

x = 100
p(x:)
# {:x => 100}を出力する

これは、ローカル変数名やメソッド名と、それらの値を一発で出力できる、極めて短く便利な方法です。p(x)(これはxの値のみを出力します)より1文字多いだけですが、変数がたくさん使われている複雑なアルゴリズムをデバッグするときはさらに重宝します。

ただし問題は、以下のように書くことを好むRubyistたちが多いことです6

p x:

上の書き方はRubyのインタプリタでは許されていますが、おそらく以下のような思いもよらない結果が生じるでしょう。

x = 1
y = 100
p x:, y:
x + y

このコードは、一見すると{:x=>1, :y=>100}を出力してから101を返すように思えますが、実は{:x=>1, :y=>101}を出力してから同じ値を返すのです。
ここで問題となるのは、Rubyのステートメントは「行の末尾=ステートメントの終了」と解釈されますが、ステートメントが「次の行に継続している」と解釈される場合はそうではないということです。つまり、以下のコードは有効だということです。

x +
y

このコードが有効である理由は、これはRubyではx +と解釈され、このステートメントにはまだ続きがあると理解され、シンプルに次の行へ処理を進めるためです。

Ruby 3.1より前は、 x:の部分でステートメントが終了しているとみなさなかったので、パーサーは引き続き「次の行のコードも調べなければ」と信じ込みます。つまり先ほどのコードは以下と同じものとして解釈されます。

p(x:, y:
  x + y)

このp x:の振る舞いをどうするか、つまり「x:の部分でステートメントを区切る形で修正すべきか」「pの丸かっこ()の省略を禁止する形で修正すべきか」「少なくとも警告を表示すべきか」については#18396で引き続き議論中です。今のところ、 p(x:) のように丸かっこを省略しなければ正常に動作します!

面白いことに、RubyでもJavaScriptと同様にセミコロン;で式を明示的に区切ることが可能なので、p x:;という書き方にすることも一応可能ではありますが、Rubyではセミコロン;は悪趣味とされているため、可能な限り避けられています。そういうわけで私たちは丸かっこ()を省略しない方法にこだわっているのです。

Rubyの構文のユルさが問題につながった例はこれだけではありません。何バージョンか前のRubyで、範囲の上限を省略する"endレス" range,が導入されたときにも同様のことが起きました。"endレス" range構文は以下のバージョンチェックのようなコードを書くときに便利です。

case RUBY_VERSION
when '3.2'...
  "modern"
when '3.0'..'3.2'
  "supported"
when ...'3.0'
  "ancient"

しかし1個目のwhenは以下のように(誤って)解釈されてしまいます。

when '3.2'..."modern"
  # empty body

これについても、('3.2'...)のように丸かっこで囲めば簡単に修正できますが、心の中がもやっとします。Rubyのパーサーは「行末の...は丸かっこで囲む必要がありますか?(... at EOL, should be parenthesized?)」と警告しますが、p x:については(今のところ)警告を表示していません。

🔗 値省略の導入後どうなったか

(架空のタイムトラッカーアプリに)以下のコードがあるとしましょう。

TimeRecord.create!(
  company: company,
  user: System.current_user,
  date: date,
  starts_at: Time.parse(starts_at),
  duration: duration,
  project: context,
  task: task,
  limits: limits
)

このコードの作者が「キーワード引数の省略」機能を利用可能になったとすると、このコードはどうなるでしょうか?

以下のようになるでしょう。

TimeRecord.create!(
  company:,
  user: System.current_user,
  date:,
  starts_at: Time.parse(starts_at),
  duration:,
  project: context,
  task:,
  limits:
)

まず「コード量が減る」「同じことの繰り返しが減る」ことがわかります(さらに、このコードの呼び出し側の行数も減らせるようになります: 画面に表示するコンテキストを増やすには、「コードが上下方向に場所を取りすぎないことが重要」だからです)。

しかし、おそらくもっと重要なのは、「わかりきったことを述べるだけの」どうでもいいコードが消え去ると、わかりきっていないことを述べている残りのコードが浮かび上がってくることです(変更前はname: nameのような同義反復的な行がたくさんあって埋もれてしまいました)。値を省略できなかった以下の3つのキーで、どこが強調されるかにご注目ください。

user: System.current_user
この TimeRecordのオーナーは引数から渡されるのでも現在のスコープ内で算出されるのでもなく、何らかのグローバル値が使われていることが強調される。
project: context
contextとして知られているものが、実はタイムトラッキングの対象となる「プロジェクト」であることが強調される。
starts_at: Time.parse(starts_at)
starts_at(これはおそらく現在のメソッドの外部から来る引数でしょう)を利用する前にはparseを行わなければならないことが強調される。

少なくとも、こうした重要な点が強調されることは、コードを読む人がコンテキストの理解を深めるうえで有用です。

しかし、これはさらなる変更(つまり、削減されなかった3つのキーも他と同様に削減してキレイにする作業)でも有用なものになるでしょう。

userはグローバル値ではなくパラメータとする必要があるのでは?そうすれば癒着も減ってテストしやすくなるよね」

contextが何らかのProjectということなら、名前はprojectの方がわかりやすいのでは?( contextがフレームワークや基底クラス由来の抽象名で変えられないなら、せめてエイリアスにできないか?)」

parseする対象がdurationではなくstarts_atという名前だけど、理由はあるんだろうか?どっちもフロントエンドから来る文字列っぽいけど、わざわざ解析してバリデーションしておくほどのものだろうか?それとも現在のクラスやメソッドが他の層で呼び出される前に解析しておく必要があるんだろうか?」

言い換えれば、値省略という「建設的な怠け者」になれる構文が導入されると、こういった「ちょっと待った!もっとうまくやれるのに」というささやかながら深い洞察を得られる機会を増やせるかもしれないということです(もちろんその気があればの話ですが!)。運とイニシアチブに少々恵まれていれば、値省略ともっと「相性」のよいデータ構造を受け渡す書き方にシフトするだけで、システムのレイヤ間に潜む不一致や、パラメータの扱い方のばらつきを明らかにできるのです。

こぼれ話

以前あるコードベースで仕事をしていたときのことです。そこで使っていた「このレポートは自分用」または「このレポートはチーム全体用」を表すフラグがmine: true/falseme: true/falseallow: :me/:teamという具合にモジュールごとにまちまちで、モジュールの開発者も開発時期もばらばらという有様でした。

関係者の誰もが「これは理想的とは言えない」ことはわかっていましたが、構文がもっとコンパクトなら、これほどちっぽけな問題でも「それでいいよ」と見過ごす気持ちにはなりません。

たとえばuser: user行とdate_range: date_range行の間にmine: meという残念な行が混じっていても誰も嫌がりませんよね?しかしパラメータのデリケートな部分を値省略でDRYに書き換えれば、浮かび上がったmine: me行がたちまち虫歯のようにズキズキ痛みを発し、それによって用語集に合わせる形でモジュールを再編成するよう促されるわけです(作業は驚くほど簡単です)。

まとめると、「ものぐさな人を喜ばすための無駄な機能」だったものが、もしかすると大規模コードベースのメンテナンス性を高めてくれる優れものかもしれない、というお話でした。

🔗 ウクライナ通信

ほんの少しお時間をください。今の私たちが暮らしている状況を小さな思い出として記事の途中にはさむことにしています。私は現在戦争中の国で暮らしています。先週起きたささやかな出来事を無作為に選んでお伝えするものです。

今週は明るい話題です。ウクライナのEU加盟への交渉が今週発表されました。これは大した成果です。ほぼきっかり10年前、ウクライナの市民運動であるユーロマイダン(後にロシアが侵略の口実にしました)が始まったきっかけは、腐敗にまみれた大統領が突然EUとの提携政策をすべて白紙に戻してロシアにすり寄ろうとしたことでした。あれから10年、ついに私たちはここまでたどり着いたのです。

引き続き記事をどうぞ。

🔗 他の言語ではどうやっているか

JavaScriptではこうです。

x = 1
y = 5
{x, y} // => {x: 1, y: 5}

この比較を完全にするために申し上げておくと、JavaScriptの名前付き関数引数(named function arguments)は、「これは辞書です」方式と「これは宣言です」方式をミックスしています。

function foo({x, y = 5}) {
  console.log(x, y)
}
foo({}) // undefined, 5
foo({x: 6}) // 6, 5
foo({x: 6, y: 7}) // 6, 7

もしかすると私が見落としているかもしれません(名称が確定していない機能を調べるのは難しい!)が、今日に至るまで「辞書形式のショートハンド」構文を持っている言語はRubyとJavaScriptしかないらしいのです。これは、名前付き引数という概念を辞書に直接結びつける形で実装している言語がこの2つしかないという事実と関連があるかもしれません7。両者は機能的には異なりますが、構文の形としては似ている点が多く、「どの引数を名前付きにするか」はメソッド定義側で決定されます。

PythonやC#など、それ以外のほとんどの言語では、引数を名前付きにするかどうかは呼び出し側が決められます。

# Python
def open(path, mode='r'):
  # pathとmodeで何かする

# 以下はすべて有効
open('data.bin', mode='b')
open(path='data.bin', mode='b')
open('data.bin', 'b')

メソッド呼び出し側で特定の引数名を省略できないように強制する方法はたくさんありますが、どれも単なる追加機能や追加構文です。

また、Pythonの名前付き引数(mode='b')の書き方は、辞書形式({"mode": 'b'})と大きく異なっています。辞書の中身を取り出して名前付き引数に入れたり、渡されたすべての名前付き引数を集めたりするのは、**演算子でできます(Rubyはおそらくこの構文もPythonから拝借したのでしょう!)。Pythonistaにとって、引数の形式と辞書の形式の関係は見えにくいだろうと推測しています。

にもかかわらず、Pythonで最近提案された以下の省略構文は、Python作者にして優しい終身の独裁者であるGuido van Rossumによって承認されたのです。

# 以下のローカル変数がある場合、
mode = 'b'
# mode=modeを表す2通りの構文が議論された
open('data.bin', =mode)
open('data.bin', mode=)

後者のmode=は、Pythonのfstringsの自己ドキュメント化機能と一致しています8

print(f"{mode=}")
# これは変数名と値を両方出力する: "mode=b"

そのときに辞書の省略記法についても議論されることが時たまありましたが、いつもというわけではありません(上で述べたことを考慮すると、どうやらPythonでは名前付き引数と辞書は見た目も手触りも別物ということになります)。興味深いことに、最近の提案で以下のようなfstringライクな構文を見かけました。

x = 1
y = 2
{x=, y=} #=> {'x': 1, 'y': 2}

これはある程度論理的に筋が通っていそうですが、Pythonの通常の辞書構文からあまりにかけ離れています。今後どうなるかを見守りたいと思います。

この機能が存在する他の言語、または追加の提案をご存知でしたらお知らせください。 興味深いものを見落としてしまったら、この論点が台無しになってしまうかもしれません。

追記: /u/Richard-DegenneRedditで指摘してくれたのですが、OCamlにもラベル付き引数と、ローカル値をそれらの引数に「ピニング(pinning: ピン止め)」する機能があります。

(* ラベル付き引数numとdenomを受け取る関数*)
let ratio ~num ~denom = Float.of_int num /. Float.of_int denom;;
val ratio : num:int -> denom:int -> float = <fun>

(* 「ラベル: 値」形式で呼び出す *)
ratio ~num:3 ~denom:10;

(* ローカル名をラベルに"ピニング"して呼び出す *)
let num = 3 in
let denom = 4 in
ratio ~num ~denom;;

🔗 まとめ

今回のシリーズ記事には、いつもの「この先どうなるか」セクションがないことにお気づきかもしれません。やってはみたものの、この値省略構文がこの先どう発展するかについては、これといったものを思いつきませんでした(「行末のイライラ」を解決するといった小さな改善は別として)。

しかしその理由は、これが他のものに影響しない単なる「孤立した」機能だからではなく、むしろその逆です。値省略は、自然に「データパターンとしての引数」や「データの形状への注目」へと至る、小さな一歩に過ぎません。しかし仮にRubyがそこから先に進んで、そうした方向性を取り入れることがあれば、既に本シリーズのパターンマッチング編で詳しく説明したトピック、すなわち「データの形状を認識しやすくする」「データの取り出し(unpacking)を強力にする」「本物のパターンマッチングをRuby言語により深くブレンドする」に関連してくることでしょう。

現時点の研究における主な結論として、以下を挙げておきたいと思います。

  1. 冗長な書き方が取り除かれる
    これは一見大したことではなさそうですが、コードの本当に重要な部分に注目しやすくなります。
  2. 「ものぐさな」開発者が喜びそうなショートカット構文が提供される
    これは、この構文と相性の良いコードを書きたくなるインセンティブになる可能性があります。
    この小さな構文で注目すべき詳細は、「これまで頑張ってコードを書いてきた人にとって最悪のコードである」でもなければ「この構文が初めての人にどう見えるか」でもありません(構文に関するこうした議論はしばしばRubyトラッカーのトピックになります)。むしろ注目すべきは「値省略は通常のRubyコードで今後どんな書き方を推し進めるか」です。

シリーズ次回は引数のforwardingです。

今後の記事をフォローしたい方は、Substack に登録いただくか、Twitterでフォローをお願いします。


お読みいただきありがとうございます。ウクライナへの軍事および人道支援のための寄付およびロビー活動による支援をお願いいたします。このリンクから、総合的な情報源および寄付を受け付けている国や民間基金への多数のリンクを参照いただけます。

すべてに参加するお時間が取れない場合は、Come Back Aliveへの寄付が常に良い選択となります。

本記事(あるいは過去の私の仕事)が有用だと思えたら、Buy Me A Coffeeサイトにある私のアカウントまでお心づけをお願いします。戦争が終わるまでの間、ここへのお支払いは(可能な場合)私や私の戦友たちが必要とする装備または上述のいずれかの基金に100%充てられます。

関連記事

Ruby: "uselessシンタックスシュガー"シリーズ記事のあらましと予告(翻訳)


  1. 他の言語の好奇心旺盛な読者向けに: Rubyで歴史的に「ハッシュ(Hash)」と呼ばれているものは、他の言語では「辞書(dictionary)」や「マップ(map)」と呼ばれています。紛らわしいですよね、わかります(Ruby出身者が外部でその用語を使おうとしても同じぐらい混乱するでしょう)。しかし世の中そういうものです。そうそう、他の言語で「名前付き引数(named arguments)」と呼ばれているものは、Rubyでは「キーワード引数(keyword arguments)」と呼ばれています。 
  2. ただしRubyでは多くの場合、文字列と異なるシンボルをキーに使います。 
  3. キーワード引数は実に長い期間をかけて導入されました。キーワード引数への完全な移行、「新しい」キーワード引数と「古い」{}なしハッシュ記法が混在することで生じていたエッジケースや齟齬の除去が完了したのは、それから約7年も後のRuby 3.0のときでした。 
  4. はい、この言葉は今後も記事ごとに1度以上使っていくつもりです。 
  5. 引数のデフォルト値は、メソッド呼び出しごとにメソッド本体と同じコンテキストで計算されるので、非常に強力な「前処理」方法として利用できる点にもご注目ください。 
  6. メソッド呼び出しの引数リストを囲む丸かっこ()は、Rubyでは完全に省略可能です。現代のコードベースでは、()なしのメソッド呼び出しはクラスレベルのDSL(belongs_to :userなど)で使い、それ以外のメソッド呼び出しでは()を省略せずに使うことが合意されています。しかしクイックデバッグのpはこの合意から外れがちです。クイックデバッグではきっと()をできるだけ書かずに済ませたいでしょう。 
  7. その他の言語のうち、Swiftにはこの究極の形式が備わっています。つまり、すべての引数はデフォルトで名前付き引数であり、一部の引数だけを位置引数にするための特殊な追加構文があります。ただし値の「省略構文」はありませんが。 
  8. これは、戯れにRubyで再実装してみたことがあります。 

CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。