Rubyってどうなの?ナナオです。
今回はあまり触ってこなかったRubyに入門してみようと思います。
きっかけとしてはある会社と面談したときに面白そうな会社だな~~と思ったのですが、Rubyをメインで使っている会社だったからです。
私のRubyのレベルは若干しかできないくらいのレベルなので、基礎はできるようにしておこうと思い、今回備忘録もかねて入門してみようと思います。
Rubyのセットアップ
まずはRubyのインストールを行っていきましょう。
バージョン管理はしっかりやっておきたいので、rbenvを使います。
私の開発環境はWindowsですが、anyenvを使いたかったのでWSL2で作業はしています。
また、すでにanyenvをダウンロード済みの環境ですが、anyenvのインストールは公式リポジトリを参照してください。
ではanyenvでrbenvをセットアップしていきます。
anyenv install rbenv
これで問題なくrbenvはインストールできます。
あとは適当に安定版の3.2.2
くらいをインストールします。
rbenv install 3.2.2
ただここでエラーが起きてしまいました。かなしいね。
==> Downloading openssl-3.1.4.tar.gz...
-> curl -q -fL -o openssl-3.1.4.tar.gz https://dqw8nmjcqpjn7.cloudfront.net/840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14.8M 100 14.8M 0 0 10.0M 0 0:00:01 0:00:01 --:--:-- 10.0M
==> Installing openssl-3.1.4...
-> ./config "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" "--openssldir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl/ssl" zlib-dynamic no-ssl3 shared
-> make -j 24
BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231225-4-g33168b3)
You can inspect the build directory at /tmp/ruby-build.20240115225829.19003.NXfhFA
See the full build log at /tmp/ruby-build.20240115225829.19003.log
ログを見てみると、zlibがインストールされていないのが原因だったようです。
インストールします。
sudo apt install libz-dev
再チャレンジしましたが、またエラーが出てしまいました。
==> Downloading openssl-3.1.4.tar.gz...
-> curl -q -fL -o openssl-3.1.4.tar.gz https://dqw8nmjcqpjn7.cloudfront.net/840af5366ab9b522bde525826be3ef0fb0af81c6a9ebd84caa600fea1731eee3
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14.8M 100 14.8M 0 0 10.1M 0 0:00:01 0:00:01 --:--:-- 10.1M
==> Installing openssl-3.1.4...
-> ./config "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" "--openssldir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl/ssl" zlib-dynamic no-ssl3 shared
-> make -j 24
-> make install_sw install_ssldirs
==> Installed openssl-3.1.4 to /home/nanao/.anyenv/envs/rbenv/versions/3.2.2
==> Downloading ruby-3.2.2.tar.gz...
-> curl -q -fL -o ruby-3.2.2.tar.gz https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.2.tar.gz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 19.5M 100 19.5M 0 0 9.8M 0 0:00:01 0:00:01 --:--:-- 9.8M
==> Installing ruby-3.2.2...
-> ./configure "--prefix=$HOME/.anyenv/envs/rbenv/versions/3.2.2" "--with-openssl-dir=$HOME/.anyenv/envs/rbenv/versions/3.2.2/openssl" --enable-shared --with-ext=openssl,psych,+
-> make -j 24
*** Following extensions are not compiled:
fiddle:
Could not be configured. It will not be installed.
/tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/fiddle/extconf.rb:73: missing libffi. Please install libffi or use --with-libffi-source-dir with libffi source location.
Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/fiddle/mkmf.log for more details.
openssl:
Could not be configured. It will not be installed.
/tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/openssl/extconf.rb:101: OpenSSL library could not be found. You might want to use --with-openssl-dir=<dir> option to specify the prefix where OpenSSL is installed.
Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/openssl/mkmf.log for more details.
psych:
Could not be configured. It will not be installed.
Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/psych/mkmf.log for more details.
readline:
Could not be configured. It will not be installed.
/tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/readline/extconf.rb:62: Neither readline nor libedit was found
Check /tmp/ruby-build.20240115230256.23220.MIDrdY/ruby-3.2.2/ext/readline/mkmf.log for more details.
BUILD FAILED (Ubuntu 22.04 on x86_64 using ruby-build 20231225-4-g33168b3)
You can inspect the build directory at /tmp/ruby-build.20240115230256.23220.MIDrdY
See the full build log at /tmp/ruby-build.20240115230256.23220.log
これはわかりやすいですね。
またいくつかのパッケージをインストールしてあげます。
また、パッケージ管理にはbrewを使用しているのですが、これのダウンロード方法についてはこちらを参照してください。
brew install openssl@1.1 libyaml
opensslは.zshrc
に設定の追記が必要です。
# .zshrc
# opensslの設定
export PATH="/home/linuxbrew/.linuxbrew/opt/openssl@1.1/bin:$PATH"
あとはreadlineもインストールしておきます。
sudo apt install libreadline-dev
再度インストールしてみたところ、成功しました!
デフォルトのバージョンを3.2.2にしておきます。
rbenv global 3.2.2
これで問題なくirbコマンドが使えるようになりました!
❯ irb
irb(main):001:0> puts 3
3
=> nil
Rubyの基礎
ここを参考にします。
適当にrubyのプロジェクトを作成していきます。
# プロジェクトの初期化
mkdir ruby-playground && cd ruby-playground
bundle init
# 適当に実行できるrubyファイルを作成
touch free_1.rb
ではまずは関数定義からやっていきましょう!
def hi
puts "Hello ruby!"
end
hi()
pythonと同じくdefを使い、終わりにはendを入れます。
実行は以下のように行います。
> ruby free_1.rb
Hello ruby!
想定通りに出力されましたね!
またpythonとは違い、引数がない関数呼び出しはカッコが必要ないようです。
面白いですね。
def hi
puts "Hello ruby!"
end
hi
毎回コマンド実行するのは面倒なので、VSCode上から実行できるように以下の拡張機能を入れておきましょう。
Code Runner - Visual Studio Marketplace
あとついでに以下のRubyにかかわる拡張機能も入れておきましょう。
Ruby - Visual Studio Marketplace
次に引数付きの関数を定義します。
def hi(name)
puts "Hello #{name}!"
end
hi("test")
Rubyでは、特殊記号を付けなくてもテンプレート文字列を使えるようです。
引数はpythonと同じくデフォルト値を設定できるようです。
def hi(name = "world")
puts "Hello #{name}!"
end
hi
さらに関数呼び出し時、引数が明確であればカッコなしでスペースで呼び出しもできるとあります!
Haskellっぽくてテンション上がります!!
hi "World"
さらに続けてクラスの定義をしてみます。
挨拶する人というクラスで、コンストラクタとメソッドを実装します。
class Greeter
# Rubyにおけるコンストラクタ
def initialize(name = "world")
@name = name
end
def say_hi
puts "Hi #{@name}"
end
def say_bye
puts "Hi #{@name}, come back soon"
end
end
def main
greeter = Greeter.new # Greeter.initializeでは呼び出せないので注意!
greeter.say_hi
greeter.say_bye
end
main
これを実行してみます。
Hi world
Hi world, come back soon
定義したとおりに出力しました。
initializeで登場している@
のついた変数はインスタンス変数であり、デフォルトでプライベートになっています。
@nameをパブリック変数にする場合、クラスにattr_accessor
を定義して明示しておく必要があります。
class Greeter
attr_accessor :name # コロン付けるの忘れずに!!
# ...中略...
end
def main
greeter = Greeter.new
greeter.name = "test"
puts greeter.name # 追加
end
これを実行した結果は以下の通りです。
test
変更後のインスタンス変数の値が出力されています。
ではさらにこのGreeterを発展させたクラスを実装します。
あまりにも一つのインスタンス変数が多重責務を負っているのでちょっとめまいがしてきますが、チュートリアルなのでこの辺はしょうがないですね。。
class MegaGreeter
attr_accessor :names
def initialize(names = "world")
@names = names
end
def say_hi
if @names.nil? # 返却される値がtrue/falseな関数はクエスチョンマークがつく
puts "..."
elsif @names.respond_to?("each")
@names.each do |name| # eachの返却値から反復処理してる
puts "Hello #{name}!"
end
else
puts "Hello #{name}!"
end
end
def say_bye
if @names.nil?
puts "..."
elsif @names.respond_to?("join") # join関数があるか判定する
puts "Goodbye #{@names.join(", ")}. Come back soon!"
else
puts "Goodbye #{@names}. Come back soon!"
end
end
end
def main
greeter = MegaGreeter.new
greeter.names = ["test_1", "test_2"] # 配列定義はpythonと一緒
greeter.say_hi
greeter.say_bye
end
# pythonで言う`if __name__ == "__main__":`みたいなやつ!
if __FILE__ == $0
main
end
面白い特徴についてはコメントしていますが、実際現場にこんなコードあったら大変ですね。
pythonとの比較
公式にありました!
上で書いたこと以外で気になったところでいうと
- 定数(値が変更されることを期待しない変数)をつくれます。
- 名前付けについての規約がいくつかあります。 たとえば、クラス名は大文字から始め、変数名は小文字で始めます。
- Pythonでアンダースコアの数によって実現しているアクセス制御は、 public、private、protectedを使って行います。
- 多重継承の代わりにMix-inを使います。
- importの代わりにrequireを使います。それ以外の使い方は同じです。
- (docstring の代わりに)クラスやメソッドの直前に書かれた複数行のコメントは、 ドキュメント生成に使われます。
- 一度定義した変数を、(Pythonでいうdelのように)未定義にする方法はありません。 変数をnilで設定すれば、変数に入っていた値をGCできるようにはできますが、 スコープが存在する限り変数自体はシンボルテーブルに残り続けます。
- yieldキーワードの振る舞いは異なります。 Pythonでは、関数呼び出しの外側のスコープへ実行結果を返します。 そのため、外側のコードは処理の再開について責任を負います。 Rubyでは、yieldは最後の引数として渡された別の関数が実行されます。 そし- て、実行が完了すると処理を再開します。
- Pythonがサポートしている無名関数はラムダ式のみですが、 Rubyはブロック、Procオブジェクト、ラムダ式といった種類の無名関数があります。
いくつか気になったところについて深堀ってみます。
Procオブジェクト、ブロック、ラムダ式とは
ちょっと複雑になってきますが、ブロックは{}
もしくはdo ~~ end
で囲われた範囲のことと定義されています。
【これで完璧!】Rubyのブロックの使い方、使い道まとめ (do~end {}) | 侍エンジニアブログ
またブロックに関して面白いコードを定義している記事がありました。
Rubyのブロック、Proc、Lambdaって何? #Ruby - Qiita
class Sample
define_method :output do
x + y
end
end
define_methodというのはクラスにメソッドを定義するための関数で、第一引数にメソッド名、第二引数に実処理を実装します。
[Ruby]define_methodを使えるようにしておきましょうか。 #Ruby - Qiita
つまり上のサンプルコードでは、def
を使わずにdefine_methodとブロックを使って関数定義をしているんですね。
クラスの定義の中に関数の実行を直接できるって部分は、Rubyの面白い特徴だと思いました。
ただブロックはオブジェクトではないので、単体で存在できるわけじゃないんですね。
そこで使用するのがProcです。
block = Proc.new {
|x, y| x + y
}
puts block.call(1, 2)
また、この時Proc.newはlambdaに置き換えることができます。
これがlambda式になります。
block = lambda {
|x, y| x + y
}
# もしくはこう
block = ->(x, y) { x + y }
puts block.call(1, 2)
yieldキーワードの振る舞いについて
pythonではジェネレータとして活躍しているyieldですが、rubyではどのように変わるのでしょうか?
簡単にまとめてくれているサイトがありました。
# 以下のような定義が
def hogehoge( x, &proc )
proc.call if block_given?
return x + 2
end
p hogehoge( 3 )
p hogehoge( 5 ){ p "foo" }
# yieldを使用することで簡潔になる!
def hogehoge( x )
yield if block_given?
return x + 2
end
p hogehoge( 3 )
p hogehoge( 5 ){ p "foo" }
つまりこの関数では、yieldを使うことで{}
で宣言しているp "foo"
の呼び出しを簡潔に行えるようになったわけですね。
pythonのジェネレータとはだいぶ役割が変わってくるなぁ。。
さらに実用的なコードを見つけました。
【Ruby】yieldとは?使いどころを具体例を用いて解説します - エンジニアの寝言
def process_page(url = nil)
get url if url # URLにアクセス
wait_loading # 画面描画の完了まち
check_error_page # 画面読み込み後、エラー画面が表示されていた場合のハンドリング
yield if block_given?
end
def login_page
process_page('https://engineer-negoto.com/') do
# ID, PWを入力しログインボタンをクリック
input_id
input_password
click_button('login')
end
end
def top_page
# 詳細画面へ遷移できるのボタンをクリックする
process_page { click_button('detail') }
end
def detail_page
# HTMLをダウンロードする
process_page { download_html }
end
この例では、process_page
関数の実行後に行ってほしい処理をブロックで指定しています。
つまり、rubyのyieldはpythonでいうwith句みたいなやつという認識でいいでしょう。
(間違っていたらコメントください)
まとめ
今回チュートリアルをやってみて、かなり面白いなと思いました。
railsなどの構築もまたやってみたいです!
それではまた。
参考
rbenvでのRubyインストール失敗対応(cannot load such file -- psych) #Ruby - Qiita