やってみる

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

「20分ではじめるRuby」をやってみた

 公式ドキュメントのやつ。1時間はかかった。

成果物

情報源

REPL

 端末を起動してirbコマンドを実行する。

irb

 Ruby用REPLが起動する。

irb(main):001:0> 

Hello World

irb(main):001:0> "Hello World"
=> "Hello World"
irb(main):002:0> 'Hello World'
=> "Hello World"

 クォーテーションはシングルでもダブルでも良さそう。違いはあるのだろうか。

 でもクォートをとったらエラーになった。

irb(main):003:0> Hello World
(irb):3:in `<main>': uninitialized constant World (NameError)
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
   from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `load'
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `<main>'

 Worldは初期化されていない語ですよ。ってことかな?

 え、じゃあHelloは初期化されているの? ていうか初期化ってなに? たぶん変数宣言のことだろうが。

 エラーメッセージは微妙だな。

puts

 puts関数。Ruby的にこれは関数なのか? それともメソッド? あるいはマクロ定義?

irb(main):005:0> puts "Hello World"
Hello World
=> nil
irb(main):006:0> puts("Hello World")
Hello World
=> nil

 関数のカッコは省略できる。試してみる! (ブラウザから)で学んだ。昔のPython2系と同じ。Python3では省略できなくなったはずだが。まあどちらでも書けるほうがいい、のかな? 省略されたほうが見やすい。

 printとの違いも気になる。そのうち調べよう。

 nilはたしかC言語でいうNULLPythonでいうNoneと似たような概念だったはず。たぶんputs関数の戻り値がないことを意味しているのだろう。

計算

irb(main):007:0> 3+2
=> 5
irb(main):008:0> 3 + 2
=> 5
irb(main):009:0> 3*2
=> 6
irb(main):010:0> 3**2
=> 9
irb(main):011:0> Math.sqrt(9)
=> 3.0
irb(main):012:0> a = 3**2
=> 9
irb(main):013:0> b = 4**2
=> 16
irb(main):014:0> Math.sqrt(a+b)
=> 5.0

関数

定義

irb(main):020:1* def hi
irb(main):021:1*   puts "Hello World !!"
irb(main):022:0> end
=> :hi

 関数名を忘れてしまったら、以下のように怒られた。

irb(main):015:1* def
irb(main):016:1>   puts "Hello World!!"
irb(main):017:0> end
/home/pi/.anyenv/envs/rbenv/versions/3.0.2/lib/ruby/3.0.0/irb/workspace.rb:116:in `eval': (irb):16: syntax error, unexpected string literal, expecting ';' or '\n' (SyntaxError)
  puts "Hello World!!"
       ^
(irb):17: syntax error, unexpected `end', expecting end-of-input
   from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `load'
    from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `<main>'

 構文エラーなのはわかったけど「関数名がない」とはっきり原因を言ってほしい。このエラーメッセージだと全然わからん。;だの\nだのputs "Hello World!!"だの、すべて的外れ。

呼出

irb(main):023:0> hi
Hello World !!
=> nil

 カッコをつけても呼べる。

irb(main):024:0> hi()
Hello World !!
=> nil

引数

irb(main):025:1* def hi(name)
irb(main):026:1*   puts "Hello #{name} !"
irb(main):027:0> end
=> :hi

 テンプレート・リテラルの構文も判明した。"#{変数名}"らしい。

 呼び出してみる。

irb(main):028:0> hi "山田"
Hello 山田 !
=> nil

オプション引数

irb(main):034:1* def hi(name="World")
irb(main):035:1*   puts "Hello #{name.capitalize} !"
irb(main):036:0> end
=> :hi

 引数の初期値をWorldにした。

 ついでにテンプレート・リテラル構文? でメソッドを呼んでいる。capitalizeは先頭1文字目を大文字にするメソッドだと思われる。

 呼び出してみる。

irb(main):037:0> hi "yamada"
Hello Yamada !
=> nil

 カッコなしで呼び出せるのいいわ。タイプ数が少なくて楽。

class

 irbに以下をコピペする。

class Greeter
    def initialize(name = "World")
        @name = name
    end
    def say_hi
        puts "Hi #{@name}!"
    end
    def say_bye
        puts "Bye #{@name}, come back soon."
    end
end

 すると以下のようになる。

irb(main):038:1* class Greeter
irb(main):039:2*     def initialize(name = "World")
irb(main):040:2*         @name = name
irb(main):041:1*     end
irb(main):042:2*     def say_hi
irb(main):043:2*         puts "Hi #{@name}!"
irb(main):044:1*     end
irb(main):045:2*     def say_bye
irb(main):046:2*         puts "Bye #{@name}, come back soon."
irb(main):047:1*     end
irb(main):048:0> end
=> :say_bye

 @nameインスタンス変数。たぶん@C#でいうthisであり、Pythonでいうselfなのだろう。思えばPythonself地獄だったが、Rubyにはない。かわりにend地獄だが。

インスタンス生成

 文書にはオブジェクトと書いてあった。たしかRubyってすべてがオブジェクトなんだっけ? まあいいや。C/C++, C#, Javaと勉強してきた私にはインスタンスのほうがわかりやすい。とりあえずRubyの用語定義についてはそのうち調べよう。

irb(main):049:0> g = Greeter.new "Pat"
=> #<Greeter:0x009eaf48 @name="Pat">

メソッド呼出

irb(main):050:0> g.say_hi
Hi Pat!
=> nil
irb(main):051:0> g.say_bye
Bye Pat, come back soon.
=> nil

インスタンス変数は参照できない

irb(main):052:0> g.@name
/home/pi/.anyenv/envs/rbenv/versions/3.0.2/lib/ruby/3.0.0/irb/workspace.rb:116:in `eval': (irb):52: syntax error, unexpected instance variable (SyntaxError)
g.@name
  ^~~~~
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `load'
    from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `<main>'

 クラス定義したときに作った@name。でも生成したインスタンスからは参照できないんだってさ。

 カプセル化されているということか。すばらしい! ここはPythonと違う。いいね。単一責任を果たすコードが書けそう。

instance_methods

 クラスメソッドのinstance_methodsを使えば見える。とドキュメントには書いてあったが、@nameが見当たらない。まあ、だってメソッドでなく変数だもんね。

irb(main):054:0> Greeter.instance_methods
=> 
[:say_bye,
 :say_hi,
 :pretty_print_cycle,
 :pretty_print_inspect,
 :pretty_print_instance_variables,
 :pretty_print,
 :taint,
 :tainted?,
 :untaint,
 :untrust,
 :untrusted?,
 :trust,
 :methods,
 :singleton_methods,
 :protected_methods,
 :private_methods,
 :public_methods,
 :instance_variables,
 :instance_variable_get,
 :instance_variable_set,
 :instance_variable_defined?,
 :remove_instance_variable,
 :instance_of?,
 :kind_of?,
 :is_a?,
 :class,
 :frozen?,
 :then,
 :public_send,
 :method,
 :public_method,
 :singleton_method,
 :tap,
 :define_singleton_method,
 :extend,
 :clone,
 :yield_self,
 :to_enum,
 :enum_for,
 :<=>,
 :===,
 :=~,
 :!~,
 :nil?,
 :eql?,
 :respond_to?,
 :freeze,
 :inspect,
 :object_id,
 :send,
 :to_s,
 :pretty_inspect,
 :display,
 :hash,
 :singleton_class,
 :dup,
 :itself,
 :!,
 :==,
 :!=,
 :equal?,
 :instance_eval,
 :instance_exec,
 :__id__,
 :__send__]

 どうやらGreeterが継承しているすべてのクラスのメソッドも含めているようだ。たぶんすべてのクラスはベースとなるオブジェクトクラスを継承している、とかそういうことなんだろう。

 引数にfalseを渡せば、そのクラスで定義したものだけが出る。

irb(main):055:0> Greeter.instance_methods false
=> [:say_bye, :say_hi]

respond_to

 どのメソッドに反応するかを調べるらしい。

 末尾の?をつけ忘れたら以下のように怒られた。

irb(main):056:0> g.respond_to("name")
(irb):56:in `<main>': undefined method `respond_to' for #<Greeter:0x009eaf48 @name="Pat"> (NoMethodError)
Did you mean?  respond_to?
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
    from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `load'
  from /home/pi/.anyenv/envs/rbenv/versions/3.0.2/bin/irb:23:in `<main>'

 このエラーメッセージは対処がそのまま書いてあって素晴らしい。でも、そう思うんだったら?ついていなくてもそれを実行してくれたらいいのに。

 ていうか、?ってなんぞ? そういう名前の変数なの? それともNULL周りの処理をする特殊な構文とか? わからん。

irb(main):057:0> g.respond_to?("name")
=> false
irb(main):058:0> g.respond_to?("say_hi")
=> true
irb(main):059:0> g.respond_to?("say_bye")
=> true
irb(main):060:0> g.respond_to?("to_s")
=> true
irb(main):061:0> g.respond_to?("@name")
=> false

attr_accessor

 インスタンス変数を公開するときはattr_accessorを使う。

class Greeter
    attr_accessor :name
end

 あと、これで既存のGreeterクラスを変更できてしまったらしい。なにそれ怖い。間違って名前重複しちゃっていても気づかないとか、ありそう。

 :nameのように:を先頭につけたやつをシンボルというらしい。このへん、あんまり説明がないのでよくわからない。今はそういうものだとしておく。詳細はここを参照。

irb(main):068:1* class Greeter
irb(main):069:1*     attr_accessor :name
irb(main):070:0> end
=> [:name, :name=]

 インスタンス生成する。

irb(main):072:0> g = Greeter.new "Andy"
=> #<Greeter:0x00772920 @name="Andy">

 nameメソッドがある。

irb(main):073:0> g.respond_to?("name")
=> true
irb(main):074:0> g.name
=> "Andy"

 nameを参照しているメソッドでちゃんと反映されている。

irb(main):075:0> g.say_hi
Hi Andy!
=> nil

 nameを変更して、反映されることを確認する。

irb(main):076:0> g.name = "Betty"
=> "Betty"
irb(main):077:0> g.say_hi
Hi Betty!
=> nil
irb(main):078:0> g.name
=> "Betty"

 おそらくattr_accessorは、C++C#でいうpublicアクセス修飾子のようなものなのだろう。デフォルトではprivateだけど、attr_accessorにセットされたものはpublicになる感じか。

irbを終了する

quit

コードをファイルに書く

a.rb

#!/usr/bin/env ruby

class MegaGreeter
  attr_accessor :names

  # Create the object
  def initialize(names = "World")
    @names = names
  end

  # Say hi to everybody
  def say_hi
    if @names.nil?
      puts "..."
    elsif @names.respond_to?("each")
      # @names is a list of some kind, iterate!
      @names.each do |name|
        puts "Hello #{name}!"
      end
    else
      puts "Hello #{@names}!"
    end
  end

  # Say bye to everybody
  def say_bye
    if @names.nil?
      puts "..."
    elsif @names.respond_to?("join")
      # Join the list elements with commas
      puts "Goodbye #{@names.join(", ")}.  Come back soon!"
    else
      puts "Goodbye #{@names}.  Come back soon!"
    end
  end
end


if __FILE__ == $0
  mg = MegaGreeter.new
  mg.say_hi
  mg.say_bye

  # Change name to be "Zeke"
  mg.names = "Zeke"
  mg.say_hi
  mg.say_bye

  # Change the name to an array of names
  mg.names = ["Albert", "Brenda", "Charles",
              "Dave", "Engelbert"]
  mg.say_hi
  mg.say_bye

  # Change to nil
  mg.names = nil
  mg.say_hi
  mg.say_bye
end

 実行権限を付与する。

chmod +x a.rb

 実行する。

a.rb
Hello World!
Goodbye World.  Come back soon!
Hello Zeke!
Goodbye Zeke.  Come back soon!
Hello Albert!
Hello Brenda!
Hello Charles!
Hello Dave!
Hello Engelbert!
Goodbye Albert, Brenda, Charles, Dave, Engelbert.  Come back soon!
...
...

コメント

 #ではじまる行はコメントである。

# これはコメントです

if文

 if, else, elsif, end

 elsifが嫌だ。else ifでもelifでもなくelsif。これは初めてのパターンだ。キモチワルイ。

?

 末尾につく?が謎。

if @names.nil?
elsif @names.respond_to?("each")
メソッド名末尾 意味
? メソッド名の一部。慣用的に真偽値を返すメソッド名を示す。
! メソッド名の一部。慣用的に破壊的な作用をもつメソッドに使う。(渡された引数のうち配列の内容を書き換えるなど。!がない同メソッドはコピーされた別のインスタンスを返す)

 おそらく!は、C#でいうref修飾子。?Pythonでいうis_xxxxのような真偽値をreturnするときのメソッド名の命名規則だと思われる。

 Rubyはシンプルに表現できて素晴らしい。大抵のプログラミング言語!?の字を名前に使えない。正規表現でいうと[a-zA-Z_][a-zA-Z0-9_]*のはず。Rubyは違うようだ。

each

@names.each do |name|
  puts "Hello #{name}!"
end

 doend{}の意味らしい。{}に固有の名前をつけるのやめてほしい。忘れるんだよ。英語わかんないんだよ。{}にしてほしかった。

蛇足

 C#っぽく書くと以下。絶対こっちのほうが見やすい。

@names.each(name) => { 
  puts "Hello #{name}!"
}

 でも()省略したいから以下のほうが嬉しい。

@names.each => name { 
  puts "Hello #{name}!"
}

 一行のときは以下で書けたら嬉しい。

@names.each => name puts "Hello #{name}!"

動的型づけ

 namesの型はstringでもarrayでも動く。

# names = string
mg = MegaGreeter.new
mg.names = "Zeke"
mg.say_hi

# names = array
mg.names = ["Albert", "Brenda", "Charles",
            "Dave", "Engelbert"]
mg.say_hi

 nameの型によって処理内容を変えている。

def say_bye
  if @names.nil?
    puts "..."
  elsif @names.respond_to?("join")
    puts "Goodbye #{@names.join(", ")}.  Come back soon!"
  else
    puts "Goodbye #{@names}.  Come back soon!"
  end
end

 配列だったらjoinメソッドがあるはず。なのでそのメソッド存在判定をしている。elsif @names.respond_to?("join")で。

 型ではなく、メソッドが存在していることをもって判断する。ダッグ・タイピングというやつである。

エンドポイント

if __FILE__ == $0
end
キーワード 意味
__FILE__ ソースコードファイルパスを返す。
$0 実行時の最初の引数(ソースコードファイルパス)

 このif文はなくても実行できる。でもたぶん、これがないとimportしたときに実行されてしまうのだろう。Pythonのときでもそうだった。そういえばRubyにおけるimportはどうやってやるんだろう? モジュールなどの概念についても知らない。

 エンドポイントは、Pythonだったら以下のように書いていた。

if __name__ == '__main__':

所感

 20分では終わらなかった。1時間はかかった。いつもこういうとき「私って無能なんだ。ノロマなんだ……😢」とおもって悲しくなる。「すぐにできるよ!」とアピールして読者を増やそうという目論見は理解できるけど、メンタルケアまではやってくれない。そういうアフターフォローって大事よ? というわけで、私は私を慰めてみる。

 大丈夫だよ。こういうのって短い時間で終わるアピールしているだけだから! 彼らにとって好都合な小さい数字を出しているだけだよ。それに、色々調査せずにやっていたらきっとそのくらいで終わっていたって。だから大丈夫。決してノロマなわけじゃないよ。

 ……むなしい。初学の時点で才能のなさを突きつけられた感。

 まあでも、Pythonよりはずっと書いていて楽しかった。なんかPythonて書いててイライラするんだよね。たぶんRubyでもendが自動挿入されなかったらキレると思う。irbではendが自動挿入されたり、インデントをよしなにやってくれたから良かった。

 テキストエディタで書いたらやってくれないよね。本番はこれからだ。

対象環境

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