Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

「Head First Ruby」を読む(1)

ほぼ新入社員といっていい会社の後輩から、「Excelの先生を探しています」と相談された。最近は学生時代にしっかりCSを学んでいたり、趣味でプログラミングをやっている新人君も多いが、彼女はそういうバックグラウンドを持っていないタイプだ。うちの会社ではそういう人材は普通である。実際、ギークばかりを採ってもうちの仕事がはかどるってわけでもないのだ。

2022年になっても、Excelの需要は高い。バグの傾向分析をしたり、プロジェクトのメンバーリストを元に所属会社別の人数を数えたり、いかにも新入社員に回ってきそうな雑用というのはたくさんあるものだ。そういうものはだいたいがみんなExcelを使ってやっている。彼らはプログラミングというのは納品するためのアプリケーションをつくるためにやるもので、その他の日常の細かなことはExcelの関数でやるものだと考えている。どうしようもなくなるとExcel VBAを担ぎ出すが、それを「プログラミング」だとは思っていない。なぜなら、Excel VBAの抽象化されたRangeオブジェクトの操作は、彼らが思っているプログラミングとどうも似ていないからだ。

私は一時期かなりExcel VBAを書いたし(プロジェクトで雑用に使える許可されたプログラミング環境がそれしかない時代があったのだ)、WindowsのCOMは偉大な発明であり、あんな複雑なものがちゃんと動いて使えているのはマイクロソフトが成し遂げた一種の奇跡みたいなものだと思っているので、割と好きでもある。ただ、さすがにベースとなる言語であるVBAで文字列操作をするのは大分泣きそうだし、2022年にはもっとよい言語がたくさんあるのでアレで仕事をするのはだいぶキツい。

そのようなExcel VBAをプログラミングのバックグラウンドがない新入社員が最初のプログラミング環境として学ぶとそれは大混乱をするだろうし、不幸だろうと思う。もうちっと下地をつくってからであれば、Excel VBAのリファレンスも読みやすくなるに違いない。それに、バグの傾向分析をやるならちょっとしたスクリプト言語の方が何倍も楽ちんだ。というわけで、世界で一番学習しやすい言語であるRubyを真面目に教えてみることにした。

Rubyのことなら内部の仕組みまで含めてある程度わかっている自信があるのだが、2022年の新入社員にRubyを学ぶと何に困難を覚えるのかはさっぱりわからない。私が教材を作るよりは、定評のある本を選ぼう。幸い、彼女は英語に不自由しないので(最近の新人君は間違いなく我々より優秀だ)、会社が金を払ってくれているOreilly Safariで読み放題の中から「Head First Ruby」をやってみることにした。「Head First xxx」シリーズは翻訳をいくつか読んだことがあって信頼がおけるブランドだし、Amazonの★も4以上だ。きっと初心者をはじめてのプログラミングの世界に誘ってくれるに違いない。たぶんな。まあ、ちょっと今年はトラブルプロジェクトに巻き込まれていて、教材を作る時間がないんだ。

というわけで、これを週に1章ずつやるぞということにした。これが新人君にとってキツいのかどうかもわからん。やってみながら考える。電子書籍はページ数がないから分量もよくわからないんだなあ。それにしたって、自分も一応読んでおく必要があるので、いまから1章を読む。そして、ハンズアウト部分をやった結果をこの後に貼る。ただ、まあ、1章は見るべきものはなんもないかな(笑)

Get Ruby

> ruby -v
ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]
> ruby
^Cruby: Interrupt

うん、Rubyインタプリタはちゃんといる。本ではバージョンが2.0.0だ。結構古いぞ。

Use Ruby

> echo 'puts "hello world"' > hello.rb
> cat hello.rb
───────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────
       │ File: hello.rb
───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   1   │ puts "hello world"
───────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────
> ruby hello.rb 
hello world

伝統を重んじている。

Use Ruby - interactively

irbは大事だ。

> irb
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> "Hello".upcase
=> "HELLO"
irb(main):003:0> exit
> 

Math operations and comparisons

Rubyを電卓として使おう

> irb
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> 5.4 - 2.2
=> 3.2
irb(main):003:0> 3 * 4
=> 12
irb(main):004:0> 7 / 3.5
=> 2.0
irb(main):005:0> 2 ** 3
=> 8
irb(main):006:0> 4 < 6
=> true
irb(main):007:0> 4 > 6
=> false
irb(main):008:0> 2 + 2 == 5
=> false

Strings

文字列だ。

irb(main):009:0> "Hello"
=> "Hello"
irb(main):010:0> 'world'
=> "world"

Variables

+=はいきなり理解出来るものだろうか・・・?

irb(main):011:0> small = 8
=> 8
irb(main):012:0> medium = 12
=> 12
irb(main):014:0> small + medium
=> 20
irb(main):015:0> pie = "Lemon"
=> "Lemon"
irb(main):016:0> pie = 3.14
=> 3.14
irb(main):017:0> number = 3
=> 3
irb(main):018:0> number += 1
=> 4
irb(main):019:0> number
=> 4
irb(main):020:0> string = "ab"
=> "ab"
irb(main):021:0> string += "cd"
=> "abcd"
irb(main):022:0> string
=> "abcd"

Everything is an object!

Rubyを最初に学ぶと「すべてがオブジェクト!」と言われても、驚きがあるわけでもないよね

irb(main):023:0> "Hello".upcase
=> "HELLO"
irb(main):024:0> "Hello".reverse
=> "olleH"
irb(main):025:0> 42.even?
=> true
irb(main):026:0> -32.abs
=> 32

Calling a method on an object

EXERCISEがある。やっとこう

irb(main):001:0> 42 / 6
=> 7
irb(main):002:0> 5 > 4
=> true
irb(main):003:0> name = "Zaphod"
=> "Zaphod"
irb(main):004:0> number = -32
=> -32
irb(main):005:0> name.upcase
=> "ZAPHOD"
irb(main):006:0> number.abs
=> 32
irb(main):007:0> "Zaphod".upcase
=> "ZAPHOD"
irb(main):008:0> -32.abs
=> 32
irb(main):009:0> name.reverse
=> "dohpaZ"
irb(main):010:0> number += 10
=> -22
irb(main):011:0> name.upcase.reverse
=> "DOHPAZ"
irb(main):012:0> rand(25)
=> 0
irb(main):013:0> rand(25)
=> 9
irb(main):014:0> rand(25)
=> 6
irb(main):015:0> name.class
=> String
irb(main):016:0> number.class
=> Integer
irb(main):017:0> name * 3
=> "ZaphodZaphodZaphod"

これだけでも語るべきことはいくつかある気がする。

Rubyではほとんどすべてがメソッド呼び出しだ。

irb(main):018:0> 42./(6)
=> 7

変数への代入は違うけど。

irb(main):019:0> number.=(-32)
/Users/tambara/.rbenv/versions/3.1.2/lib/ruby/3.1.0/irb/workspace.rb:119:in `eval': (irb):19: syntax error, unexpected '=' (SyntaxError)                                        
number.=(-32)                                           
       ^                                                
        from /Users/tambara/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `load'
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `<main>'

まあ、難しいことは後で出てくるだろう・・・

Let’s build a game

伝統に則って、数当てゲームを作るようだ。ゲームの仕様を英語で読み取るのが難しいが・・・この8つを満たすプログラムをつくるぞと。

  1. 名前を入力してもらって、挨拶する
  2. 1から100までの数字をランダムで決める
  3. プレイヤーの挑戦回数を記録する。10回で当てなきゃいけない
  4. プレイヤーに数字を入力させる
  5. プレイヤーの入力した数字と正解を比較して、高いとか、低いとか伝える
  6. 一致してたら正解だと伝え、挑戦回数も教える
  7. 挑戦回数を使い果たしたら、正解の番号を教える
  8. 正解するか、挑戦回数を使い果たすまで繰り返す

伝統に則ってるなあ・・・

Running scripts

ここからはスクリプトを書かせる。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

input = gets

puts "#{input}さん、いらっしゃい"
> ruby get_number.rb 
伝統と格式の数当てゲームにようこそ
お名前をどうぞ: たんばりん
たんばりん
さん、いらっしゃい

いらんところに改行はいったー。chompしておかなければならないな。 まあ、それは後で。

Comment

コメントというものがあるねと。

"puts" and "print"

なんで2つあるのか不思議に思うかもしれない。

そして、なんでputsはレシーバーを取らないのだと書いてある。でもそれは2章でやるそうだ。

Method arguments

メソッドには引数をつけられる。 putsはいくつでも引数をつけられるのだ。

first line
=> nil                                           
irb(main):002:0> puts "second line", "third line", "fourth line"
second line
third line                                                                
fourth line                                                               
=> nil                                    

"gets"

後ろの改行も入っちゃうぜー

hoge
=> "hoge\n"    

Parentheses are optional on method calls

メソッドに括弧つけるか問題

irb(main):004:0> puts("one", "two")
one
two                                              
=> nil                                           
irb(main):005:0> puts "one", "two"
one
two                                              
=> nil                                           
irb(main):006:0> gets
hoge
=> "hoge\n"                                      
irb(main):007:0> gets()
hoge
=> "hoge\n"           

しかし、Rubyistはメソッドが引数を取らない場合には「断固として(adamant)」括弧はつけないのだと書いてある。うむ。

String interpolation

interpolateは「補間」らしいが、日本語にしてもわからない。るりまはこれを「式展開」と呼んでいる。

irb(main):008:0> puts "The answer is #{6 * 7}"
The answer is 42
=> nil             

シングルクォートでくくると展開されない。

irb(main):009:0> puts 'The answer is #{6 * 7}'
The answer is #{6 * 7}
=> nil                   

ここの節のコラムで

  1. 行末のセミコロンとかいらんの?
  2. mainメソッドとかいらんの?

という質問が取り上げられていて、答えは両方、「めんどくさいからいらん」である。うむ。

What’s in that string?

あ、さっき私がひっかかった入力してもらった名前の最後に改行が入ってる問題がここで取り上げられる

Inspecting objects with the “inspect” and “p” methods

pメソッドは私がRuby初心者のときに不思議に思ったものの1つ。なぜなら、当時(まだ20世紀の話だ)出たばっかりのバイブルでpの説明はなかなか出てこないのに、みんなruby-list(Rubyメーリングリスト)では使われまくりだったので。

というわけで、ここでpとセットでinspectも紹介される。良いね。

Escape sequences in strings

そして、pを説明したら、エスケープシーケンスの話もする必要があるということ。構成が考えられてる。 そして、シングルクォートとダブルクオートの文字列の使い分けが説明されている。

Calling “chomp” on the string object

ここでchompの説明がくると。

What methods are available on an object?

呼べるメソッドの確認をしてる。どんなオブジェクトにもmethodsメソッドがある。

「るりま」の場所はここで教えた方がいいかもしれない。

Generating a random number

次はランダムな数を得るところを作る。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

name = gets.chomp

puts "#{name}さん、いらっしゃい"

puts "---"

puts "1から100までの数字を用意します"
puts "当てられるかなー?"
target = rand(100) + 1

puts "target = #{target}"

実行するとこんな感じ

> ruby get_number.rb
伝統と格式の数当てゲームにようこそ
お名前をどうぞ: だるま
だるまさん、いらっしゃい
---
1から100までの数字を用意します
当てられるかなー?
target = 6

Converting to strings

文字列への変換について

irb(main):001:0> num_guesses = 0
=> 0
irb(main):002:0> remaining_guesses = 10 - num_guesses
=> 10
irb(main):003:0> puts remaining_guesses + " guesses left"
(irb):3:in `+': String can't be coerced into Integer (TypeError)
        from (irb):3:in `<main>'                                        
        from /Users/tambara/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/irb-1.4.1/exe/irb:11:in `<top (required)>'
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `load'  
        from /Users/tambara/.rbenv/versions/3.1.2/bin/irb:25:in `<main>'

エラーメッセージの意味がわかりづらいというのはあるだろうなと思う。これを強制的に変換してしまう言語もあるがRubyはそうしない。

Pythonはどうだっけな。

> python
Python 3.10.6 (main, Aug 14 2022, 16:05:52) [Clang 13.1.6 (clang-1316.0.21.2.5)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> num_guesses = 0
>>> remaining_guesses = 10 - num_guesses
>>> print(remaining_guesses + " guesses left")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

しないね。

というわけで、to_sする

irb(main):004:0> puts remaining_guesses.to_s + " guesses left"
10 guesses left
=> nil                

ただし、putsprintは渡したら変換してくれるから、任せたらいいのでは・・・あ、改行が・・・

irb(main):005:0> puts remaining_guesses, " guesses left"
10
 guesses left                                                           
=> nil                                           

こうする方法はある(もちろん2行にわけて書いていい)。

irb(main):009:0> print remaining_guesses; puts " guesses left"
10 guesses left
=> nil  

Ruby makes working with strings easy

つか、式展開つかえやと。違いない

irb(main):010:0> puts "#{remaining_guesses} guesses left"
10 guesses left
=> nil

Converting strings to numbers

入力された文字列を数字にする必要がある。to_i使うだけ。

Conditionals

if文が出てくる。truefalseが出てくる。&&||も出てくる。

というわけで、ここまでのところを含めてコードはどうなったかというとこんな感じと。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

name = gets.chomp

puts "#{name}さん、いらっしゃい"

puts "---"

puts "1から100までの数字を用意します"
puts "当てられるかなー?"
target = rand(100) + 1

num_guess = 0

guessed_it = false

puts "あなたには、#{10 - num_guess}回のチャンスが残っています"
print "正解だと思う数字をどうぞ: "
guess = gets.to_i

if guess < target
  puts "うーん、あなたの答えは小さすぎるようです"
elsif guess > target
  puts "うーん、あなたの答えは大きすぎるようです"
elsif guess == target
  puts "#{name}さん、大正解!"
  puts "あなたは#{num_guess}回目で正解出来ました"
  guessed_it = true
end

if not guessed_it
  puts "残念。あなたは正解出来ませんでした(答えは#{target}でした)"
end

実行する。まだ1回しか応えられないので、クソゲーである。

> ruby get_number.rb
伝統と格式の数当てゲームにようこそ                  
お名前をどうぞ: あらた                              
あらたさん、いらっしゃい                            
---                                                 
1から100までの数字を用意します                      
当てられるかなー?                                  
あなたには、10回のチャンスが残っています         
正解だと思う数字をどうぞ: 50                     
うーん、あなたの答えは大きすぎるようです
残念。あなたは正解出来ませんでした(答えは42でした)

The opposite of “if” is “unless”

if not guessed_it

はダサいのでunlessの説明。unlessはあんまりほかの言語で見たことがない気がするけど、思っているより頻繁に使う。特にガード節とかで。

Loops

whileの説明。untilも説明してくれるけど、unlessと違ってこっちはまず書かない。

しかし、この例では使うのである。おおお。

というわけで、コードはこうなった。

# Get My Number Game
# Written by: Tambara

puts "伝統と格式の数当てゲームにようこそ"
print "お名前をどうぞ: "

name = gets.chomp

puts "#{name}さん、いらっしゃい"

puts "---"

puts "1から100までの数字を用意します"
puts "当てられるかなー?"
target = rand(100) + 1

num_guesses = 0

guessed_it = false

until num_guesses == 10 || guessed_it
  puts "あなたには、#{10 - num_guesses}回のチャンスが残っています"
  print "正解だと思う数字をどうぞ: "
  guess = gets.to_i

  num_guesses += 1

  if guess < target
    puts "うーん、あなたの答えは小さすぎるようです"
  elsif guess > target
    puts "うーん、あなたの答えは大きすぎるようです"
  elsif guess == target
    puts "#{name}さん、大正解!"
    puts "あなたは#{num_guesses}回目で正解出来ました"
    guessed_it = true
  end
end

unless guessed_it
  puts "残念。あなたは正解出来ませんでした(答えは#{target}でした)"
end

Let’s try running our game!

動かしてみようってことで、この章は終わり。

> ruby get_number.rb
伝統と格式の数当てゲームにようこそ
お名前をどうぞ: たんばりん
たんばりんさん、いらっしゃい
---
1から100までの数字を用意します
当てられるかなー?
あなたには、10回のチャンスが残っています
正解だと思う数字をどうぞ: 50
うーん、あなたの答えは大きすぎるようです
あなたには、9回のチャンスが残っています
正解だと思う数字をどうぞ: 30
うーん、あなたの答えは大きすぎるようです
あなたには、8回のチャンスが残っています
正解だと思う数字をどうぞ: 20
うーん、あなたの答えは小さすぎるようです
あなたには、7回のチャンスが残っています
正解だと思う数字をどうぞ: 25
うーん、あなたの答えは大きすぎるようです
あなたには、6回のチャンスが残っています
正解だと思う数字をどうぞ: 23
うーん、あなたの答えは大きすぎるようです
あなたには、5回のチャンスが残っています
正解だと思う数字をどうぞ: 22
たんばりんさん、大正解!
あなたは6回目で正解出来ました