Ruby: "uselessシンタックスシュガー"シリーズ「endレス(1行)メソッド」(翻訳)
本記事は、最近のRubyで出現した"無用な"(さもなければ物議を醸す)構文要素を扱うシリーズ記事の一環です。本シリーズの目的は、そうした機能を擁護することでも批判することでもなく、その機能が導入された理由、設計、そして新構文を使うコードに与える影響を分析するための一種の「思考のフレームワーク」を皆さんと共有することです。本シリーズのあらまし記事もご覧ください。
今回は、コミュニティでも賛否が大きく分かれた(ともするとナンパラのときよりも著しかった)機能である1行メソッド定義(one-line method definitions)を取り上げます。
🔗 1行メソッド定義とは何か
Rubyでは、以下のようにメソッドを定義するのが普通です。
def my_method(args)
body
end
Ruby 3.0から、この別構文として、以下のように1行だけでメソッドを定義できるようになりました。
def my_method(args) = body
🔗 1行メソッド定義が導入された理由
Rubyは主流言語としては珍しく、コードブロックを囲むのにC言語のような{}
記号を基本的に使いません。また、コードブロックの構造を示すときに、また、HaskellやPythonのような「意味のあるホワイトスペース」(significant whitespaces: ホワイトスペースによるインデントでコードブロックを表す)を使いません。
Rubyでは、(ほぼ)すべての構造で終了をend
で表します(PascalやLuaと同様)。
if condition
# ...本文...
end
items.each do |item|
# ...本文...
end
class C
# ...
def m
# ...
end
end
end
がコード入力で問題になることはほとんどなく、しかも現代のIDEなら代わりにend
を補ってくれますが、コードブロックの本体や見出しが非常に小さい場合は、この構文がかさばる感じがすることもないわけではありません(この「かさばる感じがする」という言い方は非常に不正確ですが、具体的な理由は後述します)。
ただし、Rubyのコード構成方法には多くの場合コンパクトなバージョンもあります。単に機械的にコンパクトにしただけではなく、細かな点を少し違う形で表現します。
return [] if denied?
items.map { |item| process(item) }
# 例外の新しい型を指定するためだけに本体が空のクラスを生成する
MyError = Class.new(NetworkError)
しかしメソッド定義にはコンパクトなバージョンがありませんでした。メソッド定義の書き方は1種類だけだったのです。
セミコロン;
を使って無理やりメソッドを1行で定義することも一応可能です。
def my_method(args); body; end
しかし;
を使うのは悪趣味であるという見方がRubyコミュニティで醸成されていきました。すなわち、さまざまな論理上のフレーズを1行にギュウギュウに押し込めている兆候であるとされたのです1。
原文追記
複数の読者からご指摘をいただきました。実はセミコロン;
を書かなくても以下の文は有効です。
def my_method(args) body end
返す言葉もありません...私はたまにRubyの使い方をコロッと忘れていますね🙂
しかしこれには理由があります(私はそのことを忘れていましたが)。Rubyの構造は、多くの場合このように改行を取り除いてフラットな形にすることが可能であるという意味ではイエスなのですが、次の理由で誰もこんなふうにコードを書かないという意味ではノーなのです。この書き方は、どこまでが見出しでどこから本文なのかが不明瞭になりますし、end
がぶら下がっていると構造が台無しになります。
そういうわけで、この構文は理論的には可能でも、現実には使われません。おそらくさっきの説明は、コミュニティの;
に関する見解を中心に据えるべきだったでしょう。
関数のイテレーションが主流になると、多くの言語で1個の式だけを持つ関数のショートカット構文を考案することを迫られました。
JavaScriptでは、 function(arg) { return val }
をarg => val
と書けます。
しかしRubyにはそれ用のコードブロックが既にあるので、メソッド構文を進化させる必要はありませんでした2。
「でも、構文の小さな齟齬を問題にする理由ってあるの?」と思う人もいるかもしれません(気分次第ではコードゴルフを例に持ち出すでしょう: コードゴルフは「このコードを1行に書き直せるか?」という問いでよく引き合いに出されます)。
私は本シリーズを通じて、「コードを気持ちよく読めること」「コードを一貫したストーリーとして感じられるようにすること」について多くの言葉を費やしてきました。この文脈において重要なのは「1ページにどれだけ多くのことを盛り込めるか」です。これは、分厚い書籍のように後続のパラグラフに何もかも押し込めるのが得策である、ということを言いたいのではありません。コードは書籍と違い、段落に分けて読むことは想定されていないのが普通です。
一方、レイアウトが「1行あたり2単語」「1ページあたり20語」の童謡の絵本のようにスカスカだと、「これは要するに何を言おうとしているのか」を知るのに何十ページもスクロールしなければならなくなる可能性もあります。
私が想像する優れたコードレイアウトは、紙のエンタメ雑誌のレイアウトに似ているところがあります。「記事が十分短い」「詰め込みすぎず、余白を十分取る」「引用・リスト・図解でさまざまな部分に注目を集める」「細かい話は脚注に追い出す」といった具合です(もちろん私たちのレイアウトツールはそれとは違いますが、達成される効果は多くの点で同じです)。
しかしRuby 3.0より前は、メソッド定義構文の大半が童謡の絵本のようなスタイルになっていました。
# テキスト処理アルゴリズムである単語をカプセル化する
# 小さなValue Object
class Word
attr_reader :text
def initialize(text)
@text = text
end
def inspect
"#<#{self.class} #{text}>"
end
def ==(other)
other.is_a?(Word) && text == other.text
end
def <=>(other)
text <=> other.text if other.is_a?(Word)
end
def punctuation?
text.match?(/^[[:punct:]]+$/)
end
def capitalized?
# ...などと続く(まだ始まったばかり!)
この問題は、上に示したようなたくさんの小さなメソッドをすべて含んでいるValue Object3であろうと、巨大なオブジェクト内に数個ある小さなメソッド(#inspect
や単純な述語メソッドや#to_h
などの類)であろうと変わりません。1ページ、あるいは数ページに渡るコンテキストが、ともすると「もしもし、私リカちゃん」レベルの薄い内容でたちまち埋められてしまう可能性があるのです。
このような状況から生じる暗黙の結果として、便利メソッドや便利オブジェクトといった「なくてもいい(けどあると便利な!)もの」を追加することをついつい避けるようになります。頭の中ではさっと書けるとわかっていても、実際に書いてみると2ページのコードにふくれ上がるからという理由で。
では...この種の小さなヘルパーメソッドをもっと簡潔に書くことは可能でしょうか?
🔗 1行メソッド定義が導入された経緯
このソリューションは、エイプリルフールのジョークとして誕生しました。
年に1度、不条理な機能をこの日に提案するという伝統が長年続いています(提案の一部をタグで選択しましたが、前はもっと多かったはずです)。この提案に続けて、きわめて大真面目な正当化が補足されることもよくあり、ジョーク専門の人々がその変更が本当に可能であることを証明するパッチをRubyに投げることもしょっちゅうでした。Rubyトラッカーでは、今日が何の日かを忘れていた人たちや不条理に気づかなかった人たち、あるいはそのジョークを支持する人たちが入れ代わり立ち代わり無邪気な議論を繰り広げ、構文の詳細を議論したり、同じぐらい不条理な反論を提案したりしていました。
しかし、Yusuke Endohによる2020年の投稿は、明らかに不真面目な口調で述べられていました。
Rubyの構文はどこもかしこも
end
だらけである。自分は大量のend
がRubyを終わらせる(=end)という妄想に取り憑かれており、Rubyが永遠(=endless)であることを願うものである。そこで、ひとつ新しいメソッド定義構文を提案したい。
def: value(args) = expression
続いて、ちょっと珍しいことが起きました。Rubyの優しい終身の独裁者であるMatzがレスを付けたのです。
このアイデアには本気で全面的に同意する(中略)ただしこの構文は好きではない。
そして本当になったのです4。
以下のような、より自然な構文は当初不可能だと思われていました。
def value(args) = expression
しかし凄腕の@nobu(Nobuyoshi Nakada)が何と一晩で実装したのです(パーサーを慎重に微調整する必要がまだ残されていたのは明らかでした: 新構文によって生じた制約のいくつかは次のバージョンで解決したものの、まだ厄介な点がいくつか残っており、これについては後述します)。
お気楽なジョークから言語で重要な変更が生まれることもあるんですね(しかも「endレスメソッド」というおふざけの名前も付けられました: Rubyトラッカーでは今でも半ば公式のあだ名になっていますが、Rubyのドキュメントでは"shorthand method syntax"という名前で参照されています)。
🔗 1行メソッド定義のむずかゆい点
1行メソッド定義で意図していなかった混乱を呼び起こす問題のひとつは、パーサーによる解析の明示的でないコード優先順位に関連しています。
class Test
def initialize(active)
@active = active
end
def invoke = puts "works" if @active
end
# 利用を試みる
Test.new(true).invoke
このコードの最後の行が実行されると、"works"
を出力するのではなく、 "undefined method 'invoke'"という場違いなメッセージを表示して失敗します。原因は、今述べたコード解析順序の混乱です。
# 期待される解釈:
def invoke = (puts "works" if @active)
# 実際の解釈:
(def invoke = puts "works") if @active
これが意図していない振る舞いであることは間違いありませんが、修正が恐ろしく難しいため、現在も議論が続いています(#19392)。
この手の構文の癖は、いつものように()
で修正できます!
# これで期待通りに動く
def invoke = (puts "works" if @active)
以下は、コード解析問題の別の例です。
# これは有効
def initialize(one_value) = @one_value = one_value
# これはシンタックスエラー
def initialize(two, values) = @two, @values = two, values
# 原因: 以下のように解析されるため
(def initialize(two, values) = @two), @values = two, values
# 修正: いつものように()で囲む
def initialize(two, values) = (@two, @values = two, values)
私は、Rubyが新しいPrismパーサーに乗り換えるときの地殻変動プロセスが、この問題解決に役立つかもしれないという淡い希望を抱いています(Prismの素晴らしさについてはこちらの記事をお読みください)。
🔗 1行メソッド定義導入後どうなったか
新しい構文への批判でよく言われることですが、「1ステートメントメソッドが"特殊なもの"になってしまう」という指摘があります。つまり、ステートメントを1個から2個に増やそうとすると、以下のようにコードの形状を完全に書き換えなければならなくなるということです。
# 最初はこうだった...
def owner_name = @user.name
# ..しかしもう少し複雑になったらどうする?
# 追加行を単純に既存の行の上に書くわけにはいかない
# 既存の行を下に移動、=の削除、などが必要
def owner_name
default = I18n.t('that_thing.default_owner_name')
@user&.name || default
end
ただし、Rubyの構文では、このような特徴は珍しくありません。シンプルなobjects.map { do_something }
であっても、ブロック内に別のステートメントを書く必要が生じたら、複数行に分割しなければならなくなります(さらに、多くのコーディングスタイルでは、複数行ブロックを囲むブロック構文の変更も必要です)。これは一般的に言って不便です。
ただし、これを不便なものとみなすのではなく、この構文がヒントを出してくれているとみなす手もあるのです。
せっかく小さくエレガントに書いた1行メソッドに、突然別の行を追加しなければならなくなったら、人は一瞬そこで立ち止まるでしょう(ほんの数マイクロ秒ではありますが、考えながら高速で入力していると、こういうものが邪魔くさい物体のように感じられるものです)。
さて、ここで2つのシナリオのうち1つを検討してみましょう。「1行メソッドを何とか1行のままにする方法はあるだろうか?」
たとえば以下のように1行メソッドの外に逃がす手が使えるでしょう。
DEFAULT_OWNER = I18n.t('that_thing.default_owner_name')
def owner_name = @user&.name || DEFAULT_OWNER
この方法が使えるかどうかは状況やコードベース次第ですが、うまくいけば関心の分離をよりクリーンな形で表現できるようになります。
メソッドのニーズを表現するのにどうしても2行以上が必要な場合もありえます。そういうときは、以下の要領でメソッドを数回「書き直す」のも有用です。
まず、そのメソッドの「内部モデル」を「1個のフレーズ」から「複数のフレーズ」に更新します(場合によってはメソッド名の修正も必要かもしれません)。
ここでポイントとなるのは「1個のフレーズという感覚」です。そしてこれが、新構文が追加する重要な要素である1フレーズメソッドなのです(これは後置のif
を追加するような感じで書きます)。
「古典的な」メソッドでは、以下のように最小限のメソッドでも、
def size
@objects.count
end
それを心の中で読み上げると「size
というメソッドがある」「@objects.count
を計算する」「終了」という3つのフレーズになります。
これを以下の1行メソッドにすると、
def size = @objects.count
「size
メソッドとは@objects.count
である」という1フレーズとして読めるようになります。
この結果が何文字になるかはさほど重要ではありません(逆説的ですが、このコードが何行になるかもさほど重要ではありません!)。
このショートハンド構文はよく「1行メソッド構文」と呼ばれますが、以下のevent
メソッドは完全に有効なコードです。
Event = Data.define(:kind, :context, :timestamp)
def event(kind) = Event.new(
kind: kind.to_sym,
context: self,
timestamp: Time.now
)
こう書いても、引き続き「event
はEvent
インスタンスを生成するメソッドである」という紛れもない1個のフレーズとして読めます。
一方、そうした「1フレーズ」メソッドをうまく活用したコードでは、あえて1個の式メソッドを1行メソッドに変えずに「マルチフレーズ」のままにしておいて、その部分の重要性を強調する(「ここは立ち止まってちゃんと読め」という一般的な感覚を促す)ことに使えるでしょう。
def send_event(kind, payload)
EventQueue.instance.push(Event.new(kind:, **payload))
end
すなわち、ここで説明しているのは思考のツール、コミュニケーションのツールについてです(1個の式メソッドを機械的にendレスメソッドに書き換えることではありません)。
ここで再び、パターンマッチング3「この先どうなるか」で取り上げた、精妙かつ表現力豊かな例を繰り返したいと思います。以下のコードでは複数の新構文を連携させています。つまり「複数行のパターンマッチングメソッド」に1行メソッド構文を適用する形で、構造に最後の仕上げを加えています。
def slice(*) = case [*]
in [Integer => index]
p(index:)
in [Range => range]
p(range:)
in [Integer => from, Integer => to]
p(from:, to:)
end
slice(1) # 出力: {:index=>1}
slice(1..3) # 出力: {:range=>1..3}
slice(1, 3) # 出力: {:from=>1, :to=>3}
複数行のメソッドをendレスメソッドに書き換える「論理的1フレーズ」メソッドがすぐに広まるとは期待していませんが、これを表現力豊かなツールとして使うことで、この方法に対するコミュニティの見解が時とともに変わっていくかもしれません。
🔗 ウクライナ通信
ほんの少しお時間をください。今の私たちが暮らしている状況を小さな思い出として記事の途中にはさむことにしています。私は現在戦争中の国で暮らしています。先週起きたささやかな出来事を無作為に選んでお伝えするものです。
あるニュース: 前線で発生したあらゆる出来事に加えて、数日前ロシアがスームィ州セレディナ・ブダを攻撃し、大人2名と8歳の少女が死亡しました。状況をより詳しく知るにはGoogleマップを参照することをおすすめします(比較用の戦闘地域のマップもどうぞ)。ロシアはウクライナ北部を絶え間なく砲撃して恐怖を与えることで「ロシア国土への不当な攻撃」を誘発することを狙っています。
ある小さな背景情報: 先週土曜日はホロドモール犠牲者追悼の日でした。このツイートのスレッドは、ロシア軍が我が国に試みた「過去の」大量虐殺の1つについて重要な背景情報を提供する、ウクライナ人による解説です。
ある募金活動: フィンランドを拠点とするウクライナ人ゲームデザイナーSergey Mohovおよび彼の慈善団体であるPolubotok Treasuryは、ウクライナ軍支援のための活発な募金活動を行っています。ぜひ寄付をご検討ください(スムーズに寄付できるオプションもあります)。
引き続き記事をどうぞ。
🔗 他の言語ではどうやっているか
言うまでもなく、多くの関数型言語(あるいはポストモダン世界の「関数型ファースト」言語)では、name = expression
という構文がメソッド定義の主要な方法になっています(なお、そうした言語で複数の式を持つ関数がどんな外見になるかは別の話です)。
Haskellでは以下のように書きます。
add x y = x+y
ただし本記事の文脈では、Rubyのパラダイムに近い言語ではこの問題をどう解決しているかを見ていく方に関心があります。
冒頭で述べたように、現在の主流言語のほとんどはC言語風の{}
スタイルでコードブロックを囲んでいるので、この問題が差し迫ってくることはありません。必要に応じてheader { body }
と1行で書けば済みます。ブロックを囲む記号に邪魔されることもなければ、場所を取りすぎることもありません。しかも、コードを文章のようにフレーズとして読むときは、心の中で{}
を簡単に読み飛ばせます。
それでも、一部の言語(C#やKotlinなど)では1フレーズメソッドの特殊な役割が知られています。
// 通常の関数
fun double(x: Int): Int {
return x + x
}
// 単一式関数
fun double(x: Int): Int = x + x
(C#もKotlinも、単一ステートメントでは{}
とreturn
句を両方とも削除し、暗黙で値を返すことが、このショートハンドで大事なようです)
もう1つのグループは、「意味のあるホワイトスペース」を使う言語です。多くの場合、Pythonのように見出しと同じ行に関数本体を書けるようになっています。
# 通常の関数:
def is_even(x):
return x % 2 == 0
# ...以下のようにも書ける
def is_even(x): return x % 2 == 0
この記法は、短く書けるうえに「単なる1個のフレーズ」を感じさせる効果もあります(ただしPythonではreturn
を省略できない点が冗長に感じるかもしれません)。
ScalaやNimでは、見出しと本文の間に=
記号を使うことすら可能です。この1行メソッド定義は、Rubyの1行メソッド定義とほぼまったく同じ形に見えます。
Juliaは、Rubyと同様にend
キーワードを持つ数少ない言語であり、Rubyと同様に=
を用いるショートハンドです(ただしRubyと異なり、受け渡し可能な値を作成できます: 詳しくは以下のコードを参照)。
# 通常:
function f(x,y)
x + y
end
# ショートハンド:
f(x, y) = x + y
逆の例として、構文で{}
を使い、フォーマッタがデフォルトで備わっている一部の新しい言語(GoとRust)では、({}
を使わない)1個の式による特殊形式を避けるのみならず、フォーマッタで「{}
内に式を1個持つ本文を1行で書く短縮形式」をデフォルトで禁止する傾向があります。後者のRustでは、以下のようにはっきりと述べています(強調は筆者)。
関数全体(宣言と本文)を1行で書くことを好む人々がいる。それは間違っているが、オプションとしてサポートする必要がある。
🔗 この先どうなるか
この新構文に対して、少なからぬRubyistたちが不満を表明しています。特に=
の利用は「値の代入と似すぎている」ので、値とメソッドの違いが曖昧になるとしています。
開発者が「値」と「メソッド」を明確に区別できるのは、厳しい鍛錬の賜物であるという点にも触れておきたいと思います。しかし現代では、変数に関数を代入して、その変数をデータ型として扱うことに慣れるために、関数型プログラミングのコースをすべて受講する必要はありません。
つきつめれば、どこにでもあるJavaScriptでも同じことができるのです。
// これは定義です!
function foo() { return 3 }
// これは変数です!
foo = function() { return 3 }
ただしそうした言語のユーザーでも、「関数は値にもなれる」という概念を明確にするには優秀なメンターと書籍が必要になる場合があることについては同意します。
私が出会ったあるRubyistは経験豊富で生産的でしたが、それでもコードブロックを&block
としてメソッドに渡す形の受け渡しに「違和感」を感じていました。関数型的な値が「必要」になったときは、単に-> {}
を渡していたのです。
つまり関数を=
で定義する方法はとりたてて難解なものではないのです。
しかし他方では、何かをミスリードしているように感じられるかもしれません。
少し前に別記事で書いたように、Rubyにはメソッドを値として取り扱う自然な方法が存在しません。最も短い書き方は、method(:method_name)
を呼び出すことです。これを使えばMethod
オブジェクトを必要に応じてその場で作成できますが、その代わり読みやすさもパフォーマンスも低下してしまいます。
したがって、たとえばC#で関数定義を代入っぽく書くと次のようになります。
int multiply(int x, int y) => x*y;
// 関数を変数に代入できる
var m = multiply;
// 関数を引数で渡すこともできる
call_something(multiply);
上では何らかの値が実際に代入されています。
しかしRubyはどうでしょう。
def multiply(x, y) = x * y
# 以下は実行できない:
# multiplyはその場でただちに呼び出されるが
# 引数がないので失敗する
m = multiply
# 使えるのはこの方法だけ
m = method(:multiply)
Rubyでは「代入ライクな構文」はあまり正当化されないように感じられます。
そういうわけで、私はこんなことを考えています。誰もがdef method(...) =
と書くことに慣れてきて、「メソッドは値である」という考えが自然と必要とされる未来が来るかもしれません。そのときには、このアイデアをRubyに取り込もうと試みられるようになるかもしれません。
🔗 まとめ
一部の読者がこの「エイプリルフールのジョーク」のテーマを理解して、それを「言語のひどい点」「無用な構文」の証拠として使う可能性は十分考えられます(特に私が実装の欠点について率直に書いたことを考慮すれば)。
しかし私がここに来たのは、Rubyの優位性を説いて回るためでもなければ、Rubyに価値がないと暴露するためでもありません。
ここでの私のテーマは、私たちがコードで「何をどうやって伝えたいのか」という気づきへの理解が進むに連れて言語がどう変わるのか、逆に言語が変わると私たちのそうした理解がどのように微調整されるのか、ということなのです。
プログラミング言語において、そうした変化のプロセスが生じるのは自然なことであり、避けがたいことであり、絶え間なく続くものです。ちょうど人間の話す自然言語がそうであるのと同じように。
しかし自然言語と異なり、多くのプログラミング言語の変化は、実際の使われ方によってもたらされるだけではなく、その言語そのものに内在される価値によってももたらされます。私がRubyに惹きつけられるのは、「Rubyならではの価値」すなわちストーリーレベルの明確さをもたらす、フレーズレベルの表現力があるように感じているからなのです。
私は、Rubyが「そのような表現力を備えた唯一の言語ではない」などと(ましてやそうした言語ですらないなどと)言いたいのではなく、長年に渡る設計上のさまざまな決定が疑問視されていると言いたいのでもありません。私が申し上げたいのは、私にとってのRubyは、ユーザーを(私も含めて)そのような方向へ意識的に考えさせる言語であり、(私の願いとしては)そうした考えから生み出されるテキスト(コード)をありきたりでない良質なものにする言語であるということなのです。
以上で、本シリーズで予定していた最後の機能について書き終えました。今後についてですが、いくつかの一般的な結論や、若干のボーナスコンテンツとして「私が実は好きでないRuby構文要素のリスト(少しですがあります!)」や「Rubyで実現可能だがまだ実現されていない構文要素のリスト」を記事にするかもしれません。
しかし今はもう12月、つまり次のRubyのリリースが迫っています。ということは、例年どおり今年分のchangelogをまとめる必要があるのですが、そこそこ時間がかかるのが普通です5。今年はchangelog関連のメモを記事としていくつか投稿することにし、新年が明けてから「uselessシンタックスシュガー」シリーズのまとめ記事を含む記事をいくつか公開する予定です。
今後の記事をフォローしたい方は、Substack に登録いただくか、Twitterでフォローをお願いします。
お読みいただきありがとうございます。ウクライナへの軍事および人道支援のための寄付およびロビー活動による支援をお願いいたします。このリンクから、総合的な情報源および寄付を受け付けている国や民間基金への多数のリンクを参照いただけます。
すべてに参加するお時間が取れない場合は、Come Back Aliveへの寄付が常に良い選択となります。
本記事(あるいは過去の私の仕事)が有用だと思えたら、Buy Me A Coffeeサイトにある私のアカウントまでお心づけをお願いします。戦争が終わるまでの間、ここへのお支払いは(可能な場合)私や私の戦友たちが必要とする装備または上述のいずれかの基金に100%充てられます。
関連記事
- ただし、コンソールで呼び出されるスクリプト言語としてRubyを使うのであれば、スクリプトを手軽に書き捨てたり実験したりするときにこの構文が便利です。 ↩
-
単独のlambda(これはRubyのメソッドと直接関係があるわけではありませんが)は、世間のトレンドに沿って
lambda { |arg| body }
を->(arg) { body }
と書けるようになりました。 ↩ -
はい、皆さんご承知のように
Struct
かData
を継承すればメソッドの一部は不要になります。しかし今はそのことは重要ではありません。 ↩ -
Rubyでブロックを囲むための構文構造は2種類あります。
do
とend
で囲む方法と、{}
で囲む方法です。どちらの構文を選ぶかについては、2つのスタイルがあります。コミュニティのあるスタイルでは、{}
は1行ブロックのみに使います(2行以上のブロックではdo
とend
を使います)。別のスタイルでは(小生もそうなのですが)、map
やfilter
のように値を返す「機能的な」ブロックは{}
で囲み、命令的な行が複数並んでいる場合にのみdo
とend
で囲みます。 ↩ - 訳注: zverokさんによるRuby 3.3のChangelogは既に公開されています。 ↩
概要
原著者の許諾を得て翻訳・公開いたします。
日本語タイトルは内容に即したものにしました。
endlessは一部を除き「endレス」としました。