やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

「C・C++からRubyへ」を読む

 多すぎる。気になったところだけピックアップする。

情報源

C/C++との違い

  • Rubyコードの実行時間はずっと遅い
  • RubyC++よりもずっとずっとシンプルな言語です
  • Rubyはきっとあなたのことを甘やかす

Cとの類似

  • Rubyには++--がない
  • constのような特別なキーワードがない
  • rimanの代わり

C++との類似

  • <<はリストに要素を追加する
  • ->は使いません
  • 継承は:でなく<です
  • RubymoduleC++namespaceです
  • 例外の作法は一緒ですがキーワードが違います

Cとの違い

  • コンパイル、メイク、リンク不要アセンブリコードに変換できない
  • 色々ない。ヘッダファイル、マクロ、プリプロセッサ(#define等)、キャスト、ポインタ、typedefsizeofがない
  • メモリ操作不要(GCまかせ)
  • メソッドの引数は値渡し。参照渡しでない
  • #include "foo"でなくrequire 'foo'
  • 行末セミコロン不要
  • 括弧不要
  • char型はない
  • すべて式

C++との違い

  • 明示的に参照を扱うことはない
  • 強い型付けがされますが、それは動的に行われます
  • コンストラクタはクラス名ではなくinitializeメソッド
  • 外部からメンバ変数に直接アクセスすることはない
  • 多重継承はない。代わりにMix-inを使う
  • いつでもクラスを変更できる
  • イテレータはない。代わりにeachなどのイテレータメソッドを使う
  • コンテナクラスはArrayHashだけ
  • ユニットテストのライブラリはRubyに標準添付されています

やってみる

 気になったものをいくつかピックアップしてirbで確かめてみる。

<<はリストに要素を追加する

> a = []
=> []
> a << 1
=> [1]
> a << 3
=> [1, 3]
> a << 5
=> [1, 3, 5]

 いいね! ビットシフト演算子なんてそんなに使わないし、これでいいよ。

RubymoduleC++namespaceです

module MyMod
  class MyClass
    def my_method; puts 'あはは'; end
  end
end
MyMod.MyClass.new.my_method
(irb):45:in `<main>': undefined method `MyClass' for MyMod:Module (NoMethodError)
Did you mean?  class

 は? 怒られたんですけど。

MyMod::MyClass.new.my_method

 ああそういうこと。.でなく::なのね。モジュールのときは子参照に::を使えと。たしかC++ではクラスメソッドを参照するときに::を使っていたような気がする。Rubyではモジュールのとき::を使うのね。めんどくせ。.で統一すればいいじゃん。2文字なのもいただけない。長いよ。

例外の作法は一緒ですがキーワードが違います

 そのキーワードとやらを教えて欲しかった。ググったところ以下だった。

begin 
  raise # エラーを発生させます。
rescue => e
  p e #=> RuntimeError
ensure 
  p "絶対実行"
end

 うわぁ、beginとか。嫌だなぁ。なんかBASIC言語みたいでモッサリ感ただようというか。レガシー臭というか加齢臭というか。時代においていかれた古代の遺物的な。骨董品的な。あまりに汎用性が高すぎて{}以上の意味がパッと見でわからない。beginというキーワードをみると、何かをおっぱじめるつもりなのはわかるが、それ以外なにもわからないから不安になる。最初に目に入る語としてどうなの。字数も多い。

 また新しい英語を覚えなきゃいけないじゃん。try-catch-finallyにしてほしかったわ。

英語 カタカナ 意味 他言語
begin ビギン 始める try
end エンド 終わる (なし)
rescue レスキュー 救助する catch,except
raise レイズ (上に高く)持ち上げる throw
ensure インシュア 保証する finally

 rescueensureが見慣れない。

 はたして読みからスペルを再現できるのか。rescueensureは書いて覚えなきゃならん。でもensureは書く機会が少なそう。beginは一時期、非同期処理とかで書いていたので覚えたが。

 raisePythonと同じなのでいい。

 => eってやつがキモチワルイ。アロー演算子は使わないし、無名関数はProcRubyが、ここにきて=>とかいう謎記号を使ってくるとか。いやもうrescue eとかでいいだろ。せめてrescue (e)とかさぁ。メソッド呼出でさえ()省略できるのに……。なぜここにきて=>なんぞが必要になるの? なぜヘンな記号を使おうとするの? そんなことするんだったらbegin,end{}にしてくれよ。いらないところで無駄に記号を使うのがカッコいいとか思ってない? やめて。

 今までRuby書いててすごく気持ちよかったけど、この例外処理だけは最悪。Pythontry,except,else,finally,raiseのほうがずっとわかりやすいし、短く書ける。例外処理は大事なだけに痛い。

#include "foo"でなくrequire 'foo'

 requireで外部ファイルを取り込む。対象ファイルはライブラリ(.dll,.so)やrbスクリプト

 コードを用意する。

lib.rb

def method; puts 'これはlib.rbにあるmethodメソッドです。'; end

main.rb

#!/usr/bin/env ruby
require './lib.rb'
method

 実行する。

chmod +x main.rb
./main.rb

 OK! ちゃんと別ファイルのメソッドが参照された。

これはlib.rbにあるmethodメソッドです。

 インポート系って言語によって全然違うよね。超ややこしいんだよ。

キーワード 意味 言語
#include 含める C/C++
import 輸入する Python, JavaScript, Java
using を使用して C#
use 使う Rust
require 必須 Ruby, Node.js

 意味的にはimportがわかりやすいけれど、PythonJavaScriptのそれは使い方が難しいので嫌い。単語としてはuseが一番シンプルで短い。さすが後発言語。スマートだね。まあCargo.tomlとあわせて使わなきゃいけなかったりするけど。

 それにひきかえrequireって。長いし単語が難しい。あと必須って意味なのもひっかかる。でも相対パスで使えるのはいいね。Pythonはそこんところクソだから。組込のファイル名と重複して使えないことが稀によくある。Rubyはそんなことがなさそうで安心した。使いやすそう。

コンストラクタはクラス名ではなくinitializeメソッド

class C
  def initialize; puts '初期化'; end
end
> C.new
初期化

 finalizeはないの? ググったけど超古い情報しかなかった。

class C
  def initialize
    ObjectSpace.define_finalizer(self, C.finalize)
  end
  def C.finalize
    proc { puts "bar" } end
end
C.new
  • クラスメソッドにしないとダメ。selfが参照されつづけて解放されなくなるから
  • Procを返さないとダメ

 これは面倒すぎる。とくにprocで書かないとダメなのがウザい。ネストが深くなる。

多重継承はない。代わりにMix-inを使う

 Mix-inってなに。ググったら以下がわかりやすかった。

module Mod
    def method; puts 'モジュールのメソッドです。'; end
end
class X; include Mod; end
> X.new.method
モジュールのメソッドです。

 今回はとりあえずこれだけ知っていればいいかな。

 気になったのはモジュールの使い方。これってそんなに使うのかな? クラス間をまたぐ共通メソッドなんかを定義するときに使いそうだが。まあ1つしかできない継承だけでは厳しいし。そのときになったらまた考えよう。

ユニットテストのライブラリはRubyに標準添付されています

 情報がないのでググった。

 標準ライブラリを使って単体テストをしてみる。まずは適当にコードを書く。

lib.rb

def get_name
  'ytyaru'
end

 次に上記をテストするコードを書く。

test_lib.rb

#!/usr/bin/env ruby
require './lib.rb'
require 'test/unit'
class TestLib < Test::Unit::TestCase
  class << self
    def startup; p :_startup; end
    def shutdown; p :_shutdown; end
  end
  def setup; p :setup; end
  def teardown; p :teardown; end
  def cleanup; p :cleanup; end
  def test_get_name
    assert_equal('ytyaru', get_name)
  end
  def test_get_age
    assert_equal(100, get_name)
  end
end

 なお、assert_系メソッドは以下のInstance Method Summary項に一覧がある。

 テストを実行する。

chmod +x test_lib.rb
./test_lib.rb 

 結果は以下。

Loaded suite ./test_lib
Started
:_startup
:setup
:cleanup
:teardown
.:_shutdown

Finished in 0.005678889 seconds.
---------------------------------------------------------------------------------------------------------------------------------------
1 tests, 1 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
---------------------------------------------------------------------------------------------------------------------------------------
176.09 tests/s, 176.09 assertions/s

 ほかにも便利な記述がたくさんある。詳細は以下参照。

 くわしくはまた必要になったら勉強しよう。今は触りだけサクッとやるだけに留める。

 後日以下にコードを書いた。

所感

 なんか、Rubyの情報って古いのばっかりヒットするんですけど。2015〜2018年くらいな感じ。だいぶ下火なのかな。

対象環境

$ uname -a
Linux raspberrypi 5.10.52-v7l+ #1441 SMP Tue Aug 3 18:11:56 BST 2021 armv7l GNU/Linux