読者です 読者をやめる 読者になる 読者になる

shioponの日記

IT系とかそれ以外とか

RubyのRangeオブジェクトを知る

IT Ruby プログラミング

RubyのRangeオブジェクトを知る

Rubyには「ここからここまで!」というものを定義する範囲オブジェクトというものがありまして、これはRangeクラスというものを使って生成することができます。範囲オブジェクトを生成するには..とか...を使うのですが、例えば1から3を意味する1..3をeachで回す下のコードを実行してみると、1 2 3という結果が得られます。1から3ですからね。

(1..3).each do |n|
  p n #=> 1, 2, 3
end

範囲オブジェクトの生成自体はドットドットとかだけで可能なのですが、一応Rangeクラスをnewすることでも生成できます。下のコードは上のやつと全く同じ挙動をします。

Range.new(1, 3) do |n|
  p n #=> 1, 2, 3
end

..演算子と...演算子の違い

範囲オブジェクトを生成するためには「ここから、ここまで」を表すドット2つの..演算子、または「ここから、ここまで(ただし終端は含まない)」を表すドット3つの...演算子を使用します。ドットの数で終端を含むか含まないかを判断しているのですが、意味的には同じでも1..21...3と全く同一というわけではありません。ドットを並べた場合と、Rangeクラスをnewした場合での違いを見てみます。

(1..3)  # 1から3
(1...3) # 終端を含まないので、1から2

Range.new(1, 3, false) #=> 1..3
Range.new(1, 3, true)  #=> 1...3

ドットが1つ増えると、3つめの引数がtrueになります。この3つめの引数こそ、範囲オブジェクトが終端を含むかどうかのフラグであり、終端を含むならfalse、終端を含まないならtrueになるやつです。こうやってRubyは..演算子と...演算子を区別しています。

ちなみにexclude_end?というメソッドを使えば、範囲オブジェクトの最後のフラグがfalseかtrueか教えてくれます。

Range.new(1, 3, false).exclude_end? #=> false
Range.new(1, 3, true).exclude_end? #=> true

lastメソッド(罠)

lastメソッドは範囲オブジェクトの最後の数値を返してくれるインスタンスメソッドです。実はこいつ、Rangeクラスに与えられた3つめの引数(false, true)を考慮せず、2つめの引数に与えられた値をそのまま返すため、ちょっとややこしいです。
lastではこんな結果が出ても、eachとかでは問題なく想定通りの動作をしてくれるのでご心配なく。

(1..3).last #=> 3
(1...3).last #=> 3

じつは文字もいける

使用事例でいうと圧倒的に数値が多いと思いますが、実は文字でも大丈夫です。

(‘a’..’c’).each do |n|
  p n #=> “a”, ”b”, ”c”
end

Rubyのシンボルとはいったい

IT Ruby プログラミング

Rubyのシンボルとはいったい

:apple
こういう:で始まるやつをRubyではシンボルと呼んでいます。
任意の文字列と一対一で対応するオブジェクトであり、内部的には整数値として扱われています。文字列のように振る舞うこともできますが、実際は文字列とは違い、同値ならば必ず同一という特徴があります。全てのものがオブジェクト(インスタンス)であるRubyならではの概念なのではないでしょうか。

シンボルは唯一無二なのでメモリに優しい

リファレンスマニュアルによると、内部的にシンボルは

  • シンボルの情報を記録するテーブル
  • そのテーブルの要素を指し示すポインタ

によって実装されているそうです。同じ文字のシンボルを作っても、同じ要素を参照するだけなので、文字列を生成するよりもメモリに優しく高速なんですね。

# 文字列のオブジェクトIDは生成するたびに変わる
p ‘apple’.object_id #=> 70303057073840
p ‘apple’.object_id #=> 70303078914920
p ‘apple’.object_id #=> 70303057150220

# シンボルのオブジェクトIDは同じ文字列のかぎり同値
p :apple.object_id #=> 2774748
p :apple.object_id #=> 2774748
p :apple.object_id #=> 2774748

ちなみに、プログラム内で現在生成されているシンボルはSymbol.all_symbolsで見ることが出来ます。

irb(main):001:0> Symbol.all_symbols
=> [:!, :"\"", :"#", :"$", :%, :&, :"'", :"(", :")", :*, :+, :",", :-, :".", :/, :":", :";", :<, :"=", :>, :"?", :"@", :"[", :"\\", :"]", :^, :`, :"{", :|, :"}", :~, :"..", :"...", :+@, :-@, :**, :<=>, :<<, :>>, :<=, :>=, :==, :===, :!=, :=~, :!~, :[], :[]=, :"::", :"&&", :"||", :"&.", :freeze, :inspect, :intern, :object_id, :const_missing, :method_missing, :method_added, :singleton_method_added, :method_removed,…………

まだまだあります。思ってたよりずっと多いです。

つかいみち

値そのものに意味が必要ない、ハッシュのvalueと紐付けるためのキーとしての役割で使われることが多いです。シンボルは新しい文字列を生成する必要がないのでやや高速です。

fruit = {:apple => “りんご”, :grape => “ぶどう”}
fruit[:apple] #=> “りんご”

メソッドの引数としてシンボルを利用することもあります。シンボルを引数とする場合は、メソッドと引数の間のスペースや括弧が省略できます。

def like(symbol)
  @like = symbol
end

like:game #=> :game
p @like #=> :game

まとめ

まあ、軽いので使える場面では使っていきたいかんじですね

リファレンス

class Symbol (Ruby 2.3.0)

Gitのしくみ1

IT Git 開発環境

Gitの中身

書店にGitの使い方に対する本は山程あれど、Gitの仕組みに対する本は店頭で見ません。ので、仕組みを調べてみようと思いました。
だいたいここに書いてましたね

Git - 配管(Plumbing)と磁器(Porcelain)

.git

Gitでの操作したものはほとんどすべてがここに格納されます。リポジトリのバックアップやクローンをしたい場合はこのディレクトリをコピーするだけで事足ります。まずは.gitの中身を見てみましょう。git initを実行したデフォルトの状態です。

HEAD
config*
description
hooks/
info/
objects/
refs/

Gitオブジェクトは .git/objects ディレクトリに、 リファレンスは .git/refs ディレクトリに、 インデックスは .git/index ファイルに、

Gitオブジェクトの集合をGitオブジェクトデータベースといいます。

Gitオブジェクト

git initを行うとobjects内にはinfopackというサブディレクトリが設置されますが、他には何も作られていません。gitで管理するオブジェクトが増えると、これ以外にも様々なファイルが置かれることになります。

$ echo "hello" > hello.txt
$ git add .
$ git commit -m 'hello commit'

中身が空のtestファイルをコミットしたので、objectsフォルダ内にいくつかのオブジェクトが作成されました。 Gitオブジェクトには40文字のハッシュが付けられていて、SHA-1ハッシュのはじめの2文字がサブディレクトリの名前になり、残りの38文字がファイル名になります。

$ find .git/objects -type f
.git/objects/aa/a96ced2d9a1c8e72c56b253a0e2fe78393feb7
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
.git/objects/d2/dfbd6ce99ea2b831f55d10444724b221976539

git cat-file

git cat-fileコマンドを使うと、オブジェクトの種類や中身を調べることができます。リファレンスでは、「このコマンドは、Gitオブジェクトを調べるための万能ナイフのようなものです。」とまで言われています。引数に渡すハッシュは、先頭4文字で大丈夫です。
-t オプションでファイルのタイプを表示できます。
-p オプションでファイルの中身を表示できます。

treeオブジェクト

treeオブジェクトでは、ファイルの属性、ファイルの属性、blobオブジェクトへの参照(サブフォルダの場合はtreeオブジェクトへの参照)、ファイル名の情報を記録しています。

$ git cat-file -t aaa96
tree
$ git cat-file -p aaa96
100644 blob ce013625030ba8dba906f756967f9e9ca394464a    hello.txt

ここで出てきたce013625030ba8dba906f756967f9e9ca394464aは、blobオブジェクトのハッシュへの参照です。ツリーオブジェクトはblobオブジェクトへの参照が入っているんですね。

blobオブジェクト

blobオブジェクトでは、ファイルの中身そのものが保持されています。 -pオプションを使うとhello.txtの内容が表示されました。

$ git cat-file -t ce01
blob
$ git cat-file -p ce01
hello

commitオブジェクト

commitオブジェクトでは、treeへの参照、コミットしたユーザの情報、タイムスタンプとコミットメッセージが含まれています。2回目以降のコミットでは親コミットへの参照も含まれます。

$ git cat-file -t d2df
commit
$ git cat-file -p d2df
tree aaa96ced2d9a1c8e72c56b253a0e2fe78393feb7
author shiopon01 <shio.0323@gmail.com> 1465983182 +0900
committer shiopon01 <shio.0323@gmail.com> 1465983182 +0900

hello commit

blobオブジェクトの構造

Gitオブジェクトの40文字のIDはSHA-1で生成されますが、Gitオブジェクトの中身はZlibという可逆圧縮アルゴリズムが可能なフリーのライブラリを使用して圧縮されています。

SHA-1は不可逆なので、Zlibを展開すると以下の結果が手に入ります。

>>> import zlib
>>> f = open('.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a', 'rb')
>>> zlib.decompress(f.read())
'blob 6\x00hello\n'

blob 6\x00hello\nの見方は、blobが型、スペースを挟んで6がデータサイズ、\x00はヌル区切り、そしてhello\nがデータというかんじです。

展開されたblob 6\x00hello\nSHA-1で変換すると40文字のファイルIDになり、Zlibで変換するとオブジェクトの中身になります。

>>> import hashlib
>>> hashlib.sha1('blob 6\x00hello\n').hexdigest()
'ce013625030ba8dba906f756967f9e9ca394464a'

つづく

モチベーションあるとき続けます

RubyのModuleの使い方とはいったい

Ruby プログラミング IT

Module

Rubyにはオブジェクト指向で一般的なクラス以外にも、モジュールという概念が存在します。モジュールではクラスと同じように定数やメソッドをまとめたり、クラスに組み込んで多重継承を実現したり、クラスなどをまとめることで名前空間を提供するなど、いろいろな使い方ができます。
この記事では以下の内容について解説します。

  • 定数やメソッドをまとめる
  • クラスに組み込んで多重継承を実現する
  • 名前空間を提供する

リファレンスマニュアル(2.3.0) class Module (Ruby 2.3.0)

定数やメソッドをまとめる

モジュールではクラスと同様に、定数やメソッドをまとめる機能があります。

定数

モジュール内で定義した定数は、モジュール名を経由して呼び出すことが可能です。

module Mod
  Version = "2.3.0"
end

Mod::Version #=> "2.3.0"

インスタンスメソッド

インスタンスメソッドは定義するだけでは呼び出すことができません。module_functionメソッドを使い、メソッドをモジュール関数にすることで呼び出すことができるようになります。

module Mod
  def hello
    puts 'Hello'
  end

  module_function :hello
end

Mod.hello #=> "Hello"

クラスメソッド

モジュール内で宣言されたクラスメソッド(self. から宣言されるメソッド)はincludeやextendで拡張したクラスから呼び出すことができませんが、クラスと同様にモジュールから直接呼び出すことができます。

module Mod
  def self.hello
    puts 'Hello'
  end
end

Mod.hello #=> "Hello"

モジュール関数とクラスメソッドの優先度

同名のモジュール関数とクラスメソッドがあった場合、後に定義されたメソッドのほうが実行されます。

module Mod
  def hello
    puts 'module'
  end

  def self.hello
    puts 'class'
  end

  module_function :hello
end

Mod.hello #=> "module"
module Mod
  def hello
    puts 'module'
  end

  module_function :hello

  def self.hello
    puts 'class'
  end
end

Mod.hello #=> "class"

クラスに組み込んで多重継承を実現する(Mix-in)

Rubyのクラスは単一継承のみですが、モジュールをクラスに組み込むことで多重継承を可能にしています(ここだけ聞くとJavaで言うinterfaceのようですが、moduleではメソッドの機能を実装することができます)。クラスにモジュールをincludeしたりextendすることをMix-inと言い、Mix-inによってクラスの肥大化を防いだり、クラスをまたがった同じ処理を切り出して繰り返しを防ぐことができます。
モジュール関数、クラスメソッドは組み込むことができません。

include

includeでは、対象のクラスにincludeしたモジュールのメソッドがインスタンスメソッドとして組み込まれます。クラスからnewで作成したインスタンスで呼び出すことができます。これをクラスメソッドとして呼びだそうとすると、undefinde methodのエラーが出ます。

module Mod
  def hello
    puts 'Hello'
  end
end

class Obj
  include Mod
end

ins = Obj.new
ins.hello #=> "Hello"

extend

extendでは、対象のクラスにextendしたモジュールのメソッドがクラスメソッドとして組み込まれます。クラスメソッドなのでインスタンスでは呼び出すことができず、いつも通りクラスから直接呼び出します。こちらもインスタンスから呼びだそうとすればundefined methodです。

module Mod
  def hello
    puts 'Hello'
  end
end

class Obj
  extend Mod
end

Obj.hello #=> "Hello"

名前空間を提供する

名前空間

ruby gemなどでは、クラス名やメソッド名の重複による使用者からのモンキーパッチを防ぐためにgemの中身を全て1つのモジュールで梱包することが多いです。

module Name
  class Hoge
    def self.hello
      puts 'hello'
    end
  end
end

Name::Hoge.hello #=> "hello"

名前空間とMix-in

モジュールを重ねることで、MIx-inに使うモジュールを名前空間内で提供することができます。

module Name # 名前空間としてのモジュール
  module Mod  # Mix-inとしてのモジュール
    def hello
      puts 'Hello'
    end
  end
end

class Obj # Mix-in
  extend Name::Mod
end

Obj.hello #=> "Hello"