📝

JSer が Ruby やるので備忘録

2021/07/04に公開

@armorik83 です。なにやら最近は完全にAngular 芸人と呼ばれるようになったのですが、一方で Ruby 知識はド素人、今まで避け続けてきたもんだから仕方がない。で、思い立ったので学び始めます。理由はご想像にお任せ。

以下の文体ですが、他言語にはある程度長けていても、Ruby は一切やったことがない、というレベルを想定しています。比較対象は主に JavaScript ES6、自分用なので雑です。

バージョン

JavaScript のバージョンはES nで表現するが、基本的にはブラウザの実装次第。Ruby はインタプリタ自体のバージョンを指す。インタプリタといっても、Ruby 1.9 からはYARVという VM 上で動いてるらしい。

最新は執筆時点で2.1.6, 2.2.2。この 2 系統あたりを見ておけばいいのかな、1.9.3はサポート終了してるらしいので、今回は2.0以降に絞って学ぶ。

読むもの

ざっと2.0.0のマニュアルを読んでいく。2.1, 2.2前提で学ぶとやんごとなき事情でハマる恐れがあるので、あとで知識を上書きする。

リンク先は全て 2.0.0 にしているので、参照される方は注意。

目安にするコーディングスタイル

右も左も分かってないので、とりあえず以下のものを参照する。

https://github.com/cookpad/styleguide/blob/master/ruby.ja.md

特徴

はじめに』の引用。調べなくても想像つくものと、つかないものに分ける。

想像つく

  • インタプリタ
  • 変数に型が無い (動的型付け)
  • ユーザによるメモリ管理が不要
  • 全てがオブジェクト
  • クラス、継承、メソッド
  • ブロック付きメソッド呼び出し(イテレータ)
  • クロージャ(※でも JS とは全然違った)
  • 強力な文字列操作/正規表現
  • 多倍長整数
  • 例外処理機能
  • OS への直接アクセス

想像つかん

  • 変数宣言が不要
  • 特異メソッド
  • モジュールによる Mix-in
  • ダイナミックローディング

スクリプト言語は JavaScript と PHP の経験があるので、ざっと見て馴染みないなーというものを下に分けた。CLI での Hello World とかはパスだパス!

基礎構文

ステートメント

1 行 1 ステートメント。セミコロンなし。1 行 2 ステートメント以上書きたいときに限りセミコロンで区切る。

JS Ruby
いつも付けてる 要らない

コメント行

#を使う。行末まで有効。行頭、行中でも構わない。

JS Ruby
// #

コメントブロック

=begin, =endを使う。埋め込みドキュメントと名付けられていて、JS の/*...*/とは想定が異なる。

JS Ruby
/*, */ =begin, =end

変数と定数

Ruby の変数と定数の種別は変数名の最初の一文字によって、ローカル変数、インスタンス変数、クラス変数、 グローバル変数、定数のいずれかに区別されます。
http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fvariables.html

ローカル変数だけでなく、インスタンス変数も格納された時点から有効になる。クラス変数がstaticで宣言できるのは TypeScript の特徴。グローバル変数の比較は一例。

JS Ruby TypeScript
var foobar = 42; foobar = 42
this.instanceVar = 42; @instanceVar = 42
ClassName.classVar = 42; @@classVar = 42 static classVar = 42;
const FOO_BAR = 42; FOO_BAR = 42
window.foobar = 42; $foobar = 42

ローカル変数のスコープ

宣言した位置からその変数が宣言されたブロック、メソッド定義、またはクラス/モジュール定義の終りまでです。
http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fvariables.html

以下も参照する。

https://github.com/cookpad/styleguide/blob/master/ruby.ja.md#変数

全てがオブジェクト

一例。

数値型はInteger, Floatになるんだな。Numericは Abstract なので後回し。

演算子

http://docs.ruby-lang.org/ja/2.0.0/doc/symref.html

  • + - * / % おなじみ
  • ** 累乗
  • += 自己代入(可能な構文はここ
  • < > <= >= 比較
  • && || ! and, or, not
  • & | << >> ^ ~ ビット演算

文字列

http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fliteral.html#string

シングルクオートとダブルクオートに挙動の違いがあるのは PHP と同じ。JS では ES6 より Template strings が使える

js
var name = 'armorik83';
var str1 = 'hello ${name}'; // hello ${name}
var str2 = "hello ${name}"; // hello ${name}
var str3 = `hello ${name}`; // hello armorik83
ruby
name = 'armorik83'
str1 = 'hello #{name}' # hello \#{name}
str2 = "hello #{name}" # hello armorik83

正規表現

/abc/で正規表現リテラルなのは JS と同じ。Docsをざっと見た感じ JS とそんなに変わらないが、モードにいくつか違いがありそうだ。

破壊的メソッドと値渡し・参照渡し

http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fcall.html

メソッド名の後に!が付くと破壊的メソッド。公式が行っているものの慣習らしく、言語仕様として定められているわけではない。

あと、Ruby では常に値渡しらしい。ただ、オブジェクトなので参照の値渡しということだ。副作用でハマったらこの辺を疑うってことで。

真偽値を返すメソッド

慣用的に、真偽値を返すタイプのメソッドを示すために使われます。
http://docs.ruby-lang.org/ja/2.0.0/doc/symref.html#q

JS とかで、キャメルケースでisFooとかするやつ。慣習的に Ruby ではisFooしない。

なにがしの言語 Ruby
isEmpty() empty?

配列

http://docs.ruby-lang.org/ja/2.0.0/class/Array.html

js
var arr = [1, 2, 3];
arr[1] = 4;
console.log(arr); // [1, 4, 3]
ruby
arr = [1, 2, 3]
arr[1] = 4;
p arr # [1, 4, 3]

範囲指定

http://docs.ruby-lang.org/ja/2.0.0/class/Range.html

こういう構文だと覚えるより、Range リテラルを与えていると考えた方がよさそう。

js
var arr = [1, 2, 3, 4];
console.log(arr[1..3]);       // SyntaxError: Unexpected number
console.log(arr[1...3]);      // SyntaxError: Unexpected number
console.log(arr.slice(1, 4)); // [2, 3, 4]
console.log(arr.slice(1, 3)); // [2, 3]
ruby
arr = [1, 2, 3, 4]
p arr[1..3]  # [2, 3, 4]
p arr[1...3] # [2, 3]

逆から

js
var arr = [1, 2, 3, 4];
console.log(arr.slice(-1)[0]);     // 4
console.log(arr.slice(-2, -1)[0]); // 3
ruby
arr = [1, 2, 3, 4]
p arr[-1] # 4
p arr[-2] # 3

おおっ、JS 地味につらい…。(比較にすらなってない)

%記法

ruby
words = ['foo', 'bar', 'baz']
words = %w(foo bar baz)

Array<String>は下のようにも書ける。コーディングスタイルではこちらを推奨していた。

ハッシュ

http://docs.ruby-lang.org/ja/2.0.0/class/Hash.html

JS でいう Object 型、他の言語でいう連想配列、辞書のあたり。

js
var price1 = {'apple': 2000, 'orange': 1500, 'grape': 3000};
console.log(price1['apple']);

var price2 = {apple: 2000, orange: 1500, grape: 3000};
console.log(price2.apple);
ruby
price1 = { 'apple' => 2000, 'orange' => 1500, 'grape' => 3000 }
p price1['apple'] # 2000

price2 = { apple: 2000, orange: 1500, grape: 3000 }
p price2[:apple] # 2000

apple:で宣言すると、参照時は:appleというSymbol リテラル形式なのが特徴的。

制御構造

if, unless

Ruby ではifを繋げるのはelsifであり、else if(C のように)でも elif(sh のように)でもないことに注意してください。
http://docs.ruby-lang.org/ja/2.0.0/doc/spec=2fcontrol.html#if

endってのも JS からすると驚きがデカい。

ruby
if cond
  # ...
elsif cond
  # ...
else
  # ...
end

後置ifという書き方もある。

ruby
if user.active?
  send_mail_to(user)
end

send_mail_to(user) if user.active?

三項演算子はちゃんとある。if内代入もある。個人的にはif内代入は使わない。

unlessは鳥肌もので、JS でif (!arg) { return; }のように書くことに馴染んでいる自分としては、こんな逆転する構文は面倒にしか見えない。

コーディングスタイルで推奨どころかSHOULD扱いなので、そういうものかーと思いながら見てる。

case

ruby
case cond
when val1
  # ...
when val2
  # ...
else
  # ...
end

for

js
var hash = {a: 1, b: 2, c: 3};

for (let k in hash) {
  if (hash.hasOwnProperty(k)) {
    let v = hash[k];
    console.log(k, v);
  }
}
ruby
hash = {a: 1, b: 2, c: 3}

for k, v in hash
  p k, v
end

意外なことに JS のfor文(for (var i; i < len; i++)みたいな)と Ruby のfor文は全く別物だった。JS のfor-in文が近い。

その他の繰り返し

while文はまだ馴染みがあるが、times, upto, downtoなどのメソッドはかなり新鮮。慣れるまでは毎回調べそう。

上の例はeachメソッドでこうも書ける。do必須。

ruby
hash = {a: 1, b: 2, c: 3}

hash.each do |k, v|
  p k, v
end

関数・クラス・メソッド宣言

def

比較に使う JS はオーソドックスな書き方にしている。

js
function square(n) {
  return n * n;
}
console.log(square(3)); // 9
ruby
def square n
  n * n
end

p square(3) # 9

class, initialize

JS のconstructorのように、def initializeを定義する。

js
class Component {
  constructor(store) {
    this.store = store;
  }
}
ruby
class Component
  def initialize store
    @store = store
  end
end

new とアクセサ

js
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}

var u = new User('armorik83', 83);
console.log(u.name); // "armorik83"
console.log(u.age);  // 83
ruby
class User
  def initialize name, age
    @name = name
    @age = age
  end
end

u = User.new('armorik83', 83)
p u.name # NoMethodError
p u.age  # NoMethodError

NoMethodErrorとなるので、アクセサの宣言が必要らしい。

  • attr_accessor
  • attr_reader
  • attr_writer

http://docs.ruby-lang.org/ja/2.0.0/class/Module.html#I_ATTR_ACCESSOR

ruby
class User
  def initialize name, age
    @name = name
    @age = age
  end
  attr_accessor :name, :age
end

u = User.new('armorik83', 83)
p u.name # "armorik83"
p u.age  # 83

なるほど。

インスタンスメソッド

js
class User {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  isAdult() {
    return 20 <= this.age;
  }
}

var u = new User('armorik83', 83);
console.log(u.isAdult()); // true
var c = new User('child', 8);
console.log(c.isAdult()); // false
ruby
class User
  def initialize name, age
    @name = name
    @age = age
  end

  def adult?
    20 <= @age
  end
end

u = User.new('armorik83', 83)
p u.adult? # true
c = User.new('child', 8)
p c.adult? # false

ここだけ見ると、OOPL 経験者なら戸惑うところは無さそう。

クラスメソッド、クラス変数

ruby
class ClassName
  @@classVar = 42

  def ClassName.methodName
    # ...
  end
end

クラス変数はコーディングスタイルMUST NOTだった。

継承

ruby
class Base
  def initialize prop
    @prop = prop
  end

  def print
    p @prop
  end
end

class Concrete < Base
  def initialize prop, childProp
    super prop
    @childProp = childProp
  end

  def printAll
    p @prop, @childProp
  end
end

base = Base.new('a')
base.print

c = Concrete.new('b', 2)
c.print
c.printAll

あまりいい例とは言えん。

馴染みがなくて用途が分かってないもの

以下雑多。

Proc

クロージャっぽいもの? Ruby に第一級関数は無いようで、Proc で実現しているようだ。アプリケーションを書いたことがないので想像が付いてない。

特異メソッド

インスタンスにメソッドを足せるらしい。使い道想像できず。

Mix-in

必要になったら覚えたい。

2.1, 2.2 の新機能

公式ソースではないので、ググるキッカケ的に。

ちょっとは読めるようになった

そもそも、どの解説記事を読むにも構文が分かっておらず読めなかったので、今回はその問題を潰す目的。Ruby らしい文化、Ruby 最高ってなる機能はまだまだ全然分かってないので、まあぼちぼち…。

以下まさかりやオススメ記事が並ぶと大変嬉しいです。それでは。

Discussion