Rubyでプラグイン機構を作る

rack/rack · GitHub のコード読んでてプラグイン(Rackの場合はバックエンドにどのサーバを使うか)の処理があったので取り出して書いてみた.

Rubyではクラスは定数なのでHandlerっていう名前空間の中の定数を探すだけでいい.

# sample1.rb
module Handler
  class Base
    def call
      run
    end

    def run
      raise NotImplementedError
    end
  end

  class PlugA < Base
    def run
      p "called in A"
    end
  end
end

# sample2.rb
module Handler
  class PlugB < Base
    def run
      p "called in B"
    end
  end
end

# runner.rb
require_relative './sample1.rb'
require_relative './sample2.rb'

def run(class_name)
  klass = Handler.const_get(class_name)
  klass.new.call
end

['PlugA', 'PlugB'].each do |e|
  run e
end

# => "called in A"
# => "called in B"

githubの分割バージョンがある

github.com

感想

研究室にyogiboがあってそれに乗っかりながらだらだら読んでて、夏休みの朝に再放送のアニメ見てる感じだった

RubyのStructの簡易版実装してみた

30分くらいでできる簡単な問題探しててmzpさんのやつを見つけてそれを解いてみてる.

これが動けばいい

Dog = MyStruct.new(:name, :age)
fred = Dog.new('fred', 5)
fred.age = 6
printf "name:%s age:%d\n", fred.name, fred.age

回答

class MyStruct
  def self.new(*names)
    Class.new do |_obj|
      def initialize(*values)
        @__values = values
      end

      names.each_with_index do |method, i|
        class_eval <<-EOS
          def #{method}
             @__values[#{i}]
          end

          def #{method}=(value)
             @__values[#{i}] = value
          end
        EOS
      end
    end
  end
end

参考

30分プログラムリスト - みずぴー日記

モナディックなパーザーコンビネータ作ってみた

タイトル通りで作ってみたました. もともと研究で違うことやってたはずなのに目的を見失ってなんか面白そうだしこれならゼミで話しても怒られなさそうという考えからこの頃ずっと勉強してた.

OCamlで実装しようかHaskellで実装しようか迷っててどっちでも良かったんだけど最終的にはHaskellで書いた. 最初はOCamlの勉強も少ししてて、とにかく本を買いたくなかったので以下のページを読んでた.

けど素直にHaskellでやったほうが楽そうという気持ちになり途中からHaskellに切り替えた. Haskellの文法は雰囲気がわかる程度だったのでなんとかなるだろうと思い特に何もしなかった. モナドは「モナドは単なる自己関手の圏におけるモノイド対象だよ。何か問題でも?」という感じだったので以下のページを読んでだ.

パーザーコンビネータググるといろんな人が実装しててるのでそれを参考にするといいかもしれない. こんなふうにParserの型が決まった時点で考えることはあんまりなくて型すごいって思いながら実装しいくとできたし型すごい.

type Result = Either String
type Parser v = StateT String Result v

PEGパーザにした気でいるんですがもしかしたらこれではいけないかもしれないので間違っていたらスマンという感じです.

import Control.Monad.State
import GHC.Base((<|>))
import Data.Char

type Result = Either String

type Parser v = StateT String Result v

runParser = runStateT

look :: Parser Char
look = do
  x:_ <- get
  return x

item :: Parser Char
item = do
  x:xs <- get
  put xs
  return x

satisfy :: (Char -> Bool) -> Parser Char
satisfy f = do
  a <- item
  if f a then return a else mzero

unsatisfy :: (Char -> Bool) -> Parser Char
unsatisfy f = satisfy (not . f)

cjoin :: Parser a -> Parser a -> Parser [a]
cjoin p1 p2 = do
  a <- p1
  b <- p2
  return [a, b]

select :: Parser a -> Parser a -> Parser a
select = (<|>)

many :: Parser a -> Parser [a]
many f = many1 f <|> return []

many1 :: Parser a -> Parser [a]
many1 f = do
  a <- f
  b <- many f
  return $ a:b

例えばExpr <- Number + Numberを受理するためのは以下のように書くとちゃんと計算結果が返ってくる.

number :: Parser Integer
number = do
  n <- many1 digit
  return $ read n
  where digit = satisfy isDigit

expr :: Parser Integer
expr = do
  a <- number
  op <- token (=='+') >> return (+)
  b <- number
  return $ op a b

main :: IO ()
main = print $ runStateT expr2 "1+2;" -- => Right (3,";")

4則演算バージョンがあるのでぜひ

sample.hs · GitHub

参考

感想

エンドレスモナドチュートリアルが終わって新学期が来た感じ

ruby2.2.2で現在実行中の関数名を取得する

前提

ganmacs@ganmacs~% ruby -v
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin14]

内容

現在実行中の関数名を取得したいとき、ruby 1.8までは以下のようにしてとれた。   しかし1.9からto_s(Array)の挙動が変わったらしく2.2.2では動かなった。

class Object
  def current_method
    caller.first.scan(/`(.*)'/).to_s
  end
end

# when ruby version is 1.8 or earlier
puts current_method #=> "<main>"

# when ruby verison is 1.9 or later
puts current_method #=> [["<main>"]]

なので今は以下のように書くと実行中の関数名を取得できる

class Object
  def current_method_name
    caller.first.match(/`(?<method_name>.*)'/)[:method_name]
  end
end

puts current_method_name #=> "<main>"

参考

to_s (Array) - Rubyリファレンス

Rubyで今実行中のメソッド名を知る - 2nd life

正規表現

Working With TCP Socketsを読んだ

研究してるふりをして全くせずにこれよんだ。

Working With TCP Sockets (English Edition)

Working With TCP Sockets (English Edition)

なぜ読んだかというと、少し前にこの記事いいよって教えてもらった記事があって読んでてたまたまそこに書いてあったから yuuki.hatenablog.com

雑にメモとか取りながら読んだのぜひ github.com

感想

webサーバの実装モデルっぽいのもがわかりやすく紹介されていて非常に面白かった。
英語よめるようになったし、これからも研究してるふりして積極的に関係ないことやっていくぞ!!!

while (token = lexer.read) != EOFをEnumeratorをつかってどうにかしたい

あたらしく研究室に入って(2度目)スクリプト言語の作り方って本が課題なのでやっていまして、そのときlexer作るんですが

while (token = lexer.read) != EOF
  ...
end

的な書き方してて嫌な気持ちになったので他にまともな書き方がないか調べました.

どうしたいか

  • while とかの中で代入はすきじゃないのでしたくない
  • eachみたいにつかえる
  • けど,任意の位置でreadできるようにしたい
  • しかしメモリ使用量を抑えたいので一気に配列に突っ込むとかはしたくない

つまり下みたいにはしたくない

# ファイルひらいて一気に配列に突っ込む
file = open(file_name).map { |f| f.strip }
file.each do |f|
  ...
end

どうしたか

Enumerator classを使いました.

例として以下の様なこと Enumeratorを使ってやってみました.

ファイルから1行ずつ読んでくる->読んできた1行になにかする(行番号をつける). javaLineNumberReaderみたいなやつ

class LineNumberReader
  def initialize(filename)
    @filename = filename
  end

  # nextがなかったらStopIterationをraiseする
  def read_line
    file.next
  end

  def peek_line
    file.peek
  end

  def has_next_line?
    !!file.peek
  rescue StopIteration
    false
  end

  # loopはStopIterationをrescueする
  def each
    loop { yield(read_line) }
  end

  private

  # each_line, with_indexは enumeratorを返すのでnextとかで逐次に取ってくるはず
  def file
    @file ||= open(@filename).each_line.with_index
  end
end

LineNumberReader#fileEnumeratorを返すようにしているので[メモリ使用量を抑えたいので一気に配列に突っ込むとかはしたくない]は解決出来てるはず

またLineNumberReader#read_lineを外から呼ぶようにすることで[任意の位置でreadできるようにしたい]も解決できる

reader = LineNumberReader.new('file.rb')
p reader3.read_line
...
p reader3.read_line if reader3.has_next_line?

またLineNumberReader#eachを作ってることで[eachみたいにつかえる]も解決

reader = LineNumberReader.new('file.rb')
reader.each |f|
  ...
end

while使いたいときはloopを使うと(実際eachで解消されてるけど)[while とかの中で代入はすきじゃないのでしたくない]も解決

reader = LineNumberReader.new('file.rb')
loop
  p reader.read_line
end

まとめ

ただのiteratorっぽいなにかだし,こんなことしてないで早く課題終わらせたほうが良さそう もっと賢い方法があればだれか教えてくれ!!

2週間でできる! スクリプト言語の作り方 (Software Design plus)

2週間でできる! スクリプト言語の作り方 (Software Design plus)

ansibleっぽいitamaeっぽいプロビジョニングツールminarai作った

github.com

作りました. ansibleとitamaeの名前を出しましたがお手本にしたのは @r7kamuraさんが作ったr7kamura/serverkit · GitHub というgemです.

動機

これが欲しかったわけではないんですがgem作ったことなかったし作りたいものもなかったのでなんか真似しようとおもって色々探してたらserverkitが出てきてちょうどよかったので自分でも書いてみました.

使い方

一番シンプルな使い方はrecipe.ymlを以下の内容で作って

- name: clone codic-clojure
  type: git
  repository: https://github.com/ganmacs/codic-clojure.git
  destination: codic-clojure

- name: clone dot_emacs
  type: git
  repository: https://github.com/ganmacs/emacs.d.git
  destination: .emacs.d

以下のコマンドを実行するだけです.また、既に目的のファイルが存在する場合などはスキップされるようになってます.

$ minarai recipe.yml
INFO : Minarai starting...
INFO : [SKIP] clone codic-clojure
INFO : [DONE] clone dot_emacs
INFO : Minarai finish

以上でgit cloneしてきてくれます.

変数を使った場合

recipeに変数を使うこともできます.

まずvars.ymlに以下を記述します.

remote_dotfile_repo: git@github.com:ganmacs/dotfiles.git
tmp_dir: /Users/ganmacs/tmp
dotfile_path: /Users/ganmacs/tmp/dotfile
vim_config_in_dotfile: /Users/ganmacs/tmp/dotfile/vim/vimrc
dot_vim_path: /Users/ganmacs/tmp/.vim

そしてrecipe.ymlに以下を記述します

- name: install vim
  type: homebrew
  item: vim

- name: create tmp directory
  type: directory
  destination: <%= tmp_dir %>

- name: clone dotfile
  type: git
  repository: <%= remote_dotfile_repo %>
  destination: <%= dotfile_path %>

- name: link dotfile to hgoe
  type: link
  source: <%= vim_config_in_dotfile %>
  destination: <%= dot_vim_path %>

そして実行すれば終わりです

$ minarai recipe_erb.yml.erb --variables=vars.yml
INFO : Minarai starting...
INFO : [SKIP] install vim
INFO : [DONE] create tmp directory
INFO : [DONE] clone dotfile
INFO : [DONE] link dotfile to hgoe
INFO : Minarai finish

機能

とにかくシンプルにしたくて他の構成管理ツールみたいに高機能なものを作るのはやめました. 基本的にローカルのMacの構成管理のためにしか使う気はないのでsshもできないです. コマンドも実行するrecipeを渡すだけです.(変数は使えますが)

実行できるタスク(アクション)は以下のとおりです

  • git - git cloneしてきます
  • file - fileのコピーをします
  • link - symlinkをはります
  • directory - directoryを作ります
  • homebrew - homebrewでパッケージをインストールします
  • homebrew_cask - homebrew_caskでappをインストールします
  • url_get - 指定したurlからcurlしてきます

感想

ansibleとitamaeはすごいなーって感想とserverkitのコードが読みやすくて最高って感じでした.

今後

rbenvとかdefaultとかcommandのactionも増やして構成管理に使っていきたい

関連

Serverkitつくった - ✘╹◡╹✘

itamae-kitchen/itamae · GitHub

ansible/ansible · GitHub