Ruby: "uselessシンタックスシュガー"シリーズ: numbered block parameters(翻訳)
はじめに
本記事は、"useless"シリーズ記事のひとつです。このシリーズでは、最近のRubyに導入された"無用な"(または物議を醸す)構文要素を順次取り上げます。シリーズの目的は、機能を擁護したり批判したりすることではなく、その機能や設計が導入された理由や、新しい構文を使うコードでどのような効用があるかを皆さんと共有することです。本シリーズの予告編もご覧ください。
本シリーズで取り上げる機能第1弾は、導入された当初、ほとんど私への個人攻撃ではなかろうかと思いそうになるほど性に合わなかった機能1、すなわちnumbered parameter(番号指定パラメータ)、またの名をanonymous block parameter(無名ブロックパラメータ)です。
🔗 ナンパラとは何か
Ruby 2.7から、以下のように書く代わりに、
[1, 2, 3].each { |x| puts x }
以下のように書こうと思えば書けるようになりました。
[1, 2, 3].each { puts _1 }
この_1
は、「無名第1パラメータ」を表します2。
サポートされている引数名は_1
〜_9
なので、理論的には以下のような書き方すら可能です。
# 行ごとにカラム1の値を受け取って、このように処理する:
CSV.read('somedata.csv').map { [_1, (_2 + _3) * _4] }
もっとも、この機能の最も熱烈な支持者でも、こんなコーディングスタイルを推奨するとは思えませんが(それでもコンソールでスクリプトを書き捨てて実験してみたくなる誘惑にかられそうです)。
🔗 ナンパラが導入された理由
ブロック引数を何度も繰り返し書かずに1個の短いステートメントにうまいこと収める方法の模索には、長い歴史があります。詳しくは次のセクションで説明しますが、ここでは、ここで行われるいくつかの改善が多くの人に重要だとみなされた「理由」を解明してみましょう。
ブロックは、Rubyの構文における重要な構成要素のひとつです。
array.each { ... }
を主要なループ構文として使うというアイデアは、私にとって最初のaha!体験でした。そしてこれが言語作者にとって他の構文を育てる種となったというのが私の独自理論です。
しかし、明快で簡潔な言語を愛する多くのRubyistたちは、短いブロックに出現する小さな冗長性に常日頃から苛立ちを感じていました。以下のコードは、見ようによっては形式張っていて、儀式張っているとすら感じられます。
numbers.map { |x| x**2 }
私たちは、頭の中では「個別の数値を2乗する」(Rubyの言葉で言えば「数値のリストを、それらの2乗にmap
する」)と考えますが、それをコードで書くときは「渡された数値のセット内で、個別のx
について、そのx
を2乗する」という、まるで小学校の計算練習のようなのんびりした書き方になります。
x
という名前の繰り返しをなくすのは、ちょうど文章で主語を代名詞(それら: their)に置き換えることで繰り返しを避けるのと似ています。一部の人々は、繰り返しの削減によって得られるささやかなようで大きなメリットを重要視しています3。
- 理由: わかりきったものに名前を付けない自由がある
命名はいろんな意味で難しいので、この自由は大事。
「この変数名は1文字にしておかなければならないだろうか?」
「i
だと"integration"のi
じゃなくてカウンタだと思われやしないか?」
「1文字変数は悪手なんだから、横着せずにintegration
とスペルアウトすべきでは?」
「値を変換してから後続のブロックで変数名を変える必要はあるか?」 -
理由: 見た目が煩雑にならずに済む(特に、以下のように短いブロックのチェインが連続するとき)
|x|
がブンブン飛び交っていると、このコードが何をしようとしているのかを考えるときに邪魔になる。
numbers.select { |x| x.odd? }.map { |x| x**2 }.partition { |x| x < 20 }
- 理由: 画面の場所を取らずに短く書ける
わかりきったものに名前を付けないのと同様に、これもモチベーションになる。
大画面のRetinaマルチモニタがずらりと並ぶ時代になっても、これまでどおり「コードの1ページは長さ80〜100文字、行数は30〜40に収めなければならない」というふうに気兼ねなく宣言する自由がある。
コードを把握するうえで重要なのは、(要素を容赦なく詰め込んだりせずに)一度にどれだけの範囲を見渡せるかということ。
users.each {}
という小さなブロックの中に|user|
があることを強調するためだけにブロック幅の4分の1を占めているのはどうかと思う。もっとましな方法があるだろうに。
🔗 ナンパラが導入されるまでの経緯
「引数.メソッド
だけを含むブロック」という最もシンプルなケースについては、Railsで考案されたSymbol#to_proc
を定義するというトリックがRuby 1.8.74で採用されました。
# Ruby 1.8.7より前:
users.map { |u| u.name }
# Ruby 1.8.7以降:
users.map(&:name)
要点を見事に押さえた明快な構文ですね!
なお、この:name
そのものは、これまでと同様に単なるSymbol
ではありますが、シンボルをProc
オブジェクトに変換する#to_proc
メソッドが新たに導入されていました。この#to_proc
メソッドは、proc { |arg| arg.send(self) }
と同等で、&
演算子によって呼び出されます。
何らかの理由で、同じRuby 1.8.7では、記号そのものをブロックの代わりにメソッドに渡すオプションも導入されました。
たとえばEnumerable#reduce
メソッドでは、numbers.reduce(:+)
のように(&
演算子なしで)書けます。これは未来のRuby歴史学者に残された謎です。
ここまでは順調に進みました。しかしこの構文が通用するのは、「ブロックパラメータで呼び出すメソッドが1個しかない」かつ「そのメソッドに引数をまったく渡さない」という最もシンプルなケースに限られていたので、以下のような書き方の改善にはまだ手を付けられませんでした。
things.each { |thing| puts thing }
numbers.map { |n| n**2 }
users.each { |user| user.update(status: 'active') }
当時の#15302や#153015のような野心的(っぽい)提案では、少なくとも以下のような構文が可能でした。
users.each(&:update.with(status: 'active'))
しかしこれらの提案は却下されました。主な理由は、Symbol
を拡張することで、イミュータブルな"内部名"がメソッド名を表す名前に変わってしまうからというものでした(私がSymbol#to_proc
をトリックと呼んだ理由がこれです: Symbol#to_proc
は、将来拡張可能なエンティティ/型/概念を作成するのではなく、演算子の再定義を賢く使って、概念が存在するかのように見せかけているに過ぎません)。
上述のケースを部分的に解決する別の方法は、method
メソッドを使うことで、以下のように書けます。
things.each(&method(:puts))
# または
filenames.map(&File.method(:read)).map(&YAML.method(:parse))
これは、Method
オブジェクトを利用してブロックとして渡しています。
これは「概念上は」DRYです(少なくともパラメータに名前を付けずに済みます!)が、率直に言ってまだまだ冗長です。puts
がメソッドであることは「わかりきったことを書いている」わけですし、そもそもこう書く理由って何なんでしょうか?
かつて「メソッドの取得」を演算子化するというアイデアが提案されたことがありました(#13581)。
filenames.map(&File.:read).map(&YAML.:parse)
が、最終的に#16275で却下されました( 却下されるときの心の痛みについては既に別記事に書いています)。
他にも、関数型プログラミングに適した高度な概念がいろいろ提唱されていました。呼び出し可能オブジェクトからブロックの内容を「コンストラクト」可能にする、カリー化、関数合成(マージされたものすらあります)。しかし私の知る限りでは、どれもあまり有用ではなかったようです。
最終的に合意に至ったのは、パラメータを明示的に指定しないことも可能にするために、ブロックパラメータに何らかのデフォルトの指定だけを導入することでした。
Ruby言語の設計プロセスでよく行われるように、無名な何かをどう表記するかについても多くの議論が重ねられました。
最初の提案は2011年で(#4475)、8年後の2019年(Ruby 2.7の開発中)に@1
という構文が受け入れられてマージされました(@2
や@3
なども使えます)。そしてリリース直前になって「@1
という記法はいかがなものか」と#15723で疑問視されました(このチケットではit
やthis
といった「普通の」キーワードが提案されていました)。そこからMatzとの100件ほどのコメントで議論された後、再検討されて_1
や_2
という表記に変わり、「全パラメータ」を表す_0
も導入される形で終結しました。なお、後者の_0
はやはりリリース前に削除されました(#16178)。
この議論は、コミュニティの多くの人々の間で未だにくすぶっています。たとえばオープン中の#18980は、この記法をit
的なものに変えようという主張の新しいチケットです。
訳注: その後#18980はRuby 3.4での採用が決まりました(ウォッチ20231222)。
🔗 ナンパラのむずかゆい点
新しい構文に寄せられがちな意見のひとつに「見た目が美しくない」というものがあります(他にも「間違ってる」「直感的でない」「まるでパラメータが無視されているみたいだ」といったものもあります)。単に「この見た目がキライだ」という立場で議論するのは時間の無駄ですが、私はかつて#18980のコメントで、構文の選択やそれを取り巻く設計空間について客観的な分析を試みたことがあります。当初はあまり気乗りしなかったものの、分析してみると、これを上回る構文は私には思いつかないことに気づきました。以下に再録します。
私の視点から言うと、記法の設計空間は以下のように記述できます。
- 名前に基づくスコープの規則に何らかの形で合致すべき(例:
_1
はローカル変数として有効な名前であり、ローカルな存在であることを示唆している)。これに則れば、「何らかの特殊インスタンス変数」を思わせる@1
は除外されることになります。特殊であることが伝わる外観にすべき。たとえ
_1
の意味を知らなくても(Rubyを学習中だったり、Rubyの知識をアップデートしていないなど)、「これは普通の命名と違う、何か特殊なことをしている」ということは即座に感じ取れるはずです。これに則れば、it
は除外されることになります。
「この名前が既に他で使われているかもしれない」という懸念があるとしても(実際RSpecなどでそうなっている可能性があります: "iterator"や"item"、 "index"や"time"をこの略記法で表しているコードベースを見たことがあります)、「これはローカルな名前だが特殊な名前でもある」という印象はあまり強くありません。私の理解では、現在の私たちはアラン・パリスの警句集のようなもの(「この文字は、この文脈では何か新しいものを意味する」など)を導入することにかなり消極的です。(略)
(これについては複雑な心境)おそらく似たような名前のシーケンスも容認すべき(
_1
/_2
など)率直に言って、少なくとも上の1〜3を満たす命名スキームを上回るものは思いつきません。
それよりもずっと気になるのが、そもそも「なぜその数字を選んだのか」を読み解くときです。
_2
や_3
が何らかの形で有用かどうかは議論の余地があるでしょう。結局のところ、ナンパラのアイデアは「完璧にわかりきっていることをわざわざ書かない」ことに尽きるので、コードを読む人がナンパラを目にしたら、いったん立ち止まってナンパラの個数をとにかく数えるでしょう。この時点では、「普通に名前を付ける」方が合理的に思えます。
さらに困るのは、_2
が存在すると、以下のように_1
の意味が変わってしまう場合があることです。
{name: 'Victor', country: 'Ukraine'}.each { puts "_1=#{_1.inspect}" }
# 出力:
# _1=[:name, "Victor"]
# _1=[:country, "Ukraine"]
{name: 'Victor', country: 'Ukraine'}.each {
_2 # 書いてみただけ(私なら書きません!)
puts "_1=#{_1.inspect}" # この出力は、上の文と完全に同じになるでしょうか?
}
# 出力:
# _1=:name
# _1=:country
こうなる理由は、引数の扱われ方にあります。上のコードは、Ruby 2.7より前(ナンパラ導入前で、同じ名前をローカル変数としても使えた)では以下のコードと同等です。
# _1だけを書くと「すべての引数」として扱われる
{name: 'Victor', country: 'Ukraine'}.each { |_1| puts "_1=#{_1.inspect}" } # _1は[key, value]ペアを含む
# _2も書くと、_1は単なる「第1変数」として扱われる
{name: 'Victor', country: 'Ukraine'}.each { |_1, _2| puts "_1=#{_1.inspect}" } # _1はキーだけを含む
これらは、いずれもブロックパラメータの取り出し方法(deconstruction)に関連しています。しかし名前が明示的に宣言されていなければ、Rubyはナンパラに基づいてブロックパラメータの分解方法を推測しますが、ナンパラはブロック本体の奥深くに潜んでいる可能性もあるのです。
その結果は、控えめに言っても混乱しています(さっきも書いたように、かつて_0
を"すべてのパラメータ(分解なし)"、_1
を"第1パラメータ"(以下続く)とするアイデアが#15723で提案されましたが、何らかの理由で却下されました(#16178)。
いろいろ書きましたが、私が見かけたコードではナンパラが効果的に使われていて、_1
だけに留めていました。これならたぶんOKでしょう。
🔗 ナンパラ導入後どうなったか
_1
が美しかろうとそうでなかろうと、重要なのは人間は面倒くさがりであるという事実です(それがよいとされる状況もしばしばあります)。最終的に、そういう人たちのほとんどは、ムーブを少しでも減らせる方法があれば満足してくれるのです。キー入力というムーブも、ブロックパラメータに付ける名前を頑張って考え、次の語でその名前をまた書くという心のムーブも節約できるわけです。
こういう「代名詞的なパラメータ」が身体にしみついてくると、ある種の書き方が後押しされて盛んになる一方で、別の書き方が妨げられるようです。本シリーズ記事では、構文の小さな変更がコードのあり方にどんな影響をもたらすのかを追求しています。
このナンパラという新しいショートカットによって、新しいコーディングスタイルが花開きました。それは、小さなブロックを次々にチェインすることで「入力から出力までの変換処理を宣言的に記述する」形で計算を表現するというスタイルです。
このスタイルは、主流の言語でenumerable.transformation { ... }
をメインのループとして最初に採用したRubyにネイティブで備わっているものです。私は、これが(物事を扱うときの思考のフレームワークとしての)map
やreduce
やfilter
の正規化に大きな役割を果たしたと信じています。
言い換えれば、以下のようなコードを
teams = []
users.each do |user|
next if user.admin?
user_teams = find_teams(user)
user_teams.each do |team|
teams << team unless teams.include?(team)
end
end
Ruby世界では以下のように書いているのをよく見かけます。
users
.reject(&:admin?)
.flat_map { |user| find_teams(user) }
.uniq
この書き方は、(少なくともある観点では)「admin以外の全ユーザーについて、ユーザーが属するチームの一意のセットを取得する」6という意図を明確に読み取れる形で示しています。
上の2つのサンプルコードについて、前者に_1
を導入すると、考えなければならないことが増えます(例: _1
をどこに置くべきか?見やすさ優先ならブロックの内側に置くべきだが、入力量を減らすならブロックの外に置くべき?)。
後者のサンプルコードでは、_1
を使うことでむしろ読みやすさが増します。「この_1
はユーザーを表している」といったわかりきったことを思い出さずに済み、しかも滑らかに読めるのです。
users.reject(&:admin?).flat_map { find_teams(_1) }.uniq
そして、自分のコードに_1
を追加して何かクールなトリックを仕込もうとしたときに、自分のコードに不備があることが判明する場合もあります。運がよければ、自分のコードをアトミックなチャンクに分割して少々考え直す機会にもなるでしょう。
もちろん、そうなるためにはコードの作者かレビュアーに、ある程度の自省が求められます。_1
の「入力量を減らす」側面を乱用して、array_of_pairs.map { _1[0] + _1[1] }
のような奇っ怪なコードを生み出す人を見たことがあります。
逆にActive Supportが使える環境なら、何も考えずに_1.first + _1.second
といううまい書き方を使えます。こんな使い道もあるのです!
私個人の密かな楽しみのひとつに、.then
演算子(Ruby 2.5-2.6で導入された)を使って単一の値にいろんなものをチェインするというのがあります。チェインするものはコレクションに限りません。
then
を使うと、enumerableを使っていないものであっても「入力から出力へのチェイン」を同じように直感的に適用できるようになります。
以下のコード例をご覧ください。
value = Time.parse(YAML.parse(File.read(ENV.fetch('CONFIG_PATH'))).dig('metadata', 'created_at'))
丸かっこ()
がネストしまくっていて、コードを読むときに末尾からさかのぼって読んでいる気分になります。「このparse
はどのタイミングで行われるのかな?」「あ、YAMLのあたりか」「でもこれはどこから取得したのか?」、やれやれです。
以下のコード例は上よりも「整頓」されてはいるのですが、今度は使い捨ての変数がたくさん導入されています。アルゴリズムが大規模になってくると、どれが本物の使い捨て変数で、どれが後で使われるものなのかが、コードを読んでいてもよくわからなくなってきます。さらに、ステートメントの一部がもっと込み入ってくると、何を計算してどんな結果を得たのかを見失いがちです。
config_path = ENV.fetch('CONFIG_PATH')
config = File.read(config_path)
data = YAML.parse(config)
time_str = data.dig('metadata', 'created_at')
value = Time.parse(time_str)
そういうときは.then
を使えば、以下のように「入力から出力」へのフローを素直にたどるコードを書けるようになります。
ENV.fetch('CONFIG_PATH')
.then { |config_path| File.read(config_path) }
.then { |config| YAML.parse(config) }
.dig('metadata', 'created_at')
.then { |time_str| Time.parse(time_str) }
そして、ここぞとばかりに以下のように_1
を使えば、「直前のステートメントで行った結果」を透明に参照できるようになり、使い捨ての余分なブロック変数たちがまったく不要になります。
ENV.fetch('CONFIG_PATH')
.then { File.read(_1) }
.then { YAML.parse(_1) }
.dig('metadata', 'created_at')
.then { Time.parse(_1) }
ここまで落とし込んだら以下のように行数を減らして、それまで単語が行単位でぶつ切りになっていたのを意味のある文にまとめるのが合理的です。
ENV.fetch('CONFIG_PATH').then { File.read(_1) }.then { YAML.parse(_1) } # コンフィグを読んでパースする
.dig('metadata', 'created_at').then { Time.parse(_1) } # ここから何かデータを切り出す
🔗 他の言語ではどうやっているか
暗黙のパラメータを採用することを決定した言語は他にもいくつかありますが、暗黙のパラメータを1個のみに制限しているのが普通です。
たとえば、Kotlinでは「末尾lambda」7で単一の暗黙パラメータit
が使えます。
// Kotlin
// このリテラルの型は'(it: Int) -> Boolean'
ints.filter { it > 0 }
Rubyの遠い親戚であるGroovyでも同じことをしています。
// Groovy
def greeting = { "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
# 以下は上と同じ
def greeting = { it -> "Hello, $it!" }
assert greeting('Patrick') == 'Hello, Patrick!'
Scalaでは(番号なしの)アンダースコア_
を採用しており、コードをnumbers.map(_ ** 2)
のように短く書けます。Scalaのアプローチは異色で(賢くやっているとも言えます: 質問者が誰かにもよりますが)、_
はパラメータリスト内の次のエントリを参照するので、たとえば以下のように書けます。
// Scala
// ↓これは第1パラメータを参照する
List(1, 2, 3).zip(List(4, 5, 6)).map(_ * _) //=> List(4, 10, 18)
// ↑これは第2パラメータを参照する
さらに少し別のアプローチとして、Rubyの姪っ子であるCrystal(私はCrystalは"型付きRuby"として生まれたと信じていますが、その後たくましい野生児に育って独り立ちしました)は、Rubyの&:シンボル
記法に触発されて独自の構文を確立しました。少なくともこれにより、受け取るパラメータを拡張可能になっています。
# Crystal
numbers.map &.**(2)
# 以下は上と同じ
numbers.map { |n| n ** 2 }
Rubyの別の姪っ子であるElixirは、私の知る限りブロックパラメータで複数の数字を実際に使っている唯一の言語であり、以下のようなさまざまな書き方が可能です。
# Elixir
Enum.reduce(list, %{}, &Map.put(&2, &1, &1 * 15))
(ここで&Map
の&
は、ステートメントの残りの部分を対象とするRubyのProc
変換演算子に似ていますが、&1
や&2
はそれぞれproc
の第1パラメータと第2パラメータを参照しています)
暗黙プログラミング(tacit programming)の概念についての検討にも手をつけるかもしれません。これは、ある観点から見れば「引数を何度も書かない」話でもありますが、これについて書き始めると長くなりそうです(既にうんざりするほど長くなっていますが)。しかし、私は高度な関数型プログラミングをRubyで応用する(それも、単にclass Monad
的なものを定義するよりも自然な方法で)ことについて何か書くつもりです。
🔗 この先どうなるか
この「空想」セクションは、本シリーズに毎回入れたいと思っています。「この方法が今後他にどんなふうに進化するだろうか」について想像を巡らせるという企画です。Ruby言語に提案するのに必要なアイデアというわけではありませんが、要するに少し違う形で理解するための練習です。
「番号付き引数を伴うブロック」というアイデアが今後どのように進化するかについて、ささやかながら思うところがあります。
暗黙の引数を用いる小さなlambdaはDSLで重宝しますが、それでも「まだ記号が多すぎる」という印象があります。
serialize :salary, format: -> { csv_currency(_1) }
そこでついこんなことを考えてみたくなります。以下を書けるようにするには、言語や条件のどこを変えればよいのでしょうか?
serialize :salary, format: -> csv_currency(_1)
詳しく分析したわけではありませんが、"直感的には"「次に来るコードは、bodyにステートメントが1個しかないlambdaだ」とインタプリタが十分認識できる構文のように思えます。
たった2文字の{}
を書くのも面倒がる人の気まぐれな思いつきのように見えるかもしれませんが、この変更はDSLの設計に影響するかもしれません: 読みやすさを失わずに「超コンパクトなlambda」を利用可能にする方法に注目する形で。結局のところ、明確にするための冗長な記法({}
)が宣言の途中に置かれることを前提としない(lambdaの場合は{}
の省略が容認され、場合によっては推奨される)という形で全体の設計が改善されるだけですが。
🔗 まとめ
毎度のことで恐縮ですが、本シリーズの記事はどれも非常に短くなるだろうと予想していました。しかし最もシンプルな「シンタックスシュガー」の話であっても、書いているうちに意見や視点、知見、洞察、それらのアナロジーといったものが最終的にてんこ盛りになってしまうのです。
結論として、本シリーズ記事全体で繰り返し述べられることになる注目ポイントのいくつかを、ここでも述べておきたいと思います(私が期待を盛り込みすぎないよう自制していることがおわかりでしょうか?共感を得られないのであれば、読者のお時間を無駄にしたくありません)。
そういうわけで、私のキャリアで培われたコードと構文に関する信念の一部を記しておきます。
- 言語が提供する構文要素が小さくても、それを使う開発者のコードに何らかの影響を与える可能性はあるのです(その目的が「入力量を減らしたいから」という実用一点張りであったとしても)。
コードの全体的な見た目が、ある種の書き方を(ごくわずかであれ)推し進めることは頻繁に起きます。これは場合によっては新しい構文を使うコードでなくてもよく、潜在的に新しい構文を使える可能性のある呼び出しコードによって発生することもあり、時にはクラス設計全体が影響を受けることすらあります。そして、言語の新機能では、それによってどんな書き方が推し進められるかという感覚が重要なのです。 - わかりきったことを省略し、同じ名前を何度も書かなくても参照できる機会(義務ではありません!)がユーザーに提供されれば、自然な流れが生み出されて「コードを読む人の体験」が向上する可能性があります。
- 画面上のスペース、コードのレイアウト、見た目の軽やかさ、1画面に収まるコード量、これらは大規模なシステム設計において微妙ではあるが重要な影響を与えます。
- つきつめれば、「コードの全体像がどう見えるか」がすべてなのです。
次回のエントリでは(来週に出せればと願ってはいますが)、パターンマッチングを取り上げます。いいねボタンと購読をぜひお願いします!若い方々からのコメントもお気軽にどうぞ。
お読みいただきありがとうございます。ウクライナへの軍事および人道支援のための寄付およびロビー活動による支援をお願いいたします。このリンクから、総合的な情報源および寄付を受け付けている国や民間基金への多数のリンクを参照いただけます。
すべてに参加するお時間が取れない場合は、Come Back Aliveへの寄付が常に良い選択となります。
本記事(あるいは過去の私の仕事)が有用だと思えたら、Buy Me A Coffeeサイトにある私のアカウントまでお心づけをお願いします。戦争が終わるまでの間、ここへのお支払いは(可能な場合)私や私の戦友たちが必要とする装備または上述のいずれかの基金に100%充てられます。
関連記事
- 訳注: 本記事で引用されている#18980のzverok氏のコメントでもわかりますが、その後は肯定的な意見に転じています、念のため。 ↩
- 原注: この"first"にはある種のニュアンスも込めていますが、それについては後ほど説明します。 ↩
- 原注: そんなことに考えを巡らせる価値はないと思う人もいますが、それについては何も問題ありません。ただし、ささやかな改善を得ようとするあまり他人を説得してねじ伏せようとしたり、「邪魔くさい」「自転車置き場の議論か」「プロっぽくない」などと非難したりするようになれば話は別です。 ↩
-
原注: Ruby 1.8.7当時のバージョンではロジックが異なっていました。Ruby 1.9は
hashkey:
構文など多くの機能が取り入れられた新しいメジャーバージョンでしたが、Ruby 1.8.7は「Ruby 1.8ファミリーの最後のバージョンにいくつかの新機能が導入された」ものでした。ちょうどRuby 2.7とRuby 3.0の関係にも似ています。 ↩ - 訳注: #15301はzverokさんによる提案です。 ↩
- 原注: ここで「でもこれじゃ中間の一時配列がたくさんできるので非効率だよ」というよくある会話が既に持ち上がっているとしましょう。これについての議論も反論も、既に知れ渡っています。ほとんどの場合、私にとって最初の目標は意図を明確にすることであり、必要であることが証明されれば、最終的にパフォーマンス向上のために明快さを犠牲にするのはありです(明快さとパフォーマンスを同時に得られるのでない限り、こういうトレードオフは珍しくありません)。 ↩
-
原注: 興味深いことに、比較的主流な言語の中でRubyは
method { code }
という手法を最初に取り入れた言語であるにもかかわらず、lambdaや高階関数が極めて難解だったために、Rubyのチュートリアルでは「末尾lambda」という用語を避けて単に「コードのブロック」と解説する傾向がありました。その後だいぶ経ってから、同じ構文要素が新しい言語でも有用であることが判明すると、素直に「末尾lambda」と呼べるようになりました(「誰もが知ってるあのlambda」に、メソッドの末尾の引数がlambdaの場合にシンタックスシュガーを加えたものという具合です)。主流における概念理解のレベルは、時とともに確実に変化しているのです。 ↩
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
本記事では、簡単のため必要に応じてnumbered parameterまたはnumbered block parametersを「ナンパラ」と表記します。正式な用語ではありませんのでご注意ください。