Post

私がRubyに慣れ親しんだJavaScript初心者だったころ【Ruby訛り】

まえがき

おはようございます。

「訛り」って表現は独特なのでしょうか。 でも、訛りとしか表現できない現象です。

私の母語はRubyです。 第二言語としてJavaScriptに入門しようとしたんです。

今は無事に両方使えるようになったつもりですが…未だに多少の訛りは残っているかもしれません。

別にダメって言ってるわけじゃありませんよ。私の考えではそれも個性です。 でも漠然と、他の人には読みづらいかもしれない、という不安があります。
別に矯正しなくても動くものは動くんですけどね。私の中では「郷に入っては郷に従え」のイメージでして、その言語が使われてきた文化について知りたいし、文化に従った書き方をしたいと思っているのです。

そんな私ですが、ここではあえて初心を思い出して訛って書きます。

…で、訛りってどういうこと、って?
もしあなたがRubyとJavaScriptについて多少知っているのなら、きっと見れば簡単にわかりますよ。



セミコロンを忘れがち

症例

1
2
3
4
5
6
// JavaScript
if (a === b) {
  console.log("Yes")
} else {
  console.log("No")
}

JavaScriptは「自動セミコロン挿入」というなんともパワフルな響きの機能があり、セミコロンを付けなくてもある程度動く。しかしながらものによってはセミコロン挿入してくれない場合があるため、基本的には手動挿入したほうがよいとされている。

一方Rubyはセミコロンを付けないのが通常である。 セミコロンを付けても動くには動くが、実際に行末にセミコロンが付けられているコードを外で見たことはない。アスキーアートQuineの素などの特殊なプログラムを除けば。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Ruby
# これが普通
if a == b
  puts "Yes"
else
  puts "No"
end

# 実際見たことはないけどやれば動く
if a == b
  puts "Yes";
else
  puts "No";
end

# このくらいなら問題なく動く
if (a == b)
  puts("Yes");
else
  puts("No");
end

JavaScriptを書くときでさえセミコロンがなくても違和感を感じないため気づかない。付け忘れたところでほとんどの場合動いてしまうので余計気づかない。

forをあまり使わない

これに関しては未だにどっちが良いのかわからない。このままでも良いのかもしれない。

1
2
3
4
5
6
7
8
// 症例
[1, 2, 3].forEach(i => console.log(i));  //=> 1\n2\n3

// JavaScript特有の書き方
const arr = [1, 2, 3];
for (let i of arr) {
  console.log(i);
}

Ruby言語においてforを使わないというのはあるある。 配列やら何やら反復可能なオブジェクトを反復する際、思考停止でforを選ぶことはまずない。なぜならそのような値をループさせる方法が言語自体に大量に組み込まれているから。おまけに、forに相当するeachなどというメソッドさえ存在する。

1
2
3
4
5
6
7
8
# Ruby
# for(あまり使わない)
for i in [1, 2, 3]
  puts i
end

# Array#each(よく使う)
[1, 2, 3].each {|i| puts i }

RubyでArray#eachを使う方法を症例のコードと並べるとよく似ていることがわかるだろう。

1
2
// 症例
[1, 2, 3].forEach(i => console.log(i));
1
2
# Array#each
[1, 2, 3].each {|i| puts i }

ちなみにCを母語とする友人は何の疑問もなくたくさんforを使っている。

1
2
3
4
5
// C言語訛り
let arr = [1, 2, 3];
for (let i = 0; i < 3; i++) {
  console.log(String(arr[i]));
}

清々しい。

毎回コールバック関数の波括弧をつける

その振る舞いがブロックに見える。油断すると稀に引数部分をパイプで挟む。

1
2
3
4
5
// 1から10までの総和計算
// 症例
[...Array(10)]
  .map((_, i) => {return i + 1})
  .reduce((sum, i) => {return sum + i });  // 55

しかしこれはすぐにやめた。波括弧がないことよりも随所にreturnを付けざるを得なくなる状況が私の中のRubyist的思想に合わなかった。

1
2
3
4
// 本当はこうしたい
[...Array(10)]
  .map((_, i) => { i + 1 })
  .reduce((sum, i) => { sum + i });  // undefined(NG)

おそらく上のように書ければこの癖もなかなか抜けなかっただろうが、こんなふうには書けないので結果的にスタンダードな書き方が一番手に馴染んだ。

1
2
3
4
5
// このコールバックの書き方はよく見かけるので
// きっと大丈夫。きっと。
[...Array(10)]
  .map((_, i) => i + 1)
  .reduce((sum, i) => sum + i);      // 55

ちなみにRuby。

1
2
3
4
5
6
7
8
# 寄せて書く
(1..10).reduce{|sum, i| sum + i}  #=> 55

# よりRuby的
(1..10).inject(:+)  #=> 55

# 無慈悲
(1..10).sum         #=> 55

とりあえず1行で書こうとする

メソッドチェーン大好き。

オブジェクトがハッシュに見える

こういうObjectを書いて、

1
2
3
4
const obj = {
  hello: "Hello",
  world: "World"
};

「これHashやろ。もうそれでしかないやん。でもHashと違ってキーはStringなんだよな。」

とか言ってこうなる。

1
console.log(obj["hello"]);

時々間にもう1ステップ挟まる。

1
console.log(obj[:hello]);  // error

こうなって「あっ、そうだった」からのconsole.log(obj["hello"]);
それもこれもSymbolをキーにしたHashにしか見えてないせい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Ruby
# 文字列がキーのHash
hash = {
  "hello" => "Hello",
  "world" => "World"
}

puts hash["hello"]  #=> Hello

# -----

# SymbolがキーのHash
hash = {
  hello: "Hello",
  world: "World"
}

puts hash[:world]   #=> World

本物は一味違う。

1
2
// 模範解答
console.log(obj.hello);

違う言語を学ぶことは異国に行くことに似ているということを忘れてはならない。

関数から外側の変数が見えるのが不安

こういうの。

1
2
3
4
5
6
7
8
9
function foo() {
  console.log(`x is ${x}.`);
}

let x = 42;

console.log(x);  // 42 (わかる)

foo();           // x is 42. (!?)

訛りとは違うけどそわそわする。 なんとも言えない不安に包まれる。

なぜならこういう挙動で慣れているから。

1
2
3
4
5
6
7
8
9
10
# Ruby
def foo
  puts "x is #{x}."
end

x = 42

puts x  # 42

foo     # xが存在しない旨のエラー

世界が違う。

functionを使ってしまう

使っている人と使っていない人が半々くらいの印象なのでどっちでも良いのかもしれないけどconstに無名関数を代入するよりfunctionを使ってしまう。constを使う理由もわかるんだけど使ってしまう。

1
2
3
4
// 症例
function hello(name) {
  console.log(`Hello, ${name}!`);
}

これはおそらくRuby出身に限ったことでもないと思う。 なんにせよ構造が似てるからやってしまう。

1
2
3
4
# Ruby
def hello(name)
  puts "hello, #{name}!"
end
1
2
3
# Python3
def hello(name):
    print(f"hello, {name}!")
1
2
3
4
// C
void hello(const char *name) {
  printf("hello, %s!\n", name);
}

ちなみにJavaScriptでconstを使う場合はこうなる。

1
2
// `const`を使う場合
const hello = name => console.log(`Hello, ${name}!`);

症例まとめ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const id = {
  ichiro: 100,
  Jiro  : 200,
  Saburo: 300,
  Shiro : 400,
  Goro  : 500
}

function tellAllId() {
  Object.entries(id).forEach(([name, id]) => console.log(`${name}'s id is ${id}.`));
}

console.log(`Goro's id is ${id["Goro"]}.`);

tellAllId()

うっかりスネークケースが出る

JSとRubyを行ったり来たりしていると発症することがある。

1
function tell_all_id()
This post is licensed under CC BY 4.0 by the author.