IT・技術研修ならCTC教育サービス

サイト内検索 企業情報 サイトマップ

研修コース検索

コラム

Ruby on Rails 海外事情コラム

CTC 教育サービス

 [IT研修]注目キーワード   Python  UiPath(RPA)  最新技術動向  OpenStack  システムトラブルシュート 

第38回 JavaScript開発者がRubyを学んでなるほどと思った8つのこと (野田貴子) 2018年11月

こんにちはー。野田貴子です。 昔からRuby on Railsを触っている人の多くは、Web開発技術の流行がバックエンドからフロントエンドに移るにつれて、バックエンドのRuby(on Rails)というバックボーンを持ちつつ、フロントエンドのJavaScriptを学んだことでしょう。

今ではRuby on Railsもバックエンドとフロントエンドを分業できるようになっているため、先にJavaScriptを学んでその後にRuby(on Rails)を学び始めることも珍しくないようです。

それでもフロントエンドからバックエンドという流れは目新しい感じがするのでしょうか? JavaScript経験者がRubyを学んで知った2つの言語の差異に関するブログが注目を浴びていたのでご紹介します。

Ruby gotchas for the JavaScript developer
https://blog.calendly.com/ruby-gotchas-javascript-developer/

以下翻訳です。

1. 暗黙のリターン

これはScala、Lisp、Perl、あるいはRustを触ったことがある人には馴染みがあると思いますが、JavaScriptを含むそれ以外のほとんどのプログラミング言語では、関数で返すものは明記する必要があります。

function add(a, b) {
  return a + b;
}

しかしRubyでは最後に実行された文の値が自動的に返されます。上のコードをRubyで実装すると次のようになります。

def add(a, b)
  a + b
end

return キーワードはRubyにも存在しますが、通常はメソッドの前半で値を返す時に使われます。Rubyを書くのに慣れてくれば return キーワードを外すことにも慣れていくでしょう。ここでのなるほどは、既存のコードを読んだときに、あるメソッドが何かを返すことを想定しているのかどうかが常にはっきりするわけではないということです。そのため、うっかりとメソッドの最後にログを出力する文を追加してしまって、その前の文の返り値を使っていた別の箇所のコードが動かなくなってしまうことに気が付かないことがあります。

def set_name(name)
  @name = name
  logger.log "nameを#{name}に設定した" # 返り値を変更してしまっている!
end
2. 文の最後の条件

Rubyでは文の最後に条件を設定できます。

puts 'x is 42' if x == 42
puts 'x is NOT 42' unless x == 3

そしてご覧のとおりRubyには unless キーワードもあります。このようなシンタックスシュガーはプログラミング言語を表現豊かにし、開発者が書いたコードの意味を表現するのに役立ちます。つまり、条件付き構文を誤って使ってしまうと、コードの意味を読み取る際に余計な時間がかかることになります。

3. カッコなしのメソッド呼び出し

Rubyではメソッドを呼び出すときにカッコを省略できます。たとえば次の2つは同等です。

Time.now() # 現在時刻を返す
Time.now   # こちらも現在時刻を返す

JavaScriptでは2行目の方は now 関数への参照を返すと考えられますが、Rubyではこちらもメソッド呼び出しです。

別の例を見てみましょう。

connection.post url, params, default_headers

post メソッドの呼び出しは判断できたでしょうが、urlparamsdefault_headers もメソッド呼び出しであることに気が付きましたか? params メソッドがデータベースからの読み取りやネットワークリクエストの場合はどうなるでしょうか? そのような事実は簡単に隠れてしまうので、その「隠れた」呼び出しがループ内で起こっている場合は特に、意図しないコードにパフォーマンスの低下が発生しています。

おまけの面白ネタ:supersuper() の呼び出しは動作が異なります。

4. 可変文字列

Rubyの文字列は変更可能なので、こういったことができます。

s = 'mellow'
s[0] = 'y' # 一文字目を変更する
puts s # 'yellow'と表示される

この性質によって文字列を複製せずにより効率的に文字列操作ができるようになりますが、文字列を受け入れるメソッドがその文字列を変更してしまう可能性もあります。しかし安心してください。オブジェクトの状態を変更するメソッドは名前に ! を追加する慣習があります。標準ライブラリにある例を紹介します。

s.sub('foo', 'bar') # 元の文字列をコピーして置換したものを返します
s.sub!('foo', 'bar') # 置換を直接実行します

しかしこれは単なる慣例なので、標準ライブラリ以外では必ずしもこの慣習に従っているとは限りません。文字列(または任意のオブジェクト)を誤って変更しないようにするために、s.freeze を呼び出してフリーズすることができます。

ちなみに、RubyのString#replaceはJavaScriptの文字列置換とは全く違います。

5. ブロック、プロシージャ、ラムダ

JavaScriptではこれはすべて関数に関する話です。この3つは他の値のように第一級オブジェクトであるため、任意の場所で簡単に割り当て、回り込み、呼び出しができます。後で実行する必要があるコードを記述しておくひとつの方法です。技術的にはJavaScriptで関数宣言関数式は同じではありませんが、その違いは非常にわずかですので、これらを定義して使用する構文はほぼ同じです。RubyにはJavaScript関数のメソッド、ブロック、プロシージャ、ラムダに似た構文が4つあります。最後の3つはまとめてクロージャと呼ばれます。

Rubyのメソッドは第一級オブジェクトではありません。値として使用したり、渡すことはできません。クロージャも作成しません。Rubyでのメソッドの使用法はかなり単純で、今までの私の経験では混乱する状況になったことはないので、詳細には触れずにおきます。

ブロックは無名関数のようなものです。たとえば配列内のすべての要素を二乗させたいとします。最近のJavaScriptでこれを実装する方法は次のとおりです。

[1, 2, 3].map(x => {
  return x * x;
});

こちらがRubyのコードです。

[1, 2, 3].map do |x|
  x * x
end

do から end の間のコードはブロックです。このマッピング関数を変数に保存してそれを渡したいときはどうすればよいでしょうか? JavaScriptでは非常に些細な作業です。

let squarer = x => {
  return x * x;
}
[1, 2, 3].map(squarer);

みなさんはRubyでも同じようなことができると考えたかもしれません。

\# 文法エラー
squarer = do |x|
  x * x
end
[1, 2, 3].map(squarer)

しかし、上記の構文は有効ではありません。これは実際に私がRubyを始めた頃の発見のひとつでした。メソッドに似ているブロックは第一級オブジェクトではありません。Rubyはこの制限を回避するために新しいタイプのオブジェクトを導入する必要がありました。procです。これの唯一の目的は、ブロックを変数に保存して渡すことができるようにブロックをラップすることです。

squarer = Proc.new do |x|
  x * x
end
[1, 2, 3].map(&squarer)

squarer の前の & を見てください。単項演算子の & をprocに適用してブロックに変換しています(ブロックがmap 呼び出しの中でインライン展開されるかのようです)。procを呼び出すには、procの call メソッドを呼び出す必要があります。

squarer.call(5) # 25を返す

待ってください。Rubyにもラムダがあります! ラムダはプロシージャと非常に似ていますが、lambda キーワードを使用して定義する点が異なります。

squarer_lambda = lambda do |x|
  x * x
end

lambdaとprocの違いは2点あります。

  1. lambdaは呼び出したときに正しい数の引数を渡す必要がありますが、procはそうではありません。以下の行はエラーが発生します。

     squarer_lambda.call
     \# -> ArgumentError (wrong number of arguments (given 0, expected 1))
  2. lambda内の return 文と break 文でそのlambdaは終了します(JS内の関数/関数式に相当する動作です)。しかし、procやprocの中で returnbreak を行うと、それらを呼び出したメソッドの方が終了します。こちらのStackOverflowの回答に詳細な説明があります。

要点をまとめます。

  • ブロックはRubyで最も基本的なクロージャです(無名関数式がインラインで渡されると思ってください)
  • ブロックを渡すために変数にブロックを代入する必要がある場合は、それをprocでラップする必要があります
  • 引数の数を検証する必要がある場合や、return が呼び出しメソッドを終了しない実行コンテキストを作成する必要がある場合は、lambdaを使用します

Rubyにはほぼ同じことをする4つの構文があり、新規参入者は混乱しますが、言語のエコシステムや共通のコーディング規則にさらされるにつれて、より快適になるでしょう。まだ混乱しているなと感じたときは、Rubyのクロージャに関するこちらの記事を読むことをお勧めします。

6. シンボル

Rubyのシンボルはモノを識別するために使われます。どんな種類のモノかというと、ハッシュキー、メソッド、クラス、インスタンス変数です。これらを参照するたびに、実際にはシンボルを使用しています。名前にコロン(:)を付けることで、独自のシンボルを作成することもできます。例を見てみましょう。

status = :bad_request

ここで :bad_request シンボルを作成し、それを変数に割り当てました。外側ではシンボルは文字列に似ています。どちらもテキストの文字を保持できるオブジェクトであり、上記の行は次のように記述できます。

status = 'bad_request'

しかし、シンボルには重要な違いがいくつかあります。

  • シンボルは不変です。シンボルの名前は決して変わりません。
  • 与えられたシンボル名は、Rubyプログラム全体で同じオブジェクトを参照します。

これにより、シンボルは文字列よりも効率的な識別子になります。文字列の場合プログラム内で 'bad_request' が見つかるたびに新しい文字列が作成され、後でガベージコレクタによって破棄されます。シンボルは違います。初めて :bad_request という式を実行したときは新しいシンボルが作成されますが、その後のプログラムでは同じシンボルオブジェクトを参照します(Rubyはプログラムの実行中にSymbol Tableを実行し続けます)ので、メモリが節約されます。インタプリタはオブジェクトの名前でなく内部オブジェクトIDを比較すればよいので、シンボルも高速に比較できます。

これらの理由から、シンボルは一般にRubyのHashキーとして使用されます。次の行は、シンボルキーと文字列値を持つHashリテラルを作成しています。

\# キーはシンボル
user = {:first_name => 'Bruce', :last_name => 'Dickinson'}

Ruby 1.9でより短い代替構文が導入されました。

\# こちらもキーはシンボル!
user = {first_name: 'Bruce', last_name: 'Dickinson'}

上の行は有効なJavaScriptのように見えるので、キー用の文字列を持つHashオブジェクトを作成していると見間違いやすいですが、実際にはこちらでもシンボルを使用しています!

下の構文では、キーに文字列を使用しています。

# キーは文字列
user = {'first_name' => 'Bruce', 'last_name' => 'Dickinson'}

同様に、ハッシュにインデックスを付ける方法にも注意してください

user[:first_name] # :first_name (シンボル)で検索します
user['first_name'] # 'first_name' (文字列)で検索します
user.first_name # first_nameを呼び出す関数を呼ぼうとしています

こちらの短い記事でシンボルについてより詳細に解説されています。

7.等価演算子

JavaScriptには、==(緩やかな等価)と ===(厳密な等価)の2つの等価比較演算子があります。前者は自動的に型変換され、後者は型変換されません。

// JavaScript
1 == '1' // true
0 == [] // true
1 === '1' // false
0 === [] // false

コードのふるまいをより分かりやすくするために、ほとんどの場所で === を使用することがJavaScriptのよくある習慣です。ほとんどの場合、Rubyは比較の際に型強制を実行しません。

\# Ruby
1 == '1' # false
0 == [] # false

Rubyにも === 演算子がありますが、その意味はまったく異なります。実際には、これはオブジェクトのクラスの実装に依存します。なぜなら、演算子はRubyの通常のメソッドにすぎないからです。3つのイコール演算子は case 文内の一致ロジックを記述するために用意されています。等価比較演算の詳細はこちらです。

もしRubyで===を見かけたら、それは==のつもりだったはずです。

8 厳密な真偽

JavaScriptは真偽の判断に対して多くの批判を受けています。しかし、学習曲線を乗り越えたあとはこのタイプの言語を使っていろいろな種類の簡略表記を書くことになります。たとえば、JavaScriptの if (x)x が真であることを意味するだけでなく、x0 でもなく、空の文字列でもないことを意味します。

Rubyでは、真偽値でない値の真偽評価は厳密です。0''[]はすべて真と評価されます。ですのでそんな悪い習慣は忘れて、もっと明白に真偽値を使いましょう。覚えておくべきルールは簡単です。

Rubyで偽なのはfalseとnilだけです。

空であるかを確かめるためには empty? を使います。

まとめ

新しいプログラミング言語を学び始めたばかりの頃は、今までと異なる構文や新しいパラダイムに不快になるものですが、この差異は私たちの学習に役立っています。みなさんがより豊かな開発者になるためにに、他のプログラミング言語の経験がある人に向けたRubyの参考資料を紹介します。

 


 

 [IT研修]注目キーワード   Python  UiPath(RPA)  最新技術動向  OpenStack  システムトラブルシュート