ためすう

第16章 オリジナル言語を作る: Domain-Specific Languages

2019-07-02

「Rubyによるデザインパターン」の読書メモです。

「内部」DSL

命令文をDSLの中に取り込んで、それをRubyプログラムとして解釈するアイディア

backup.pr

backup '/home/russ/documents'
backup '/home/russ/music', file_name('*.mp3') & file_name('*.wav')
backup '/home/russ/images', except(file_name('*.tmp'))
to '/external_drive/backups'

interval 60

test.rb

require 'finder'
require 'find'

class Expression
  def |(other)
    Or.new(self, other)
  end

  def &(other)
    And.new(self, other)
  end
end

class All < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p
    end
    results
  end
end

class And < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 & result2)
  end
end

class Not < Expression
  def initialize(expression)
    @expression = expression
  end

  def evaluate(dir)
    All.new.evaluate(dir) - @expression.evaluate(dir)
  end
end

class FileName < Expression
  def initialize(pattern)
    @pattern = pattern
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      name = File.basename(p)
      results << p if File.fnmatch(@pattern, name)
    end
    results
  end
end

def backup(dir, find_expression=All.new)
  puts "Backup called, source dir=#{dir} find expr=#{find_expression}"
end

def to(backup_directory)
  puts "To called, backup dir=#{backup_directory}"
end

def interval(minutes)
  puts "Interval called, interval = #{minutes} minutes"
end

def except(expression)
  Not.new(expression)
end

def file_name(pattern)
  FileName.new(pattern)
end

eval(File.read('backup.pr'))

まとめ

内部DSLの背景にある考え方は、とても簡単です。Rubyの構文ルールに合うようにあなたがDSLを定義します。そして、DSLで書かれた、やるべきことが記述されているプログラムに必要な基盤もあなたが定義します。

Ruby で defined? を使ってみる

2019-07-01

やったこと

defined? を使ってみます。

確認環境

$ ruby --version
ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-darwin17]

調査

$ irb
irb(main):001:0> defined? print
=> "method"
irb(main):002:0> defined? Hoge
=> nil
irb(main):003:0> class Hoge end
=> nil
irb(main):004:0> defined? Hoge
=> "constant"

存在しない Hoge クラスを引数に与えると、nil でしたが、

クラス作成後は constant が返ってきました。

参考

第15章 専用の言語で組み立てる: Interpreter

2019-07-01

「Rubyによるデザインパターン」の読書メモです。

Interpreter の例

require 'find'

class Expression
  def |(other)
    Or.new(self, other)
  end

  def &(other)
    And.new(self, other)
  end
end

class All < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p
    end
    results
  end
end

class FileName < Expression
  def initialize(pattern)
    @pattern = pattern
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      name = File.basename(p)
      results << p if File.fnmatch(@pattern, name)
    end
    results
  end
end

class Bigger < Expression
  def initialize(size)
    @size = size
  end

  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p if (File.size(p) > @size)
    end

    results
  end
end

class Writable < Expression
  def evaluate(dir)
    results = []
    Find.find(dir) do |p|
      next unless File.file?(p)
      results << p if (File.writable?(p))
    end
    results
  end
end

class Not < Expression
  def initialize(expression)
    @expression = expression
  end

  def evaluate(dir)
    All.new.evaluate(dir) - @expression.evaluate(dir)
  end
end

class Or < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 + result2).sort.uniq
  end
end

class And < Expression
  def initialize(expression1, expression2)
    @expression1 = expression1
    @expression2 = expression2
  end

  def evaluate(dir)
    result1 = @expression1.evaluate(dir)
    result2 = @expression2.evaluate(dir)
    (result1 & result2)
  end
end

ins = (Bigger.new(2000) & Not.new(Writable.new)) | FileName.new('*.mp3')
puts ins.evaluate('./')

まとめ

インタープリタを使うと処理速度が遅くなる傾向があり、また高速化することは困難なため、Interpreter パターンの使用は高い性能を必要としない領域に制限するのがよいでしょう。また Interpreter パターンは確実にかなりの基盤を必要とするため、複雑さが生じます。

train_test_split でデータを分割する

2019-06-30

やったこと

訓練データを学習データ、性能評価用のデータに分割します。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import sklearn
print(sklearn.__version__)
0.19.0

調査

from sklearn import datasets
iris = datasets.load_iris()
X = iris.data
y = iris.target

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
from sklearn import datasets
X.shape: (150, 4)
y.shape: (150,)
X_train.shape: (120, 4)
X_test.shape: (30, 4)
y_train.shape: (120,)
y_test.shape: (30,)

全体150個のデータのうち、20% の 30個がテストデータになっていることが分かります。

参考

第14章 オブジェクトを組み立てやすくする: Builder

2019-06-30

「Rubyによるデザインパターン」の読書メモです。

ビルダ (Builder) クラスが担当するのは、ある複雑なオブジェクトのコンポーネントの組み立てです。各ビルダには新しいオブジェクトに対してステップごとに構成を指示するインターフェースを持っています。

test.rb

class Computer
  attr_accessor :display
  attr_accessor :motherboard
  attr_reader :drives

  def initialize(display=:crt, motherboard=Motherboard.new, drives=[])
    @motherboard = motherboard
    @drives = drives
    @display = display
  end
end

class CPU
end

class BasicCPU < CPU
end

class TurboCPU < CPU
end

class Motherboard
  attr_accessor :cpu
  attr_accessor :memory_size

  def initialize(cpu=BasicCPU.new, memory_size=10000)
    @cpu = cpu
    @memroy_size = memory_size
  end
end

class Drive
  attr_reader :type # :hard_disk, :cd, :dvd
  attr_reader :size # 単位は MB
  attr_reader :writable

  def initialize(type, size, writable)
    @type = type
    @size = size
    @writable = writable
  end
end

class ComputerBuilder
  attr_reader :computer

  def initialize
    @computer = Computer.new
  end

  def turbo(has_turbo_cpu=true)
    @computer.motherboard.cpu = TurboCPU.new
  end

  def display=(display)
    @computer.display = display
  end

  def memory_size=(size_in_mb)
    @computer.motherboard.memory_size = size_in_mb
  end

  def add_cd(writer=false)
    @computer.drives << Drive.new(:cd, 760, writer)
  end

  def add_dvd(writer=false)
    @computer.drives << Drive.new(:cd, 760, writer)
  end

  def add_hard_disk(writer=false)
    @computer.drives << Drive.new(:dvd, 4700, writer)
  end
end


builder = ComputerBuilder.new
builder.turbo
builder.add_cd(true)
builder.add_dvd
builder.add_hard_disk(true)

p builder.computer

出力結果

$ ruby test.rb
#<Computer:0x00007fd3da8b3c78 @motherboard=#<Motherboard:0x00007fd3da8b3c50 @cpu=#<TurboCPU:0x00007fd3da8b3bd8>, @memroy_size=10000>, @drives=[#<Drive:0x00007fd3da8b3bb0 @type=:cd, @size=760, @writable=true>, #<Drive:0x00007fd3da8b3b88 @type=:cd, @size=760, @writable=false>, #<Drive:0x00007fd3da8b3b60 @type=:dvd, @size=4700, @writable=true>], @display=:crt>

まとめ

Builderパターンの考え方は、オブジェクトを作り出すのが難しい場合や、オブジェクトを構成するのに大量のコードを書かなければいけない場合に、それらの生成のためのコードを別のクラス、すなわちビルダに分離するというものです。

第13章 正しいクラスを選び出す: Factory part2

2019-06-30

「Rubyによるデザインパターン」の読書メモです。

Abstract Factory

Factory Method パターンがTemplate Method パターンをオブジェクトの作成に適用したのと同じように、Abstract Factory パターンは同じ問題にStrategyパターンを適用したのです。

アブストラクトファクトリで大事なことは、どの製品の型が一緒になるのかという知識をカプセル化することなのです。

class Duck
  def initialize(name)
    @name = name
  end

  def eat
    puts "アヒル #{@name} は食事中です。"
  end

  def speak
    puts "アヒル #{@name} はガーガー鳴いています。"
  end

  def sleep
    puts "アヒル #{@name} は静かに眠っています。"
  end
end

class Frog
  def initialize(name)
    @name = name
  end

  def eat
    puts "カエル #{@name} は食事中です。"
  end

  def speak
    puts "カエル #{@name} はゲロゲロっと鳴いています。"
  end

  def sleep
    puts "カエル #{@name} は眠りません。一晩中ゲロゲロ鳴いています。"
  end
end

class Algae
  def initialize(name)
    @name = name
  end

  def grow
    p "藻 #{@name} は日光を浴びて育ちます。"
  end
end

class WaterLily
  def initialize(name)
    @name = name
  end

  def grow
    p "睡蓮 #{@name} は浮きながら日光を浴びて育ちます。"
  end
end

class Tree
  def initialize(name)
    @name = name
  end

  def grow
    p "樹木 #{@name} が高く育っています。"
  end
end

class Tiger
  def initialize(name)
    @name = name
  end

  def eat
    puts "トラ #{@name} は食べたいものを何でも食べます。"
  end

  def speak
    puts "トラ #{@name} はガオーとほえています。"
  end

  def sleep
    puts "トラ #{@name} は眠くなったら眠ります。"
  end
end

class PondOrganismFactory
  def new_animal(name)
    Frog.new(name)
  end

  def new_plant(name)
    Algae.new(name)
  end
end

class JungleOrganismFactory
  def new_animal(name)
    Tiger.new(name)
  end

  def new_plant(name)
    Tree.new(name)
  end
end

class Habitat
  def initialize(number_animals, number_plants, organism_factory)
    @organism_factory = organism_factory

    @animals = []
    number_animals.times do |i|
      animal = @organism_factory.new_animal("動物#{i}")
      @animals << animal
    end

    @plants = []
    number_plants.times do |i|
      plant = @organism_factory.new_plant("植物#{i}")
      @plants << plant
    end
  end

  def simulate_one_day
    @plants.each {|plant| plant.grow}
    @animals.each {|animal| animal.speak}
    @animals.each {|animal| animal.eat}
    @animals.each {|animal| animal.sleep}
  end
end

jungle = Habitat.new(1, 4, JungleOrganismFactory.new)
jungle.simulate_one_day

pond = Habitat.new(2, 4, PondOrganismFactory.new)
pond.simulate_one_day

Factory の一段上のレイヤーで、組み合わせを担っています。

まとめ

「どのクラスを選ぶのか?」という問いに答えるための技術です。

pandas の DataFrame に変換する

2019-06-30

やったこと

pandas で 配列データ、辞書データを DataFrame に変換してみます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import pandas as pd
print(pd.__version__)
0.20.3

調査

from sklearn import datasets
iris = datasets.load_iris()
train = iris.data

print(type(train))

df = pd.DataFrame(train)
print(type(df))

data : ndarray (structured or homogeneous), Iterable, dict, or DataFrame Dict can contain Series, arrays, constants, or list-like objects

引数に渡すデータは ndarray だけでなくても良いようです。

リストを渡す

import pandas as pd
train = [2, 3]
print(type(train))

df = pd.DataFrame(train)
print(type(df))
<class 'list'>
<class 'pandas.core.frame.DataFrame'>

辞書を渡す

import pandas as pd
train = {'col1': [1, 2], 'col2': [3, 4]}
print(type(train))

df = pd.DataFrame(train)
print(type(df))
<class 'dict'>
<class 'pandas.core.frame.DataFrame'>

参考

pandas で任意の列を抽出する

2019-06-29

やったこと

pandas で任意の列を抽出してみます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import pandas
print(pd.__version__)
0.20.3

調査

アイリスデータの1 ~ 2列目を取得します

import pandas
import pandas as pd
from sklearn import datasets
iris = datasets.load_iris()
train = iris.data

df = pd.DataFrame(train)
df[[0, 1]]

また、下記でも同じものが取得できます。

df.loc[:, [0, 1]]

参考

NumPy の astype を使ってみる

2019-06-29

やったこと

NumPy で astype を使いデータをキャストしてみます。

確認環境

$ ipython --version
6.1.0
$ jupyter --version
4.3.0
$ python --version
Python 3.6.2 :: Anaconda custom (64-bit)
import numpy as np
np.__version__
'1.13.1'

調査

float -> int へ変換してみたいと思います。

x = np.array([1, 2, 2.5])
print(x)
print(x.dtype)
[ 1.   2.   2.5]
float64
x = x.astype(int)
print(x)
print(x.dtype)
[1 2 2]
int64

参考

第13章 正しいクラスを選び出す: Factory part1

2019-06-29

「Rubyによるデザインパターン」の読書メモです。

パラメータ化された Factory Method パターン

class Duck
  def initialize(name)
    @name = name
  end

  def eat
    puts "アヒル #{@name} は食事中です。"
  end

  def speak
    puts "アヒル #{@name} はガーガー鳴いています。"
  end

  def sleep
    puts "アヒル #{@name} は静かに眠っています。"
  end
end

class Frog
  def initialize(name)
    @name = name
  end

  def eat
    puts "カエル #{@name} は食事中です。"
  end

  def speak
    puts "カエル #{@name} はゲロゲロっと鳴いています。"
  end

  def sleep
    puts "カエル #{@name} は眠りません。一晩中ゲロゲロ鳴いています。"
  end
end

class Algae
  def initialize(name)
    @name = name
  end

  def grow
    p "藻 #{@name} は日光を浴びて育ちます。"
  end
end

class WaterLity
  def initialize(name)
    @name = name
  end

  def grow
    p "睡蓮 #{@name} は浮きながら日光を浴びて育ちます。"
  end
end

class Pond
  def initialize(number_animals, number_plants)
    @animals = []
    number_animals.times do |i|
      animal = new_organism(:animal, "動物#{i}")
      @animals << animal
    end

    @plants = []
    number_plants.times do |i|
      plant = new_organism(:plant, "植物#{i}")
      @plants << plant
    end
  end

  def simulate_one_day
    @plants.each {|plant| plant.grow}
    @animals.each {|animal| animal.speak}
    @animals.each {|animal| animal.eat}
    @animals.each {|animal| animal.sleep}
  end
end

class DuckWaterLilyPond < Pond
  def new_organism(type, name)
    if type == :animal
      Duck.new(name)
    elsif type == :plant
      WaterLily.new(name)
    else
      raise "Unknown"
    end
  end
end

class FrogAlgaePond < Pond
  def new_organism(type, name)
    if type == :animal
      Frog.new(name)
    elsif type == :plant
      Algae.new(name)
    else
      raise "Unknown"
    end
  end
end

pond = FrogAlgaePond.new(3, 2)
pond.simulate_one_day

具象クラスも外から渡すパターン

class Duck
  def initialize(name)
    @name = name
  end

  def eat
    puts "アヒル #{@name} は食事中です。"
  end

  def speak
    puts "アヒル #{@name} はガーガー鳴いています。"
  end

  def sleep
    puts "アヒル #{@name} は静かに眠っています。"
  end
end

class Frog
  def initialize(name)
    @name = name
  end

  def eat
    puts "カエル #{@name} は食事中です。"
  end

  def speak
    puts "カエル #{@name} はゲロゲロっと鳴いています。"
  end

  def sleep
    puts "カエル #{@name} は眠りません。一晩中ゲロゲロ鳴いています。"
  end
end

class Algae
  def initialize(name)
    @name = name
  end

  def grow
    p "藻 #{@name} は日光を浴びて育ちます。"
  end
end

class WaterLily
  def initialize(name)
    @name = name
  end

  def grow
    p "睡蓮 #{@name} は浮きながら日光を浴びて育ちます。"
  end
end

class Pond
  def initialize(number_animals, animal_class,
                 number_plants, plant_class)
    @animal_class = animal_class
    @plant_class = plant_class

    @animals = []
    number_animals.times do |i|
      animal = new_organism(:animal, "動物#{i}")
      @animals << animal
    end

    @plants = []
    number_plants.times do |i|
      plant = new_organism(:plant, "植物#{i}")
      @plants << plant
    end
  end

  def simulate_one_day
    @plants.each {|plant| plant.grow}
    @animals.each {|animal| animal.speak}
    @animals.each {|animal| animal.eat}
    @animals.each {|animal| animal.sleep}
  end

  def new_organism(type, name)
    if type == :animal
      @animal_class.new(name)
    elsif type == :plant
      @plant_class.new(name)
    else
      raise "Unknown"
    end
  end
end


pond = Pond.new(3, Duck, 2, WaterLily)
pond.simulate_one_day

animals と plants の組み合わせを呼び出し元から決めることができます。

この組み合わせも構造化するのは次の part2 で実施してみます。