たぶんRubyでは 〜 Maybeモナドに憧れて 〜

投稿者 nanki 2009-10-28 14:25:00 GMT

HaskellにはMaybeモナドというのがあって、エラー処理をかなり適当な感じに書けてとても便利そう。

一方、多くのプログラミング言語では、

request.mobile && request.mobile.docomo?

File.open('example.txt').read rescue nil # これはすこし横着

こういった記述を頻繁に使う必要があり、なんとかしたい。

そこで、

class Never
  instance_methods.each do |v|
    undef_method(v) unless %w(__id__ __send__).include?(v)
  end

  def method_missing(*args)
    self
  end

  def end
    nil
  end
end

class Maybe < Never
  def initialize(value)
    @value = value
  end

  def method_missing(*args, &block)
    Maybe.new @value.__send__(*args, &block)
  rescue Exception
    Never.new
  end

  def end
    @value
  end
end

class Object
  def maybe
    Maybe.new(block_given? ? yield : self)
  rescue Exception
    Never.new
  end
end

上のようなコードを実行すると、

request.mobile.maybe.docomo?.end # => true/false or nil

File.maybe.open("file_does_not_exist.txt").read.end # => nil

こういう風に書く事ができるようになって幸せ。 途中のどこかで失敗すると、単にnilが返る。

このように、包んだオブジェクトのフリをするオブジェクトを使う手法はRailsでは多用されていて、読んでみると割と楽しい。ProxyObjectとか呼ぶらしい。

class Maybe < Never のあたりが、

子「お父さん、運動会絶対見に来てね」
父「絶対行くぞ」

一週間後

子「お父さん、運動会絶対見に来てね」
父「たぶん…」

というような会話を想像してしまって、とても人間的。

参考:

今日のgolf - るびまゴルフ第七回

投稿者 nanki 2009-09-14 15:02:00 GMT

月に一度のgolf. というか、人がやっているのを目にすると、手を出したくなってくる。
たぶん、子供がプラモデル組み立てているのを見て「ちょっとお父さんにかしてみなさい」というタイプ。

結局、前回の最短は23Bだったらしい。変数名まで同じだった :)
文字数を削るという共通の目的が、変数名から意味すら奪うのだろうか。

20Bという噂はなんだったんだろう…

反省を生かして、今回は、27Bまで縮んだところで寝かせておく。

続きにあるのはお気に入りの答えだが、パー。

追記: 2009/09/17 いくつかの方針で使っている方法を組み合わせたら23Bになった。驚き。

参考:

chm.rbのバグ?

投稿者 nanki 2009-08-11 22:54:00 GMT

とあるchmファイルをWindowsで見ていて気がついたのだが、一つのキーワードに複数のドキュメントが割り当てられている場合がある。

同じファイルを、Macで見ると、最初のコンテンツしか表示されない。

調べてみると、chmlib のRuby bindingである chm が原因らしい。

そもそも、chmのインデックスは下記のような形式のドキュメントになっていて、chmの中の、chm.rbでこれをパースしているのだが、キーワードが複数のドキュメントに対応する場合が想定されていないようだ。

…
<li><object type="text/sitemap">
  <param name="Name" value="animal">
  <param name="Name" value="dog">
  <param name="Local" value="dog_doc">
  <param name="Name" value="cat">
  <param name="Local" value="cat_doc">
</object>
…

普通の感覚ではこれを見ても一番上のキーワードが下の方まで有効だと思えないのだが…

あと、目次(Topic)を読むところが、case-sensitiveになっていたので、目次が取れていなかった。

コミットしてから気がついたけど、ゴミが…

参考:

今日のgolf - るびまゴルフ

投稿者 nanki 2009-07-02 23:14:00 GMT

久々にgolf。 問題はコチラ.

標準入力から、3,5 という入力を受け取って、3 から5 までの数字を出力するコード。

まず素直(?)に書いて、

p *eval(gets.split(',').join('..')) # 35B

順等に縮めると

p *eval(gets.sub',','..') # 25B

ここからは手強いので寝かせて。

eval'A,B='+gets;p *A..B # 23B

A, Bの重複がとても気になる…

ちょっとずるいけど、$ ruby -n を使っていいと、

p *eval(sub',','..') # 20B

文字列のRangeを使う方法もやってみたけど、挙動に問題があるのでやめ。

風の噂では、20Bまでいける、と聞いたんだけど…がんばります。

参考:

Rubyist Magazine - るびまゴルフ 【第 6 回】


Chemr高速化

投稿者 nanki 2009-04-21 18:46:00 GMT

今回のあらすじ

Flex のドキュメントを、オフライン時にも使えるようにしようと企んだ nanki は、coderepos:chm-generators を手にいれ chm ファイル作ってみるが、サイズが150MBもあるので、Chemr 本体に手を加えて、gzip 圧縮に対応することによって無事50MBにまでスリム化することに成功する。が、よく使うリファレンス群はそもそもサイズが5MB程度なので、たいして役に立たないことに気がつき衝撃を受ける。

これは巨大な chm を作って対抗するしかない、とマシンにインストールされた全ての gem の rdoc をchm 化するが、今度はインデックスが大きすぎるためか検索がもたつくことに気付く。

「これでは仕事ができない!」

不当な怒りに燃えた nanki は、よりよい開発環境を手に入れるために Chemr に改造を施し、高速化することを決意したのだった。

果たして戦いの果てに未来はあるのか!?

結果

一回のインクリメンタル検索処理あたり、0.3~1秒程度かかっていたのが100倍くらい速くなった。 体感では100倍とはわからないが、もたつきが感じられないレベルになった。

インデックスの最初の二文字を抜き出してHashで持ち、検索文字列の最初の二文字でO(1)でしぼりこむ仕組み。

高速化の恩恵は最初の数文字の偏りに大きく依存すると思うけど、そもそもそんな状況では、インクリメンタル検索が快適ではないので気にしないこと。

!DOCTYPE 宣言があると外部CSSが効かない(詳細不明)問題とか、ruby-cocoaでsize/countが無限再帰になる問題とか、Chmerとtypoする問題などにも嵌って大変だった。

その後、バージョン違いがたくさん(railsだけで5個くらい)あったgemを削除しまくったので、普通に速くなってあんまり意味がない。

gem を300種類くらい入れて複数のバージョンを保持しているような人しか恩恵が得られないかも。

ちなみに、もたつきが気になるというのは、僕のタイプ速度より速く、キーリピートより遅いくらい。つまり全然支障はない。

codereposのは古いっぽい…?

参考:

僕はあんまり携帯HTMLをいじらないけど、タイトルのような問題があるという話はよく聞く。

また、CSSにはスコープの概念がないので、TinyMCEなどのWYSIWYGエディタを使っていると、Wordなどから<style>タグを含む断片が貼付けられて、ページ全体のスタイルに影響を与えてしまう、などといったことが起こる。

こういった問題への対応策として、CSSのインライン化が有効に働きそうなので、調べてみた。 参考に挙げているが、Perl/PHP では、こういったライブラリが存在しているらしい。

我らが(?)Rubyはというと、(最近遊びでCSSParserを書いたので、なければ作ろうと思ってたけど)やっぱり存在していて、これが結構便利そう。

TamTam: Inline CSS

しかし、こんな名前で公開されても…。見ても気付かないよ…

インストール

$ sudo gem install tamtam

コードがドキュメント、とばかりに、主要な部分を読んでみた。 使い方を見てみよう。

使い方

TamTam.inline
  :css => '.a { background-color:red }',
  :body => "<div class='a'></div>"
#=> "<div class="a" style="background-color: red;"></div>"

素晴らしい。

また、:documentを使うことによって、HTML内の<style>タグの内容をそのまま使うこともできる。

TamTam.inline
  :document => "<style>.a { background-color:red }</style><div class='a'></div>"
#=> "<style>.a { background-color:red }</style><div class="a" style="background-color: red;"></div>"

スタイルを適用した後の余計なclass属性は、元から存在するclass名と衝突するかもしれない。 そんなあなたには、:prune_classes => trueというそれらしいオプションがあるのだが、 バグなのか何なのか、スタイルを適用する前にclassを全部取りさってしまうので(僕の)望む動きとは違うようだ。

TamTam.inline
  :document => "<style>.a { background-color:red }</style><div class='a'></div>",
  :prune_classes => true
#=> "<style>.a { background-color:red }</style><div></div>"
# 適用されてない!

携帯電話…

携帯電話用に使う場合、<link>タグを認識する部分を自分で書かないといけない。

それと、聞いた話では、携帯電話の場合は通信量を減らすために、style='background-color: …'よりも、bgcolor='…'が好まれるそうだ。

CSSだけでは属性を指定するという使い方は出来ないので、その場合も独自に拡張する必要がありそうではある。

参考:


treetop.vim

投稿者 nanki 2009-03-17 09:28:00 GMT

syntax

TreetopというRubyで書かれたpackrat parserがある。

その文法ファイルをsyntax highlightするvim. これがなきゃ始まらない。

参考:

決定版! Rails用画像添付プラグインpurl

投稿者 nanki 2008-10-23 10:15:00 GMT

Rails pluginとして動く言語、purlを公開しました。

purl はクライアントサイドから柔軟な画像処理を行うために開発されました。 サムネイルの生成などを行うプラグインはいくつかありますが、そのどれよりも強力で汎用性、拡張性があります。

セットアップ

Rails2.1以上では、

$ script/plugin install git://github.com/nanki/purl.git

それ未満では、

$ git clone git://github.com/nanki/purl.git -- vendor/plugins/purl

gitが無い環境なら、githubのdownloadからファイルを落として、vendor/plugins/ に配置してください。

依存ライブラリ

  • 必須: ImageMagick, rmagick
  • あった方がいい: cairo, rcairo

その他

purl のデフォルトの環境では、画像の保存にデータベースを利用します。 imagesテーブルに、blob型のdataカラムを追加してください。

# app/models/image.rb
class Image < ActiveRecord::Base
  attr_readonly :data
end

その上で、ImageController#upload などに以下のように記述します。

# app/controllers/image_controller.rb
class ImageController < ApplicationController
  def upload
    Image.create(:data => params[:data].read)
  end
end

さらに、config/routes.rbを書き替え、purl が実行されるようにします。 書く場所は先頭に近い方がいいと思います。

# config/routes.rb
  map.connect 'purl/:commands',
    :controller => 'purl', :action => 'filter', :commands => /.*/
# app/controllers/purl_controller.rb
class PurlController < ApplicationController
  caches_page :filter

  def filter
    begin
      result = Purl::StackMachine.new(
        params[:commands], Purl::Purl.new).run
      send_data(result[-2], :type => result[-1], :disposition => 'inline')
    rescue => e
      render :status => 500, :text => 'error'
    end
  end
end

そんなこんなでセットアップは終わり。

言語

purl はURL上で記述されることを前提としています。 常にワンライナーであれ!

文法

purl は逆ポーランド記法を採用しています。 データと命令を書かれた順番にスタックマシンにプッシュしていき、命令が実行された結果がスタックマシン上につまれていきます。 命令列は最初に:で区切られ、数字としてパースできるものは数字へ、()で囲われた部分は文字列へと変換されます。

ポリシー

デフォルトで用意される全ての命令は、外部の値に依存しません。 つまり、命令列が決まれば実行結果は確定します。 このポリシーにより、全ての実行結果がキャッシュ可能になります。

サンプルコード

画像をそのまま表示

次の例では、load はスタック上に既にある数字1を消費して、ID=1のImage#dataをデータベースからロードして、スタック上にプッシュします。 to.png では、スタック上の画像データをPNG形式のバイト列に変換し、mimetypeとともにスタック上に積みます。

/purl/1:load:to.png

画像の形式を変換

次の例では、画像をGIF形式に変換しています。

/purl/1:load:to.gif

画像のリサイズ

次の例では、画像を100x100にリサイズしています。 リサイズ用の命令は、何種類か用意されており、高さを一定に保つ resize.heightや、上限を指定する、resize.upto などがあります。

/purl/1:load:100:100:resize:to.png

画像の合成

次の例ではふたつの画像を合成しています。

/purl/1:load:9:load:composite:to.png

次の例では順番を入れ替えています。

/purl/1:load:9:load:swap:composite:to.png

コメント

スタックに積んだ後すぐにpopすると、単純に無視されるので、コメントとして使えます。 キャッシュのライフサイクルコントロールなどに使えると思います。

/purl/1:load:(daily):pop:to.png

図形の描画

詳しくは解説しませんが、試験的にcairoの描画機能をベースにした図形描画命令を用意しています。 ctx命令で、指定したサイズのcairoコンテキストを準備し、xtc命令で実際にサーフェースへの描画を実行します。

/purl/80:80:ctx:40:40:moveto:40:40:40:-20:20:arc.ccw:1:0.3:0:rgb:fill:60:20:4:circle:0:0:0:rgb:fill:xtc:to.png

制限

実行結果をファイルとしてキャッシュする場合、ファイルシステムによる字数制限を受けます。

拡張

拡張は命令モジュールの追加という形で行います。 詳しくはソースコード見てください。

今後

  • 無名関数的なもの。
  • 外部から直接呼び出せない命令とか?
  • エラーメッセージとか?
参考:

この間の.zshrc - cheat 補完

投稿者 nanki 2008-09-30 00:25:00 GMT

netswitch! | 今日の.zshrc - cheat 補完 の補完関数だけど、なんだか遅いなと思ってたら間違いがあって、毎回cheatコマンドを実行していた。

元エントリ中のコードは修正済みだが、差分は以下の通り。

-    echo $*[1,-2] | sed -e 's/^/compadd /g' > ~/.zsh/completion/$*[-1]
+    eval $*[1,-2] | sed -e 's/^/compadd /g' > ~/.zsh/completion/$*[-1]

-  completion_list `cheat sheets | grep -v All` _cheat
+  completion_list 'cheat sheets | grep -v All' _cheat

遅さに困ってた人ごめんなさい。


今日の.zshrc - cheat 補完

投稿者 nanki 2008-09-25 07:00:00 GMT

cheatsheet を集めた cheat という gem があって、たまに便利である。

გ gem install cheat
გ cheat assertions
assertions:
  # Credit: http://nubyonrails.com/articles/ruby-rails-test-rails-cheat-sheet
  # Use 'cheat assert_raise' for more details
  
  # Standard Ruby Assertions
  
  assert                  boolean 
  assert_equal            expected, actual 
  assert_raise            *args 
  assert_raises           *args, &block 
  …
  …

cheat自身の使い方は

გ cheat cheat

どんなcheatsheetがあるのかは

გ cheat sheets

で見られる。

なんだけど、数が多すぎてもうどうしようもないので、さらに便利にするために、zshの補完を使ってみた。

見よう見まね。

completion_list () {
  mkdir -p ~/.zsh/completion
  if [ ! -f ~/.zsh/completion/$*[-1] ]; then
    eval $*[1,-2] | sed -e 's/^/compadd /g' > ~/.zsh/completion/$*[-1]
  fi

  `cat ~/.zsh/completion/$*[-1]`
}

_cheat () {
  completion_list 'cheat sheets | grep -v All' _cheat
}

compdef _cheat cheat

completion_list は一応再利用できるようにしてあるけど、ちゃんと調べたら既にありそうだ。

追記:

いつまでたってもassertionsの内容が古いままだなぁと思っていたら、キャッシュしてた。

გ cheat --new

でキャッシュをクリア。