関西Ruby会議05でカヌレを配布してみた
去る 2013年8月31日、関西Ruby会議05 が開催された。
今年もスタッフとして参加し、おやつ係を担当した。
なぜおやつが必要なのか
1日中、会議に集中していては頭が回らなくなる。糖分が必要。
Minami.rb の勉強会では、毎回おやつの時間があって楽しい。美味しい。
そこで関西Ruby会議でも、おやつ配布することにした。
カヌレとは
フランスのボルドー女子修道院で古くから作られていた菓子。蜜蝋を入れることと、カヌレ型と呼ばれる小さな型で焼くことが特徴である。そもそもカヌレとは、「溝のついた」という意味である。外側は黒めの焼き色が付いており固く香ばしいが、内側はしっとりとして柔らかい食感を持つ。 ─── Wikipedia より
カヌレを 3個ずつ配布
全種類を一人で食べるのは多すぎる。しかし 1個だと物足りない。
多すぎず少なすぎず、3個がちょうどよいのではないか。
カヌレの種類を選べなくすることでワクワク感を演出。
どうしても違う味のカヌレが食べたければ、他の参加者と交換してもらう。
何が当たったか見せ合ったり、トレードしたりで、コミュニケーションの助けにもなったのではないか。
80人余りの参加者がカヌレの袋を除き込んでニコニコしている光景が、個人的にはとても楽しかった。
反省点
Ruby初級者向けレッスン 45回 ─例外─
第57回 Ruby/Rails勉強会@関西での初級者向けレッスンの資料を公開します。*1
エラーメッセージ
require 'open-uri' open 'http://github.com/rubykansai/workshops/wiki'
.../open-uri.rb:223:in `open_loop': redirection forbidden: http://github.com/rubykansai/workshops/wiki -> https://github.com/rubykansai/workshops/wiki (RuntimeError) from .../open-uri.rb:149:in `open_uri' from .../open-uri.rb:688:in `open' from .../open-uri.rb:34:in `open' from ex.rb:2:in `<main>'
- 上 2行が Ruby のコード。
- 実行すると、下のエラーメッセージが出た。
- Ruby のエラーメッセージは、いんちき英語でも読める。
- 「...(RuntimeError)」までが 1行
- 後は「from ...」から行末までが 1行
- 最初の行がエラーの出た場所。
- 最後の行が呼び出し元。
- 「in `<main>'」は実行を開始したオブジェクト。
- 下から 2行目「in `open'」は <main> から open メソッドが呼ばれたという意味。
- 各行がメソッドの呼び出しを表している。*2
- 結局、英語のメッセージを読む必要がある ><
- 「redirection forbidden」理由は分からんが http から https へのリダイレクトが禁止されてるらしい。
- たぶん url を https://... に直せば動く。
- 「redirection forbidden」理由は分からんが http から https へのリダイレクトが禁止されてるらしい。
- 最初の行の行末「(RuntimeError)」は RuntimeError 例外が発生したという意味。
例外いろいろ TypeError
"1" + 1 # ~> ex.rb:1:in `+': can't convert Fixnum into String (TypeError) # ~> from ex.rb:1:in `<main>'
- 下のコメントは実行した際のエラーメッセージ*3
- String の "1" に Fixnum の 1 は足せない。
例外いろいろ NoMethodError
1.to_sym # ~> ex.rb:1:in `<main>': undefined method `to_sym' for 1:Fixnum (NoMethodError)
- Fixnum の 1 に to_sym というメソッドはない。
例外いろいろ NameError
n.times{puts 'Ruby!'} # ~> ex.rb:1:in `<main>': undefined local variable or method `n' for main:Object (NameError)
- n という変数やメソッドはない。
例外いろいろ NoMethodError (再び)
n = ARGV.first.to_i unless ARGV.empty? n.times{puts 'Ruby!'} # ~> ex.rb:2:in `<main>': undefined method `times' for nil:NilClass (NoMethodError)
- Rubyには、他の言語のように NullPointerException のようなものはない。
例外いろいろ Errno::ENOENT
open('nothing.txt') # ~> ex.rb:1:in `initialize': No such file or directory - nothing.txt (Errno::ENOENT) # ~> from ex.rb:1:in `open' # ~> from ex.rb:1:in `<main>'
- 存在しないファイルを open しようとした。
- 「in `initialize'」は File.open メソッドが Fileクラスのインスタンスを生成しようとしている。
- Errno::Exxx の例外は OS の errno に対応した例外。
例外いろいろ SyntaxError
1 def even?(n) 2 if n % 2 == 0 3 true 4 else 5 false 6 end 7 8 puts even?(0) 9 puts even?(1) # ~> -:9: syntax error, unexpected end-of-input, expecting keyword_end
- if-else-end の end を忘れている。
Intelligence と Wisdom
雨が降ってきて…
- Intelligence
- 雨だ!
- Wisdom
- 傘を差そう
- 雨宿りしよう
- それが雨だと認識できる ─── Intelligence (知識)
- 濡れないためには、傘を差すか、雨宿りしないと ─── Wisdom (知恵)
例外も同じで、
- エラーを検知したものが例外を起こす (Intelligence)
- 対処方法を知っているものが例外を捕捉する (Wisdom)
例えば、アプリケーションがファイルを読もうとして、
- open できない!
- read できない!
File ライブラリはエラーを検知できる。(Intelligence)
- 既定値を使うから、読めなくてもよい。
- 別のファイルから読みなおしたい。
- etc...
これはアプリケーションの仕様。(Wisdom)
ライブラリには分かりようがない。
例外を捕捉する (コード例1)
files = %w[file.txt file1.txt file2.txt] files.each do |fn| begin open(fn, 'w'){|f| f.puts "Ruby!!"} break rescue => ex $stderr.puts "#{ex} (#{ex.class})" end end
- ファイルに "Ruby!!" と書き込みたい。
- ファイル名は file.txt
- もし書けなければ、代わりに file1.txt に書こう。
- それでも書けなければ、file2.txt に書こう。
- それでダメなら諦める。
コード解説
- ファイル名 "file.txt", "file1.txt", "file2.txt" という Array を用意。
- 各ファイル名に対して処理を繰り返す。─── files.each ...
やってみる
$ ls -Fdl file* drwxr-xr-x 2 mas staff 68 4 12 23:37 file.txt/ -r--r--r-- 1 mas staff 0 4 12 23:37 file1.txt # file.txt はディレクトリ # file1.txt は書き込み権なし $ ruby retry.rb Is a directory - file.txt (Errno::EISDIR) Permission denied - file1.txt (Errno::EACCES) $ ls -Fdl file* drwxr-xr-x 2 mas staff 68 4 12 23:37 file.txt/ -r--r--r-- 1 mas staff 0 4 12 23:37 file1.txt -rw-r--r-- 1 mas staff 7 4 12 23:39 file2.txt # file2.txt ができた! $ cat file2.txt Ruby!!
例外を捕捉する
begin 式1… [rescue [型1[, 型2]…][=> 変数][then] 式2…]… [else 式3…] [ensure 式4…] end
- [ ... ] 内は省略可能。
- begin - end で例外を捕捉する準備。
- 式1 が例外を捕捉したい処理 (複数書ける)
- rescue節で例外を捕捉。(rescue は複数書ける)
- 「型」に例外クラスを指定すると、特定の例外だけを捕捉できる。(型は複数書ける)
- 式2 が例外対処のための処理 (複数書ける)
- 発生した例外に対応する rescue節がなければ、捕捉できない。
- ensure節は begin - end のブロックを抜ける際の後始末。
- 例外が起きても、起きなくても実行される。
- 式4 が後始末の処理 (複数書ける)
- else節は、上記 式1 で例外が起きなかった際に実行される。
- rescue節で指定しなかった例外を捕捉する処理ではない。
- 式3 で起きた例外は、このブロックでは捕捉しない。
上記のコードを省略なしで書くと、3通りの実行パターンがある。
- 式1→式3→式4 (例外なし)
- 式1→式2→式4 (例外を捕捉)
- 式1→式4 (例外が発生するが、捕捉できない)
例外を捕捉する (コード例2)
require './factorial' def fact(n) n.factorial rescue ArgumentError "1以上の整数を指定してください" rescue NoMethodError "整数を指定してください" end fact 3 # => 6 fact 4 # => 24 fact 5 # => 120 fact 0 # => "1以上の整数を指定してください" fact 2.5 # => "整数を指定してください" fact "2" # => "整数を指定してください"
- ハチドリ本のサンプル
- メソッド定義の def - end にも rescue, else, ensure が書ける。*4
例外の種類
puts NoMethodError.ancestors # >> NoMethodError # >> NameError # >> StandardError # >> Exception # >> Object # >> Kernel # >> BasicObject
- 一覧はるりま参照。
- NoMethodError は NameError のサブクラス
- NameError は StandardError のサブクラス
- StandardError は Exception のサブクラス
- Exception は例外の基底クラス
rescue修飾子
require './factorial' 3.factorial rescue 0 # => 6 4.factorial rescue 0 # => 24 5.factorial rescue 0 # => 120 0.factorial rescue 0 # => 0 2.5.factorial rescue 0 # => 0 "2".factorial rescue 0 # => 0
- rescue の後置。
- まず rescue の左辺を評価。
- 例外が起きると rescue の右辺を評価。
- 例外が起きたら、結果は 0 でいい。という大雑把なコード。
- 捕捉する例外クラスを指定できない。
- 例外オブジェクトを指す変数を指定できない。
例外を起こす
raise "simple" # ~> ex.rb:1:in `<main>': simple (RuntimeError)
- 例外を起こすには Kernel#raise メソッドを使う。
- RuntimeError が発生する。
例外を起こす (クラス指定)
raise ArgumentError, "bad argument" # ~> ex.rb:1:in `<main>': bad argument (ArgumentError)
- 例外クラスを指定して raise できる。
例外を起こす (オブジェクト)
raise TypeError.new("can't convert...") # ~> ex.rb:1:in `<main>': can't convert... (TypeError)
- 例外オブジェクトを raise することができる。*5
例外を起こす (独自の例外クラス)
class MyError < StandardError; end raise MyError, 'original' # ~> ex.rb:3:in `<main>': original (MyError)
サンプルコード
- https://github.com/higaki/learn_ruby_kansai_57
- 演習問題 1, 2, 3 の回答例も上げてます。
スライド、コードに間違いがあれば、ご指摘ください。
解答例 ─ Ruby初級者向けレッスン 44回
第56回 Ruby/Rails勉強会@関西 での Ruby初級者向けレッスン 44回 の解答例 *1
演習問題 1
0 から 9 までの数値をもつ配列 a がある。a = (0..9).to_a a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
- 各要素を順番に表示しよう。
- 各要素を 2倍した値を持つ配列を作ろう。
- 全要素の合計値を計算しよう。
- スライドのおさらい。
- 改めてやってみると、意外とできないかも知れないよ。
# -*- coding: utf-8; -*- # 0 から 9 までの数値をもつ配列 a がある。 a = (0..9).to_a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 各要素を順番に表示しよう a.each{|i| puts i} # >> 0 # >> 1 # >> 2 # >> 3 # >> 4 # >> 5 # >> 6 # >> 7 # >> 8 # >> 9 # 各要素を 2倍した値を持つ配列を作ろう a.map{|i| i * 2} # => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] # 全要素の合計値を計算しよう a.inject{|s, i| s + i} # => 45 ## マニアが書くと a.inject(:+) # => 45
演習問題 2
0 から 9 までの数値をもつ配列 a がある。
- 奇数の要素だけを持つ配列を作ろう。
- ただし odd? メソッドは使用禁止。
- パズルだと思って、いろいろやってみよう。
# -*- coding: utf-8; -*- # 0 から 9 までの数値をもつ配列 a がある。 a = (0..9).to_a # => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 奇数の要素だけを持つ配列を作ろう a.select{|i| i.odd?} # => [1, 3, 5, 7, 9] # ただし odd? メソッドは使用禁止 # 2 で割った余りが 1 なら奇数 a.select{|i| i % 2 == 1} # => [1, 3, 5, 7, 9] # 偶数でなければ、それは奇数 a.select{|i| !i.even?} # => [1, 3, 5, 7, 9] # rubyist は true/false の否定を嫌う # reject は式が真の要素を捨て去る a.reject{|i| i.even?} # => [1, 3, 5, 7, 9] ## マニアが書くと a.reject(&:even?) # => [1, 3, 5, 7, 9]
マニアのコードに出てきた & は、スライドに登場した、ブロックの受け渡しに使う & とは少し違う。*2
演習問題 3
Enumerable#map を自作してみよう。module Enumerable def my_map …… end endただしEnumerable#map とEnumerable#map! は使用禁止。
会場には「mapが使えないなら collectを使えばいいじゃない」と言う人がいた。
こういうことですね。わかります。*3
module Enumerable def my_map &block collect(&block) end end a = [*0..4] # => [0, 1, 2, 3, 4] a.map{|i| i * 2} # => [0, 2, 4, 6, 8] a.my_map{|i| i * 2} # => [0, 2, 4, 6, 8] a.map # => #<Enumerator: [0, 1, 2, 3, 4]:map> a.my_map # => #<Enumerator: [0, 1, 2, 3, 4]:collect>
惜しい。
多くのブロック付きメソッドはブロックを渡さないと Enumerator を返す。
my_map の方は #
# -*- coding: utf-8; -*- # Enumerable#map を自作してみよう # ただし Enumerable#map と Enumerable#map! は使用禁止 module Enumerable def my_map unless block_given? # ブロックがもらえなかったら Enumerator を返す to_enum __callee__ else # 空の Array を用意し、 inject([]) do |result, item| # ブロックの評価結果を追加する result << yield(item) end end end end # 試してみる a = [*0..3] # => [0, 1, 2, 3] # map と my_map の比較 a.map{|i| i * 2} # => [0, 2, 4, 6] a.my_map{|i| i * 2} # => [0, 2, 4, 6] # Enumerator も比較 i = a.map # => #<Enumerator: [0, 1, 2, 3]:map> j = a.my_map # => #<Enumerator: [0, 1, 2, 3]:my_map> i.next # => 0 i.next # => 1 i.next # => 2 i.next # => 3 j.next # => 0 j.next # => 1 j.next # => 2 j.next # => 3 j.next # => # ~> -:40:in `next': iteration reached an end (StopIteration) # ~> from -:40:in `<main>'
詳しくは、時間がとれたら書きたい。*4
Ruby初級者向けレッスン 44回 ― ブロック ―
第56回 Ruby/Rails勉強会@関西での初級者向けレッスンのスライドを公開します。*1
繰り返し
a = [0, 1, 2] a.each do |i| puts i end a.each{|i| puts i} # >> 0 # >> 1 # >> 2
- ブロックの代表的な使い方は繰り返し処理。
- do と end で囲まれたもの、{ と } で囲まれたものがブロック。
- 上のコードと下のコードは同じ処理をしている。
- Array のように、たくさんのオブジェクトを持っていると、全てのオブジェクトに繰り返し同じ処理をしたいことがよくある。
- | と | で囲まれた変数 i が Array の各要素を順に指す。
便利な例
a = [0, 1, 2, 3] # => [0, 1, 2, 3] a.map{|i| i * i} # => [0, 1, 4, 9] a.select{|i| i.even?} # => [0, 2] a.inject{|s, i| s + i} # => 6 a.find{|i| i.odd?} # => 1 a.all?{|i| i.even?} # => false a.any?{|i| i.even?} # => true
- 初級者には、このスライドがおすすめ。
- 特に inject メソッド
ブロックを渡す
- メソッドには、ブロックをひとつ渡せる。
- ブロックをどう使うかは、メソッド次第。
- 繰り返し
- ハリウッドの原理
open('hello.txt') # => #<File:hello.txt> open('hello.txt'){|f| f.read} # => "こんにちは\n"
- ブロックの役割りは、繰り返しだけではない。
- ブロックの有無で動作を変えるメソッドがある。
ハリウッドの原理
# open('hello.txt'){|f| f.read} begin f = open('hello.txt') f.read ensure f.close unless f.nil? end
- 処理のスケルトンをメソッドで用意しておき、処理の一部をブロックで切り替える。
- ブロック付きの 1行で書いた open メソッドは、ブロックなしで書くと、だいたいこんな感じ。
- close の部分は f.close unless f.closed? かも。
値を受け取る
- ブロックは、値を受け取れる。(多重代入)
- 何を幾つ受け取れるかは、メソッド次第。
- 参考
- 第54回 Ruby/Rails勉強会@関西
- Ruby初級者向けレッスン 42回
- 繰り返しと多重代入
値を受け取る (2)
- 受け取るか受け取らないかは、ブロック次第。
2.times{puts 'こんにちは'} # >> こんにちは # >> こんにちは 2.times{|i| puts i} # >> 0 # >> 1
- 上の例は値を受け取っていない。
- しかし times メソッドは値を渡してくれる。(下の例)
- ブロックは必ずしも値を受け取る必要はない。
Hash の例
people = {matz: 47, dhh: 32} # => {:matz=>47, :dhh=>32} people.each{|person| p person} # >> [:matz, 47] # >> [:dhh, 32]
- key と value のペアを受け取れる。
- ふたつの値が、ひとつの Array オブジェクトに。
Hash の例 (2)
people = {matz: 47, dhh: 32} people.each do |name, age| p "#{name}(#{age})" end # >> "matz(47)" # >> "dhh(32)"
- ふたつの値を、それぞれ別の変数で受け取れる。
- | と | の間に , で区切って変数を列挙する。
each_cons の例
midosuji = ["梅田", "淀屋橋", "本町", "心斎橋", "なんば"] midosuji.each_cons(2){|path| p path} # >> ["梅田", "淀屋橋"] # >> ["淀屋橋", "本町"] # >> ["本町", "心斎橋"] # >> ["心斎橋", "なんば"]
- ふたつの値を、ひとつの変数で受け取ると Array オブジェクトになる。
each_cons の例 (2)
midosuji.each_cons(2) do |from, to| p "#{from} - #{to}" end # >> "梅田 - 淀屋橋" # >> "淀屋橋 - 本町" # >> "本町 - 心斎橋" # >> "心斎橋 - なんば"
- ふたつの値を、ふたつの変数で受け取る。
each_cons の例 (3)
a = [*0..3] # => [0, 1, 2, 3] a.each_cons(3){|i| p i} # >> [0, 1, 2] # >> [1, 2, 3] a.each_cons(3){|i, j| p [i, j]} # >> [0, 1] # >> [1, 2]
- 繰り返しの回数は、どちらも 2回。
- みっつの値を、ひとつの変数で受け取ると Array オブジェクトになる。(上)
- みっつの値を、ふたつの変数で受け取ると、みっつ目の値が受け取れない。(下)
おかしいな? と思ったら
p unknowns.first # >> [1, ["matz", 47]] unknowns.each do |id, (name, age)| id # => 1 name # => "matz" age # => 47 end
- 要素を、ひとつ取り出して見る。
- 例えば、こんなふうに表示されたら、値は幾つか?
- 答えは、ふたつ! *2
- 数値 (1)
- Array
- 中身は、文字列 ("matz') と数値 (47)
- 答えは、ふたつ! *2
- みっつの変数で受け取るには Array の中の Array を ( ) で表記する。
ブロックを受け取るメソッド
- こんな感じで呼びたい
monta{puts 'block!'} # >> block! # >> block! # >> 大切なことなので
- monta というメソッドを作る。
- ブロックを 2回評価して、
- 最後に '大切なことなので' と出力する。
ブロックを受け取る方法は、ふたつある。
ブロックを受け取る (2)
def monta &block block.call block.call puts '大切なことなので' end
- 引数でブロックを受け取る。
- 引数に & を付ける。
- call メソッドでブロックを評価する。
値を渡す
- monta メソッドの仕様を変更。
- ブロックに '大切なことなので' という文字列を渡す。
def monta yield '大切なことなので' yield '大切なことなので' end monta{|i| puts "#{i} block!"} # >> 大切なことなので block! # >> 大切なことなので block!
- yield メソッドに文字列を渡すだけの簡単なお仕事です。
値を渡す (2)
- また monta メソッドの仕様を変更。
- ブロックに '大切な', 'ことなので' という、ふたつの文字列を渡す。
def monta &block block.call '大切な', 'ことなので' block.call ['大切な', 'ことなので'] end monta{|i| puts "#{i} block!"} # >> 大切な block! # >> ["大切な", "ことなので"] block!
- block.call に、ふたつの文字列を渡すだけだと……
- ブロックがひとつの変数で待ち受けていると、ふたつ目の文字列が受け取ってもらえない。
- ふたつ以上の値を渡すときは、Array のオブジェクトにして渡す。
ブロックは Proc
block = Proc.new do |i, j| puts "#{i}#{j} block!" end monta &block # >> 大切なことなので block! # >> 大切なことなので block!
- あらかじめブロックだけ生成しておける。
- メソッドに渡す際には & を付ける。
Q&A
- Q1
- ブロックをふたつ渡せないの?
- A1
- & なしで普通の引数としてなら渡せます。
- Q2
- さっきから Emacs で Ruby のコード実行してるけど、それなに?
- A2
- るびきちさんが作られた rcodetools を使ってます。Ruby を起動して実行結果を # => の後に埋め込んでくれます。(コード補完もできます) コメントなので、結果の埋め込まれたコードは、そのまま保存・実行できます。
- Q3
- どんなキーバインドなの?
- A3
- えっ、それ重要?
*1:スライドだけ欲しい人は直接どうぞ http://higaki-it.jp/ruby/56/slide.pdf
*2:こんなデータ構造が Array で渡ってきたら、データ構造の設計が間違ってる。
第55回 Ruby/Rails勉強会@関西でも ust してみた
前回同様、第55回 Ruby/Rails勉強会@関西にて無人 ust を敢行。
前日の夜に手順のおさらいをすると、LiveShell のファームウェアが自動更新された。
これでダッシュボードとの同期ができるようになったみたい!
やったね。
これで勝つる!!
ところが、今回はネットワーク回線の問題で失敗したみたい。
Wi-Fi ルータには docomo の BF-01B を使ってる。*1
以前は e-mobile 使ってたけど、たまに電波の入らないことがあって買い替えた。
最初は調子良かったのが、だんだん回線品質が低下してきて、ついにはダッシュボードの同期もあやしくなってきた。
まだ半年ほど縛りはあるけど Xi とかに機種変更するか。
しかし冷静に考えてみると、docomo 3G の問題なのか、Wi-Fi の問題なのか、はっきりしない。
APが干渉なしで選べるチャネルのパターンでは、最大三つのAPしか置けないことが分かります([1、6、11]や、[2、7、12]、[3、8、13]、[4、9、14](日本のみ)のパターンしかありえない)。
無線LAN同士の干渉を考える ―― 無線にゃん
そこでひらめいた!
BF-01B から有線でネットに接続できなかったっけ?
つまり、Internet ―― docomo ―(3G)― BF-01B ―(有線)― LiveShell という構成にすれば、どんなに Wi-Fi が干渉しても無問題 (ぉぃ
次回は BF-01B からの有線でチャレンジしたい。
Minami.rb 第13回勉強会で受けた衝撃
半年ぶりくらいに Minami.rb の勉強会に参加した。
Rubyリファレンスマニュアル読み (Hashクラス編)
るりまをみんなで読もうというセッション。
Hash クラスと言えば、第54回 Ruby/Rails勉強会@関西 の初級者向けレッスンでやった。
ところが、何人かはレッスンを受講していたはずなのに、いちいち感心している。
分からなければ、グループワークで質問してるはずだ。ということは、何が分からないかも分からないままレッスン終了してたんだろう。 orz
まったく伝わってなかった。
自分の力不足を痛感した。
今後はグループワークでフォローできるようにしたい。
演習問題回答例
演習問題1
属性として身長と体重を追加しよう。体重は秘密にしよう。
仕様を決める。
- initialize で height, weight を渡す
- 省略時は nil
class Person @@variables = [:@name, :@born, :@height, :@weight] def initialize name, born = nil, height = nil, weight = nil @name, @born, @height, @weight = name, born, height, weight end attr_accessor :height attr_writer :weight def hash @@variables.map{|var| instance_variable_get(var)}.hash end def eql? o @@variables.all? do |var| instance_variable_get(var).eql? o.instance_variable_get(var) end end end
属性が増えたので、それに合わせて hash, eql? も変更した。
演習問題2
BMI を計算するメソッドを追加しよう。
体重[kg]
身長[m]
仕様を決める。
- height は Float で、単位は cm
- weight は Float で、単位は kg
- @height, @weight は Float でなくても、あるていど動くようにする
- 計算できない場合は NaN を返す
class Person def bmi Float(@weight) / (Float(@height) / 100.0) ** 2 rescue Float::NAN end end
演習問題3
Person#<=> を書き直そう。
その妥当な仕様は?p0 = Person.new('matz') p1 = Person.new('Matz', Time.local(1965, 4, 14)) p0 <=> p1 # => ?
仕様を決める。
class Person def <=> o return nil unless o.kind_of? Person @@variables.each do |var| lhs = instance_variable_get(var) cmp = lhs <=> o.instance_variable_get(var) next if cmp == 0 return cmp unless cmp.nil? return -1 if lhs.nil? return 1 end 0 end end
演習問題4
クラス Person には、いくつかのバグがある。 それを見つけ出して修正しよう。
バグを探す。
- @born に strftime メソッドがないと age で例外が発生する
- 計算できなければ nil を返す
- to_s が age を使用しているので、以下同文
- age が nil なら、@name のみとする
class Person def age (Time.now.strftime('%Y%m%d').to_i - @born.strftime('%Y%m%d').to_i) / 1_00_00 rescue nil end def to_s if a = age "#{name}(#{a})" else name end end end
こればバグか?
matz = Person.new('matz') matz.freeze matz.name # => "matz" matz.name.upcase! matz.name # => "MATZ"
グループワークのときに質問してみたら、みんなこれは放置しているそうだ。
あえて対応するなら、
class Person def name @name.dup end end matz.name.upcase! # => "MATZ" matz.name # => "matz"
間違いがあれば、指摘くださると助かります。