第55回 Ruby/Rails 勉強会@関西で初級者向けレッスンやってきた
初級者向けレッスンを担当したので、以下スライドを解説。*1 *2
クラスを作ってみよう
class Person; end obj = Person.new # => #<Person:0x10102718> obj.class # => Person Person.superclass # => Object
- Person という名前のクラスを作る。
- class から end までがクラス。
- クラス名は大文字で始める (キャメルケース)
属性を持たせてみよう
class Person def initialize name @name = name end end matz = Person.new('matz') # => #<Person:0x10138598 @name="matz">
- initialize は特別なメソッド
- new すると呼ばれる
- 引数 (name) をインスタンス変数 (@name) で記憶
- def から end がメソッド
属性にアクセスしてみよう
class Person attr_reader :name end matz.name # => "matz"
- インスタンス変数は外から見えない
- attr_writer
- セッター
- attr_reader
- ゲッター
- attr_accessor
- セッター&ゲッター
変数・定数のおさらい
- 最初の数文字を見れば区別できる
- クラス名は定数
属性を増やしてみよう
class Person def initialize name, born = nil @name, @born = name, born end attr_accessor :born end matz.methods.map(&:to_s).grep(/born/) # => ["born", "born="]
- initialize メソッドを変更
- オーバーロードではない
- 引数に既定値があれば省略できる
- attr_accessor で born メソッドと born= メソッドが生成された
属性を増やしてみよう (2)
- アクセスしてみる
matz.born = Time.local(1965, 4, 14) dhh = Person.new('dhh', Time.local(1979, 10, 15)) matz.born # => 1965-04-14 00:00:00 +0900 dhh.born # => 1979-10-15 00:00:00 +0900
- 代入のような表記で born= メソッドが呼ばれる
メソッドを作ってみよう
class Person def age (Time.now.strftime('%Y%m%d').to_i - @born.strftime('%Y%m%d').to_i) / 10000 end end matz.age # => 47 dhh.age # => 32
- メソッド名は小文字で始める (スネークケース)
- return は省略可能
- 最後に評価した式の値がメソッドの値になる
メソッドを上書きしてみよう
matz.to_s # => "#<Person:0x10138598>" class Person def to_s "#{@name}(#{age})" end end matz.to_s # => "matz(47)" dhh.to_s # => "dhh(32)"
- to_s はオブジェクトの文字列表現
- 未定義なら Object#to_s が実行される*3
インスタンスを比較してみると……
person = Marshal.load(Marshal.dump matz) person == dhh # => false person == matz # => false # おかしい
- 深いコピー
- Marshal.dump して Marshal.load する
- == はメソッド
- Person#== を定義しても良いが……
- ==, !=, >, <, >=, <= 別々に定義するの面倒
順序を決めよう
class Person include Comparable def <=> o @name <=> o.name end end person == matz # => true person == dhh # => false matz > dhh # => true
- <=> 演算子で順序を決定
- Comparable で ==, != , >, <, >=, <= を生成
Array#sort してみよう
- 順序が決まれば sort できる
people = [matz, dhh]
people.sort # => [dhh(32), matz(47)]
Array ときたら、次は ……
Hash のキーにしてみると……
値に入れてもおもしろくないので
h = {matz => "Ruby", dhh => "Rails"} h[matz] # => "Ruby" h[dhh] # => "Rails" key = Marshal.load(Marshal.dump matz) key == matz # => true h[key] # => nil # おかしい
- 深いコピーをしたオブジェクトをキーにすると
- 値が取り出せない!
hash 値を計算しよう
class Person def hash code = 17 code = 37 * code + @name.hash code = 37 * code + @born.hash end end matz.hash # => -22068619118 dhh.hash # => 14923733106
eql? を上書きしよう
- hash 値がぶつかっていないか調べる必要がある
- Hash クラスは eql? でキーが正しいか判断する
class Person def eql? o return false unless @name.eql? o.name return false unless @born.eql? o.born true end end key.eql? matz # => true key.eql? dhh # => false
- a.eql? b が真の場合 a.hash == b.hash であること
- そのように hash, eql? を定義する
Hash にアクセスしてみよう
h = {matz => "Ruby", dhh => "Rails"} h[matz] # => "Ruby" h[dhh] # => "Rails" h[key] # => "Ruby"
- 最初に作った h (Hash) は Person#hash を定義する前に作成したもの
- 格納位置がおかしいので作り直す*5
アクセス制御してみよう
オマケ
- public
- protected
- private
class Person protected :born end matz.born # ~> protected method `born' called for matz(47):Person (NoMethodError)
- protected なメソッドを呼ぶと例外が発生する
関西Ruby会議に関する重要なお知らせ
LT で話してきたのでご報告。
Emacs から rbenv を使う
gist を表示する練習を兼ねて、Emacs で rbenv を使う elisp を作ってみた。
ググってみると exec-path を設定する話がやたら出てくるけど、rbenv でインストールした複数の ruby を切り替えて使う例が出てこない。
以下のように環境変数に使いたい ruby のバージョンを指定すれば切り替えられる。
(setenv "RBENV_VERSION" "1.9.3-p194")
指定した ruby が rcodetools などで使えるようになっている。
RUBY_DESCRIPTION # => "ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin10.8.0]"
でも、いちいち環境変数設定するのが面倒すぎる。
(require rbenv) して、M-x rbenv すると、メッセージバッファでインストールした ruby を選択できる。
以下のように補完もできる。
Click <mouse-2> on a completion to select it. In this buffer, type RET to select the completion near point. Possible completions are: 1.8.7-p370 1.9.3-p125 1.9.3-p194 jruby-1.6.7.2 rbx-1.2.4
j TAB で jruby-1.6.7.2 を選択してみた。
RUBY_DESCRIPTION # => "jruby 1.6.7.2 (ruby-1.8.7-p357) (2012-05-01 26e08ba) (Java HotSpot(TM) 64-Bit Server VM 1.6.0_33) [darwin-x86_64-java]"
xmp すると、ちゃんと切り替わったことが確認できる。
いろいろ言いたいこともあるだろうが、自分にとってはこれで充分。
演習問題解答例
演習問題 1
既存の Array に変更を加える方法と、新しく Array を作る方法で [0, 1, 2, 3] という Array を作ろうという問題。
a = [] b = a b # => [] a.object_id # => 134275960 4.times{|i| a[i] = i} a # => [0, 1, 2, 3] a.object_id # => 134275960 b # => [0, 1, 2, 3]
- Array#[]= で Array オブジェクトを変更する例。
- object_id が等しいので既存のオブジェクトの変更。
- b の指すオブジェクトも変わってるんで、既存のオブジェクトの変更。
a = [] b = a b # => [] a << 0 << 1 << 2 << 3 b # => [0, 1, 2, 3]
- 「くく」で変更する例。
a = [] b = a b # => [] a[0, 4] = *0..3 b # => [0, 1, 2, 3]
- Array#[]= に位置とサイズを指定して代入する例。
a = [] b = a b # => [] a[0..3] = *0..3 b # => [0, 1, 2, 3]
- Array#[]= に Range を指定して代入する例。
後は新しく Array オブジェクトを作る例。
[*0..3] # => [0, 1, 2, 3] (0..3).to_a # => [0, 1, 2, 3] %w[0 1 2 3].map(&:to_i) # => [0, 1, 2, 3] "0123".split(//).map(&:to_i) # => [0, 1, 2, 3] Array.new(4){|i| i} # => [0, 1, 2, 3] 4.times.to_a # => [0, 1, 2, 3]
たぶん、みんなも、もっと変態な例を思いついたはず。
会場でピックアップできればよかったなぁ。
演習問題 2
文字列の単語を数える。文字を数える。
まずは単語
def wc(a) a.inject(Hash.new{|h, k| h[k] = 0}) do |s, i| s[i] += 1 s end end wc("No Ruby, No Life.".scan(/\p{Word}+/)) .sort_by{|w, n| -n} .each{|i| puts "%8d %s" % i.reverse} # >> 2 No # >> 1 Ruby # >> 1 Life
- 文字列は適当。
- String#scan で単語を抽出
- Array#sort_by で語数の降順
つづいて文字
sum = wc("No Ruby, No Life.".split(//)) (" ".."~").to_a.each_slice(8) do |line| puts line.map{|c| "%s(%2d)" % [c, sum[c]]}.join(' ') end # >> ( 3) !( 0) "( 0) #( 0) $( 0) %( 0) &( 0) '( 0) # >> (( 0) )( 0) *( 0) +( 0) ,( 1) -( 0) .( 1) /( 0) # >> 0( 0) 1( 0) 2( 0) 3( 0) 4( 0) 5( 0) 6( 0) 7( 0) # >> 8( 0) 9( 0) :( 0) ;( 0) <( 0) =( 0) >( 0) ?( 0) # >> @( 0) A( 0) B( 0) C( 0) D( 0) E( 0) F( 0) G( 0) # >> H( 0) I( 0) J( 0) K( 0) L( 1) M( 0) N( 2) O( 0) # >> P( 0) Q( 0) R( 1) S( 0) T( 0) U( 0) V( 0) W( 0) # >> X( 0) Y( 0) Z( 0) [( 0) \( 0) ]( 0) ^( 0) _( 0) # >> `( 0) a( 0) b( 1) c( 0) d( 0) e( 1) f( 1) g( 0) # >> h( 0) i( 1) j( 0) k( 0) l( 0) m( 0) n( 0) o( 2) # >> p( 0) q( 0) r( 0) s( 0) t( 0) u( 1) v( 0) w( 0) # >> x( 0) y( 1) z( 0) {( 0) |( 0) }( 0) ~( 0)
- String#split で文字に分解
- Enumerable#each_slice で、8文字ごとに表示*1
もっといいのがあったら教えて!
*1:ASCII のみだけど
第54回 Ruby/Rails勉強会@関西で ust してみた
勉強会で Ustream 中継するのが面倒で、なんとかならんもんかと思っていた。
そこで LiveShell ですよ。
【LiveShell】 PC不要で簡単、高画質なUstream/独自RTMPサーバ(H.263)対応
- 出版社/メーカー: Cerevo
- 発売日: 2013/08/14
- メディア: Camera
- クリック: 30回
- この商品を含むブログを見る
これを使えば有線/無線LAN経由で ust できるっぽい。
手持ちのカメラではコンポジット出力すると、液晶に何も映らなくなって操作できないのでビデオカメラも購入した。*1
パナソニック デジタルハイビジョンビデオカメラ V300 内蔵メモリー32GB パールホワイト HC-V300M-W
- 出版社/メーカー: パナソニック
- 発売日: 2012/02/01
- メディア: Camera
- クリック: 89回
- この商品を含むブログ (6件) を見る
初期設定が済めば、電源入れるだけで ust 中継が始まる。
配信停止とか録画とかは、ダッシュボード(専用のサイト) から行うのだが、これがなかなか言うこと聞いてくれない。
LiveShellは ust すると同時に自身のステータスを cerevo のサーバに送ってる (たぶん)
ダッシュボードと LiveShell の間には cerevo のサーバがあって、そこを経由して指令を出したり、ステータスを見たりできる (たぶん)
ところが、この経路のどこかで通信が途絶えてる。
どこが問題なのか?
どうすれば問題を切り分けられるのか?
さっぱり分からん。
ust のチャネルでは中継が見えているのに、ダッシュボードでは LiveShell がオフライン状態。
本番では録画やミュートなどダッシュボードからの操作は諦めて、たれ流しにした。
前日に練習したときにもステータスが反映されず、本体の電源オフ/オンやダッシュボードのログアウト/ログイン、設定変更など試行錯誤する間にネットワークに繋がらなくなって、初期設定をやりなおしたら本体とダッシュボードの同期が取れた。*2
本当ならダッシュボードからテロップ入れたりできるみたいなんだけど残念。期待してたのに残念。
なお、今回の ust は完全に放置してたんで、途中でピントがずれたり、カメラが横向いたりして、ぐだぐだだったらしい。これは失敗。
第54回 Ruby/Rails 勉強会@関西で初級者向けレッスンやってきた
初級者向けレッスンを担当したので、以下スライドを解説。*1
スライドだけ欲しい人は直接どうぞ。
- まずは Array のリテラルを紹介。
- 最初のは、Fixnum を 4つ持つ Array.
- 次のは、Fixnum, String, Array, Float, Symbol の 5つの要素を持つ Array.
Array とは (2)
a = [1, "two", [3, "3"], 4.0, :five] a[0] # => 1 a[-1] # => :five a[1] = "2nd" a[3, 2] # => [4.0, :five] a[1..-2] # => ["2nd", [3, "3"], 4.0] a[5] # => nil
- 添字は 0 オリジン
- 添字が負数なら後ろから数える
- 代入できる
- サイズを指定して取り出せる
- 範囲を指定して取り出せる
- 値がなければ nil
Array オブジェクトの作り方
["a", "b", "c"] # => ["a", "b", "c"] ("a".."c").to_a # => ["a", "b", "c"] [*"a".."c"] # => ["a", "b", "c"] %w[a b c] # => ["a", "b", "c"]Array オブジェクトの作り方 (2)
"No Ruby, No Life.".scan(/\w+/) # => ["No", "Ruby", "No", "Life"] "1,1,2,3,5,8".split(/,/) # => ["1", "1", "2", "3", "5", "8"]Hash とは
- 連想配列クラス
- 任意のオブジェクトを持つことができる
- 任意のオブジェクトをキーにできる
{:AAPL=>566.71, :GOOG=>605.23} {AAPL: 566.71, GOOG: 605.23} # => {:AAPL=>566.71, :GOOG=>605.23}
- つづいて Hash のリテラルを紹介。
- キーと値は => で区切る。
- 1.9 からは下の表記を使える。(よりデータの羅列に見える)
Hash とは (2)
h = {AAPL: 566.71, GOOG: 605.23} h[:AAPL] # => 566.71 h[:MSFT] = 31.16 h[:FB] # => nil
- Hash もアクセスには [ 角かっこ ] を使う
- 代入できる (値の存在しないキーに対しても可)
- 値がなければ nil
Hash オブジェクトの作り方
a = [:AAPL, 566.71, :GOOG, 605.23] Hash[*a] # => {:AAPL=>566.71, :GOOG=>605.23}順序がおかしくても気にしない
- Array から Hash を作ることが (たまに) ある。
- キーと値を羅列した Array を展開して Hash.[] で Hash オブジェクトを生成。
- ファイルから読み込んだデータを String#split して Hash を生成するような用途。
Hash[:AAPL, 566.71, :GOOG, 605.23] # => {:AAPL=>566.71, :GOOG=>605.23} Hash[:AAPL, 566.71, 605.23, :GOOG] # => {:AAPL=>566.71, 605.23=>:GOOG}要素数が奇数なら、Hash[:AAPL, 566.71, :GOOG, 605.23, :FB] # ~> odd number of arguments for Hash (ArgumentError)あまり使わないけど、使うとハマる Array の初期化Array の初期化
Array.new(4, 0) # => [0, 0, 0, 0] a = Array.new(3, "ruby") # => ["ruby", "ruby", "ruby"] a[0].upcase! # => "RUBY" a # => ["RUBY", "RUBY", "RUBY"]a = [] a[0] += 1 # ~> undefined method `+' for nil:NilClass (NoMethodError)全ての要素が同一のオブジェクトを指している。 では、どうするか? そこでブロックですよ!
- mutable なオブジェクトで初期化する際は要注意
ブロックで初期値を指定すると、別のオブジェクトになる。 レッスンではサッと流したけど以下が重要。Array の初期化 (2)
a = Array.new(3){"ruby"} # => ["ruby", "ruby", "ruby"] a[0].upcase! # => "RUBY" a # => ["RUBY", "ruby", "ruby"]次の例だと、せっかくブロック使っても無意味。
- ブロックが要素数だけ繰り返される
- ブロック内でオブジェクトを生成している
s = "ruby" a = Array.new(3){s} # => ["ruby", "ruby", "ruby"] a[0].upcase! # => "RUBY" a # => ["RUBY", "RUBY", "RUBY"]また、ブロックでは値が受け取れる。a = Array.new(3){|i| i.to_s} # => ["0", "1", "2"]Hash についても同じことがいえる。Hash のデフォルト値
hash = Hash.new(0.0) # => {} hash[:AAPL] # => 0.0 hash = Hash.new{|h, k| h[k] = ""} # => {} hash[:GOOG] # => "" hash[:IBM] # => ""※ キーの破壊
- Hash はサイズを指定しない
- サイズだけ指定されても、どのキーなんだよってことになる
- ブロックには Hash と キーが渡される
- そこでデフォルト値を生成する
キーの破壊
なので、キーを破壊すると、破壊前の格納位置に到達できなくなる (たぶん) ただし、String をキーにしても、Hash のキーを破壊することはできない。 Ruby は String を特別に扱っている
- Hash はキーの hash メソッドで値の格納位置を決める (たぶん)
- 格納位置の値が確かにそのキーの値であるか eql? メソッドで確認する (たぶん)
- String オブジェクトをコピーして
- freeze する
key = "ruby" # [あとでこわす] h = {} h[key] = "関西" h # => {"ruby"=>"関西"} h.first # => ["ruby", "関西"] h.first.first # => "ruby" h.first.first.eql? key # => true # 文字列は同じ h.first.first.equal? key # => false # 別のオブジェクト h.first.first.frozen? # => true # freeze されている key.upcase! # => "RUBY" h # => {"ruby"=>"関西"} # 破壊できない繰り返し each
[0, 1, 2].each{|i| puts i} [0, 1, 2].each do |i| puts i end # >> 0 # >> 1 # >> 2
- 繰り返しには each メソッドを使う
- ブロックは { 波かっこ } または do end で囲む
- ブロック内を繰り返す
- 繰り返しのたびに i が順番に要素を指す
Enumerable という便利なモジュールがありまして、繰り返し Enumerable
Array.ancestors # => [Array, Enumerable, Object, Kernel, BasicObject] Hash.ancestors # => [Hash, Enumerable, Object, Kernel, BasicObject]
- Enumerable
- 繰り返しを行なうクラスのための Mix-in
- クラスには each メソッドが必要
それぞれのメソッドは「るりま」を見てね。きりがないし。*5 でも、この後、しょーもない紙芝居やるよりも inject の動きとか見た方が良かったんじゃないか、とスライド作りながら思ってた。*6 inject を教科書通りに呼ぶと、こんな感じ。繰り返し Enumerable (2)
a = [2, 3, 5, 7] # => [2, 3, 5, 7] a.map{|i| i * i} # => [4, 9, 25, 49] a.select{|i| i.odd?} # => [3, 5, 7] a.inject{|s, i| s += i} # => 17 a.all?{|n| n.prime?} # => truea = [2, 3, 5, 7] # => [2, 3, 5, 7] a.inject(0){|s, i| s += i} # => 17 a.inject(0) do |s, i| puts "s = #{s} + #{i}" s += i end # >> s = 0 + 2 # >> s = 2 + 3 # >> s = 5 + 5 # >> s = 10 + 7各ループで s と i は上記の値を指している。 つづいてスライドの例a.inject{|s, i| s += i} # => 17 a.inject do |s, i| puts "s = #{s} + #{i}" s += i end # >> s = 2 + 3 # >> s = 5 + 5 # >> s = 10 + 7s の初期値が省略されると、s = a[0]; i = a[1] から繰り返しが始まる。 しかし、上級者になると、こんな書き方をする。a.inject(&:+) # => 17ブロックの引数は、代入だと思ってみる。繰り返しと多重代入
a = [[:matz, 47], [:dhh, 32]] a.size # => 2 a.each{|i| puts "#{i[0]}(#{i[1]})"} a.each{|name, age|puts "#{name}(#{age})"} # >> matz(47) # >> dhh(32)i = [:matz, 47] i # => [:matz, 47] name, age = [:matz, 47] name # => :matz age # => 47さらに無理のある例、これも代入だと繰り返しと多重代入 (2)
a = [[1, [:matz, 47]], [2, [:dhh, 32]]] a.size # => 2 a.each do |id, (name, age)| puts "#{id}: #{name}(#{age})" end # >> 1: matz(47) # >> 2: dhh(32)i = [1, [:matz, 47]] i # => [1, [:matz, 47]] id, (name, age) = [1, [:matz, 47]] id # => 1 name # => :matz age # => 47かっこがないと、id, name, age = [1, [:matz, 47]] id # => 1 name # => [:matz, 47] age # => nilHash では、みんな自然と多重代入してた繰り返しと多重代入 (3)
h = {matz: 47, dhh: 32} h.each{|i| puts "#{i[0]}(#{i[1]})"} h.each{|name, age|puts "#{name}(#{age})"} # >> matz(47) # >> dhh(32)h = {matz: 47, dhh: 32} h.each do |i| puts i.class p i end # >> Array # >> [:matz, 47] # >> Array # >> [:dhh, 32]Hash#each すると、キーと値のペアが Array で渡される。 要素の数と引数の数が合わないと、どうなるのか?*7# 引数が多い場合 i, j, k = [1, 2] i # => 1 j # => 2 k # => nil # 引数が足りない場合 i, j = [1, 2, 3] i # => 1 j # => 2 # 引数が足りなくても…… i, *j = [1, 2, 3] i # => 1 j # => [2, 3]ちょっと蘊蓄Array のコピー
a = [1, 2, 3] b = a a[0] = 0 a # => [0, 2, 3] b # => [0, 2, 3]
- 代入はコピーじゃない
- a が指す Array オブジェクトを b も指すようになった
Array のコピー (2)
a = ["a", "b", "c"] b = a.dup a[0] = "A" a # => ["A", "b", "c"] b # => ["a", "b", "c"]
- 浅いコピーなので注意!
Array のコピー (3)
a = ["a", "b", "c"] b = a.dup a[1].upcase! a # => ["a", "B", "c"] b # => ["a", "B", "c"]
- 自分が書き換えるオブジェクトは何なのか意識すること
- Array オブジェクト
- Array 要素のオブジェクト
今回、話さなかったこと
- each はメソッド
- 制御構造ではない
[].respond_to? :each # => true
- ブロックは Proc
block = Proc.new{|i| i * 2} [*0..4].map &block # => [0, 2, 4, 6, 8] block[5] # => 10
- 外部イテレータ
a = [2, 3, 5, 7] i = a.each i.next # => 2 i.next # => 3 i.next # => 5 i.next # => 7 i.next # ~> `next': iteration reached an end (StopIteration)演習問題は [あとでかく]まとめ
- Array と Hash の作り方・使い方
- 初期化・デフォルト値はブロックで
- 繰り返しはブロックで
- 浅いコピー・破壊に注意
関西Ruby会議04で初級者向けレッスンやってきた
2011年11月11日(金)・12日(土)に関西Ruby会議04が開催された。
「会議」という感じではないが、KOF (関西オープンソース2011)というイベントの会場で開催されるということもあり、新人Rubyistを勧誘 (洗脳?) すべく初級者向けレッスンも開催した。*1
レッスン内容
大クラス主義、オープンクラス、ダック・タイピングなんかも話したかったけど、時間の都合で割愛した。
課題
初心者が対象なので FizzBuzz を出題した。
解答例1
まず一般的な回答例
(1..30).each do |i| case when i % 15 == 0 then puts "FizzBuzz" when i % 5 == 0 then puts "Buzz" when i % 3 == 0 then puts "Fizz" else puts i end end
解答例2
TAをしてくれたアジャイルかわばたさんがワンライナーで書きたいと言って、その場で書いたもの。*2
(1..30).each{|i|puts i%15==0? "FizzBuzz": i%3==0? "Fizz": i%5==0? "Buzz" : i}
以下むりやり考えた逸般的な解答例。
解答例3
0 から 30 までの文字列の配列を用意して、3の倍数と 5の倍数を潰す。
a = [*"0".."30"] 3.step(30, 3){|i|a[i].sub!(/\d*$/, "Fizz")} 5.step(30, 5){|i|a[i].sub!(/\d*$/, "Buzz")} puts a[1..-1]
- "0".. なので添字と内容が一致する
- \d* なので数字がなくてもマッチ
- $ なので末尾にマッチ
- 1.. なので "0" は表示しない
解答例4
ググッて見つけたのをちょっと改造。
puts (1..30).map{|i|[[i, "Fizz"], ["Buzz", "FizzBuzz"]][i%5==0?1:0][i%3==0?1:0]}
5の倍数でないとき、あるときの配列に、3の倍数でないとき、あるときの配列が入っている。