Skip to content

Latest commit

 

History

History
213 lines (160 loc) · 9.01 KB

syntax.md

File metadata and controls

213 lines (160 loc) · 9.01 KB

文法

プログラミングが楽しいかどうかは、文法が大きな役割を果たしているのは容易に想像できるでしょう。 Rubyプログラミングが楽しく感じる理由を文法から考察します。

カッコ不要のメソッド呼び出し

ひと目見て最も異質な文法が、カッコなしのメソッド呼び出しでしょう。 Rubyでは

Faraday.get 'https://google.com'

のようにカッコ無しでメソッドが呼び出せます。 よく他言語利用者から「そんなにカッコを書きたくないのか」と揶揄されるわけですが、 カッコを書きたくないのもそうなんですが、それ以上に重要なメリットがあります。 それは、宣言的に見える記述をするのに必須だということです。

例えば、ActiveRecordで関連性を定義する記述は、

class User < ApplicationRecord
  has_many :articles
end

と書きますが、これは has_many というメソッド1:articles という引数でメソッド呼び出ししているだけなのに、 あたかも 宣言しているように見える ところがとても良いのです。 これをもし has_many(:articles) と書いてしまったら 台無し なのです。

do-end ブロック

Rubyは、メソッド呼び出しで do-end ブロックを渡すことができ、少し特殊な引数 &block でアクセスできます。

[1, 2, 3].map do |n|
  n * 100
end
# => [100, 200, 300]

似たような処理の固まりを与える仕組みは他の言語にもあり、関数ポインタやクロージャと呼ばれています。 しかし、他の言語のそれは関数の 引数のひとつ であり、Rubyのブロックは通常の引数とは別の特殊な引数であるという違いがあります2。 メソッド呼び出しにカッコを付けた場合、ブロックはカッコの外側に書かれます。

[1, 2, 3].reduce(5) do |sum, i|
  sum + 1
end
# => 11

上記のコードはJavaScriptの場合このように書きます。

// 素のJavaScript
[1, 2, 3].map(function(n) {
  return n * 100;
});

// arrow function
[1, 2, 3].map((n) => {
   return n * 100;
});

// return の省略
[1, 2, 3].map((n) => n * 100);

近年だんだん記述が楽になってきましたが、処理が複数行になることも多いので2番めの記述をすることが多くなります。 見てわかるとおり、メソッド呼び出しの () の中に引数の一つとして書かれています。

カッコの外側に書かれることが実はものすごく重要な差を生み出しています。 その例として、Rubyの有名なテストフレームワークである rspec と、それを真似たJavaScriptの jest の記述を比較してみましょう。

describe SayHello do
  it 'say hello world' do
    expect(SayHello.hello('world')).to eq 'hello world'
  end
end

同じ事をJavaScriptで書いてみます。

describe('SayHello', () => {
  it('say hello world', () => {
    expect(hello('world')).toBe('hello world');
  });
});

美しいのはどちらか一目瞭然ですね。 JavaScriptでは、 () => {}); という 無意味な記号の羅列 が目に付きます。 it( のカッコも邪魔ですし 'say hello world',, も邪魔です。 それに比べRubyは it 空白 タイトル 空白 do という、 対称性の高い見た目になります。 JavaScriptは ( タイトル , という非対称で美しくないし、そもそも記号なので息が詰まる思いがします。 他言語利用者は「そんなこと・・・」と思うかもしれないけど、 そんなところが一番重要なんだ って気づいてほしい。 見た目の美しさは楽しさに繋がるし、実用面でも可読性(というかメリハリ)の高さはバグ発生の回避にもつながります。 Rubyの真髄はもしかしたら 空白 にあるのかもしれません。

expect().to eq 'hello-world' というのも宣言的で良いですね。 とはいえ、このrspecはJavaScriptと比較しやすいように書いたコードで、まだ本気でありません。

describe SayHello do
  subject { SayHello.hello 'world' }

  it { is_expected.to eq 'hello world' }
end

本気を出した rspec ではカッコが完全に消えてさらに宣言的になりました。

!, ? 付きメソッド

Rubyでは、破壊的なメソッドはメソッド名の末尾に ! 、 真偽値を返すメソッドには ? を付けると言う習慣があります。 ! の場合はそこまで厳密に運用されているわけではないですが、 ? の方はほぼ100%守られている習慣です。 特にこの ? が実に強力で、真偽値が返ってくるんだろうと予測できることもさることながら、なによりも その判別がしやすい のが非常に利点です。 すなわち、 ? という文字は目に付きやすく、 isXXX は目が滑りやすいという話です。 これも空白のときと同じように、メリハリがポイントなのかもしれません。

! はRuby本体では破壊的なメソッドという意味で使われますが、 ライブラリやアプリケーションではそのような使われ方をすることは多くありません。 アプリケーションのクラスなどを書く場合、通常あまり非破壊なメソッドを書くことは無いからです。 その代わり、 それぞれのプログラマが思い思いのルールで利用しています。

例えば、ちょっとだけ違う動作をする本質的には同じメソッドを表現するために使うことがあります。 ActiveRecord だと、 save は失敗したときに false を返し、save! は失敗したときに例外を吐きます。 この明確だけど微々たる差異を、 ! を使わずメソッド名で表現なんてできそうにありません。

自分の場合、 ! は躍動感があるので、 本質的で特殊な動作 を表すのに使ったりします。 例えばテストコードにおいて3HTTPリクエストをする や、 一連のブラウザ操作 などに ! 付きのメソッド名を与えます。

RSpec.describe UsersController, type: :controller do
  descrine 'GET #index' do
    let(:request!) { get :index }

    it do
      request!
      expect(response).to have_https_status :ok
    end
  end
end

! の持つ異質性により、この「リクエスト」はなにかが違うぞ、と注意を引きます。 そしてその躍動感により、名詞ではなく動詞 であるとひと目で感づいてくれるはずです。 do_request は2単語なのがダサいし、全ての文字が平凡すぎるのです。

ちなみに、実は文中にも出ている response と対になる request が元々存在し、 request と言う名前は使えません。 そんな危機的状況をたった1文字で解決できるという点でも ! はとても偉大です。

case 式

Rubyの case 式はただの switch 文ではありません。

case foo
when XXX

と書いた時、中で XXX === foo と言う判定がされます。 この === というのがかなりの曲者で、例えば String.===(x)x.is_a?(String) と言う判定がされます。 この仕組みにより、 when の横には本当にありとあらゆるものが書け、恐ろしい表現力を発揮します。 もちろん === は再定義可能なので、自分のライブラリで素晴らしい === を書くこともできます。 相当センスは必要ですが。

def odd?
 -> (x) { x.odd? }
end

case foo
when /hello,.+/
when String
when odd?
when 10...100
when 1, 3, 5
end

これが全て読んで字のごとく期待通りの判定がされます。 ただ残念なのは、case式を使うこと自体があまりない ということです。

他の言語で好きな文法

特にないです。

強いて言えばJavaScriptの

const a  = 1;
const q = { a };
# => { a: 1 }

はそこそこ好きです。とは言えRubyには適応できなさそうです。

まとめ

RubyはDSL言語と言われるように、あたかも宣言してるかの如き表現が自然にできる言語です。 カッコの省略が強力で、文法は基本的に空白区切りになっていて非常に読みやすく感じます。 あとは 型みたいなクソッタレなものが出現しない のも本当に最高ですね。

Footnotes

  1. いわゆるクラスメソッドを呼び出している。 ここでクラスメソッドが呼び出せるのもRubyの特異性?

  2. Rubyも lambdaproc などのクロージャがあり普通の引数として渡すこともできるがブロック引数がよく使われる

  3. 驚き最小の原則に反するのでテスト以外では使わない