Python,Rubyの言語内DSL構築力:PythonでRakeをまねる場合を例に
Python で Rake を真似るとしたらという反応を頂いたので、それにまつわるお話を。
まず、CodeReposにコミットしてあるtasktoolsはdistutils及びsetuptoolsを拡張するということを念頭に置いています。ですのである程度distutils.core.Commandの思想というか、インターフェイスを残しています。
- タスクをクラスで定義すること
- オプションの定義方法
sub_commandsやfinalize_optionsといったメソッド
などなどは元のまんまです。
じゃあ、distutilsやsetuptoolsを抜いて好きにRakeをPythonで真似るとしたら、というのが今回メインのお話。最初にこんな感じでコマンドを定義しますよ、というのを出してしまいます。
- from __future__ import with_statement
- from 今回作成したモジュール import *
- from subprocess import call
- # ここからタスク定義
- with namespace("test"):
- @task
- def build(t, depends = ["test:make"]):
- """builds this modules. """
- call(["ls"])
- print t.name, ": build done"
- @task
- def make(t):
- print "make"
- # 実行
- run()
Rakeだとこうなる。
- namespace "test" do
- desc "builds this modules"
- task "build" => "make" do |t|
- sh("ls")
- puts "#{t.name} done."
- end
- task "make" do |t|
- puts "make"
- end
- end
Python版もなかなかシンプルで見やすいと思うんですがどうでしょう。あとちょっとtips。さっきの記事だとos.systemを使ってますが、subprocessモジュールのcallやcheck_callのほうが便利じゃないかな、と思います。
タスクのメタ情報は引数のデフォルト値を使います。ここで@task(depends = ["test:make"])ってしたほうがいいんじゃないの?と思う人もいるかもしれません。真っ当な意見だと思いますし、それがPythonicなやり方と思います。しかしそうするとこうなります。
- with namespace("test"):
- @task(depends = ["test:make"])
- def build(t):
- """builds this modules. """
- call(["ls"])
- print t.name, ": build done"
- @task() #<= ここがキモくないですか?
- def make(t):
- print "make"
Pythonではproperty以外では()をつけないとメソッドが呼び出せません。だから引数が何もない場合は空のカッコがつくわけです(まぁDescriptorでhogehogeすればできるんですがね)。これ、イケてないですよね。というわけで引数のデフォルト値を使います。
ではこのタスク定義でタスクを実行するためのコードです。
- from __future__ import with_statement
- import inspect,new,sys
- from contextlib import contextmanager
- from optparse import OptionParser
- from itertools import *
- class _Tasks(dict):
- def __getitem__(self,key):
- if key in self: return dict.__getitem__(self, key)
- _abort("Error: Unknown task: %s"%key)
- class Task(object):
- def __init__(self, run, name, description,
- depends = None, options = None):
- self._run = new.instancemethod(run, self, self.__class__)
- self.name = _get_current_ns()+name
- self.description = description
- self.depends = depends or []
- self.options = options or []
- tasks[self.name] = self
- def run(self):
- for depend in self.depends:
- if tasks[depend].run() is False:
- _abort("Error: faild %s"%depend)
- return self._run()
- _namespace = []
- tasks = _Tasks()
- @contextmanager
- def namespace(name):
- _namespace.append(name)
- yield
- _namespace.pop()
- def task(f):
- names, _, _, values = inspect.getargspec(f)
- Task(f, f.__name__, f.__doc__ or "no description", **dict(izip(names[1:], values or [])))
- def print_tasks():
- maxlength = max(imap(len, tasks.iterkeys()))
- for name in sorted(tasks.iterkeys()):
- print name.ljust(maxlength), "#", tasks[name].description.strip()
- def run():
- usage = "usage :%prog [options] targets"
- parser = OptionParser(usage)
- parser.add_option("-T", "--tasks", action="store_true",
- dest="print_tasks",
- help="Display the tasks (matching optional PATTERN) with descriptions, then exit.")
- (options, args) = parser.parse_args()
- if options.print_tasks: _abort(print_tasks())
- if not args: _abort(parser.get_usage())
- map(lambda t:t.run(), imap(tasks.__getitem__, args))
- def _abort(msg):
- if msg : print msg
- sys.exit(1)
- def _get_current_ns(): return _namespace and ":".join(_namespace)+":" or ""
結構短いかな?。一応ちゃんと動きます。
tasks.py -T
test:build # builds this modules.
test:make # no description
tasks.py test:build
make
tasks.py
test:build : build done
うむ。
結局、Rubyの言語内DSL構築能力は
()が省略できる- ブロック(pythonではデコレータとwith_statementがありますが)
によるものが多いと思っています。ことPythonとの対比では。「シンプルさ」「書きやすさ」なんかは好みかな、と(Rakeの例だとdocstringを持っている分Pythonの方がいい感じさえしてきます)。例えばendってなんだよ、って言う人にはPythonのほうがウケがいいかもしれませんしそうじゃないかもしれません。
そんなこんなで試しに書いてみたらいい感じだったんで、ちゃんと本格的にPython版Rakeとして開発を続けてみようかなあ。
ところで言語内DSLといえばScalaでしょう。Rubyなんてちょろいもんですよ。なんてったってScalaは(ry
有能なプログラマチェック&雑感
から。なんか流行ってるらしいし、就職という人生の転機も近いのでやってみます。文系のエセプログラマなんで、そんなにくわしくは書けないけど、まぁなんとかかろうじてできてるかな、って場合はどうやってるか、見たいなのも書いて見ます。
×要求自体をシンプル化する
たぶん、できてない。やろうとするんだけど、できてない気がする。
○メタレベルプログラミング
C#もLisp系も普段使わないので、普段使っている言語で考える。たぶん、これはやっているはず。というか前職(ドリコム)の頃には行き過ぎて分かりづらいコードを量産していた気がする。反省。
で、なんでこういうことをし始めたかというと・・・たぶんシンプルにしたかったんだと思う。そして、コードが動くことだけじゃなく、コードの中身にこだわっていたからだと思う。
○DSLをオンデマンドで自作する
どうだろう。Rubyを書いていたときにはそれっぽいことはしていた。Pythonでもするかなあ・・・。
これも、便利にしたい、とかいうよりは俺の場合はコードの中身の問題だったなあ。だってDSLだったり、DSLぽかったらカッコイイじゃないですか。これって結構大事なモチベーションだったりする。
△デザインパターン
あやしい。一応デザパタ入門、J2EEパターンあたりの本は持っていて、きちんと読んだ。たぶん、組み立て方のパターンは頭にはいっているはず。ただ名前とかは忘れた。ある意味無意識に使えるようになってきている・・・といいなあ。
またまたこれもコードの中身の問題。やっぱりデザパタつかってるとカッコイイ、なんて思っていた時代があったわけです。ぶっちゃけPHP書いてた時代です。Mojaviあたりが出てきた時代からPHPは書いておりまして。その頃のPHPのフレームワークなんてのはいかにJAVAのフレームワークをパクるか、みたいな感じで。だからJ2EEパターンあたりまで押さえていたわけです。
△英語
話せない。書くのは遅い。けどPCでなら読むのは結構読めると思う。英語に対する恐怖心とかはない。
このページ下部に翻訳中のドキュメントが放置してあるのは秘密(笑
というわけで英語。書くのはダメだけど、読める。昔どっかで読んだんだけど、その時もすごい人が「翻訳ソフト買ったり使ったりするくらいなら快適に辞書をひける環境を作れ」っていってた。
というわけでやっぱり辞書が快適にひける環境が大事ではないか、と。俺の場合「英辞郎」+「DokoPop!」です。これでどこでもctrl+右クリックで一瞬で引けます。翻訳サイトをブックマークレットから利用したりするより絶対こっちのほうがいいです。
また「Babylon」も同じようにどこでも辞書が引けるソフトです。こいつがすごいのはFlash上の文字(例えば歌詞閲覧サイトのFlashとか)でもその場でctrl+右クリックで辞書が引けることです。
というわけでこの辞書環境のおかげで安心して読めます。
△処理系やライブラリの動作原理の精密な理解
精密な理解・・・はしていないし、自分で実装するなんて考えてもみない。ただし、処理系のソースは読む。前にもPythonでgetattrと__getattribute__の違いとかを読んでいた。
これはやっぱり、自分で調べたほうがはやいだろ、っていう気持ちからでしょうか。LLの場合、時として自分の予想と反することがおこるわけで、そういう場合ですね。
○ライブラリに関する知識
たぶん、大丈夫なはず。たぶん・・・
やっぱこれは情報収集をいかにしてるか、ってことだと思う。一応、そこそこRSSは購読してる。動くものを作るときが目的の時(それ自体の学習が目的じゃない場合)はとりあえずライブラリ化されていないか探す。
△基本的なコンピュータサイエンスの知識
- 宣言的記述と手続き的記述の違いを理解する。
- これは大丈夫。
- トランザクション、ロールバック、正規化、外部結合、などなど、RDBの基本
- たぶん大丈夫。昔出会い系のシステムを作っていたときに、ログ集計とかそんなもろもろの部分をやっていていやになるほどやった。出会い系だから、入金とかもからんでいて、結構しんどかった。出会い系でも会員が10万とかになると結構しんどいものがあったし、勉強になったなあ、と思う。
その点、最近はO/Rマッパつかったりで生のSQLなんて全然書かないし、衰えている気がする。その出会い系のところでは10行以上のSQLをガリガリ書いて集計とかをしていた。 - 言語理論
- コンパイラ―原理・技法・ツール
、計算論 計算可能性とラムダ計算
あたりは読んだ。でもこれは違うな。というわけでこれはダメそうだ。
- プロダクションシステム、前向き推論、後ろ向き推論、フレーム、などの人工知能系の概念
- さわりぐらいはやっている。ので簡単なのなら分かる。俺のような文系のプログラマがここらへんのさわりをやるんだったら、Schemeによる記号処理入門
がオススメ。
Schemeがわかる人ならすぐ読みきれる。薄いし。練習問題もあるし、コード例もあってわかりやすい。Schemeの解説も随時されているけど、やっぱりSchemeがわからない人にはちょっとオススメしかねるなあ。
ここはいかに本を読んでるかが勝負だと思った。
×セキュリティ技術の基本
これは、だめだ。今勉強したい分野のひとつ。
△プロトコルの詳細
これってどう仕組みか、長所は?短所は?とかっていうことだろうか?それだったらだいたい分かる。HTTP、FTPくらいかなあ、telnetで話せるのは。他はググりながら恐る恐るしゃべるんだろう(笑
これっていつ覚えたんだっけ。たぶん、昔本を読んだんだと思う。HTTPとかFTPはぶっちゃけ使うものってこと。使わないプロトコルなんて、絶対覚えられないとおもう。
○異質なプログラミングパラダイムを理解しておく
大丈夫なのではなかろうか。普段使ってるのはaboutに書いてあるとおりC、JAVA、Python、Ruby、Javascript、C、Delphiといったそこらへんの言語だけど、ふつけるも読んだし、Schemeはわりと好き。gaucheがWindowsで完璧に動くようになれば、メインにするかもしれない。最近はOcamlにすげえ興味がある。
これはただの興味(笑 なにも実用的な意味はなかったりする。単純に、新しい言語を覚えるのは楽しい。それだけ。
×汎用のキーカスタマイズツール
昔は窓使いの憂鬱で、Windowsのキーバインドをviにしていたけど、やめた。俺以外の人が触ると、「このWindows みたいなの 使いにくい」って言われるわ、学校行ったら全然思うようにPCが使えないわで散々だった。つまり俺ポータビリティが落ちすぎる。
Linuxならvim+zsh+screenでzshのキーバーインドもviにして、reverse-i-searchとか自分が使う機能を適当なキーにバインドして使っている。正直、これでなれると生bashでもつらくなるので、そうならないようにWindowsにbashを入れて両方使えるようにしている。
○開発環境の具体的な機能の理解と使いこなし、開発環境のマクロ言語、開発環境の正規表現
大丈夫。俺にとってはvimを使いこなしてるか、と等価だったりする(笑
もうvimを使い始めて3年くらいになるわけで、よく考えたら甘酸っぱい10代からvimを使っているわけで。それでも全然使いこなせている気がしない。
こういうvimを使いこなそうってのは、「気持ちよさ」からきている気がする。ずっと昔は俺も秀丸で書いていたりしたわけだけど、なんていうか、圧倒的に気持ちいい。キーボードを打つのが。
例えば、頭のなかでコードが出来上がっていて、あとはそれをゴリゴリ実装するだけだとする。他のエディタだとその過程がなんかもどかしい。んだけどvimだとその過程が楽しかったりするんだなあ、これが。
○スクリプト言語
自信をもって○を付けられるのがこれくらいな罠。
Rubyでご飯を食べていたこともあれば、Pythonでエミュレータを書いたりする変な人はそうそういないような気がする。RubyとPythonは票がわれそうだし。実際両方やっている身としてはどっちかやってればいいとつくづく思う。
せっかくなのでRubyとPythonについて、文系の素人プログラマが思っていることを書いてみる。どっちも完璧に好みにあうわけでは当然ない。文句を言いたい点は実はPythonの方が多い。でもRubyには決定的にどうもなあ、な部分があるのでPythonに傾いている。
それはRubyの特徴でもあるobj.methodというやつ。カッコつけなくてもメソッドが実行できるっていう。これがどうも俺には扱いにくくてならない。関数をオブジェクトとして扱いづらいし、メソッド呼んでることを忘れて、めちゃくちゃ重いコードを書いてしまう。
Pythonなら標準では()つけないと呼び出せないし、propertyを使えばカッコを付けずに呼び出すこともできる(ココらへん参照)。これがいいんだなあ。たしかPascalあたりから引っ張ってきたものだと思うんだけど。
そのほか、Rubyにはあんまり文句はない。Pythonと違って命名規約がわりと浸透していて統一感があるし、(pythonではgethoge,get_hoge,getHogeが混在している。)、メタプログラミングも統一感があるし(pythonはmetaclassとかデスクリプタとかややこしい)、ブロックは便利だし、call/ccあるし(笑
○プログラミングに飽きて、プログラマを辞めたくなったときの準備をしておく
大丈夫だ。そもそもPCにはDTMから入ったのでそこに戻るのだろう。
○:9(開発環境についての部分はまとめたので3つで数えた)、△:5、×:3。○とその他半々といったところ。文系のくせに、文系っぽいところがとことんダメな気がするのは気のせいか。
○の理由はだいたい「コードの美しさ」、「興味」の2点だったなあ。これは今後も維持していこう。逆にいえば×の部分は興味がもてないのが原因なんだろう。どうやってモチベーションをあげるか、が大事と見た。
こうやって改めて客観的に自分を見てみるのもよいものだなあ。
考えてみるとやっぱりコンピュータの基本があやしいなあ。こういう人はこれからもっと増えてくるはず。一般的なプログラマなら意図的にやろうと思わないと、やらないで過ぎてしまう。
「ゲーム機の変わりに○○を買ってもらって・・」とか「大学で初めて○○に触って・・・」みたいな世代とは根本的に違うんだろうな。俺の年齢ってちょうどPCがPCになった最初の世代なんだよなあ。だって、小学校の時にWindows95ありましたもん。だいたいのソフトはもう、一通りそろってましたもん。中学生から携帯もってましたもん。
というわけで、なんにもしらなくてもPC使えて、プログラムだって組める。そういう世代だからこそ、基礎をもっと勉強しないとなあ。
Rails1.1: FormBuilderの使い方。
Rails1.1にはFormBuilderというものがあります。
でもいまいちAPIマニュアルで説明が不足していて、なんだかなあ、という感じ。 こういうときはテストをみるのが一番ですね。
テストを参考にどういう風に使うのか、定義してみます。
- class LabellingFormBuilder <ActionView::Helpers::FormBuilder
- include ApplicationHelper
- include ActionView::Helpers::TagHelper
- include ActionView::Helpers::AssetTagHelper
- include ActionView::Helpers::FormTagHelper
- #... and other heplers etc.
- def initialize(*args)
- super
- end
- (field_helpers - %w(hidden_field)).each do |form_element|
- class_eval %Q{
- def #{form_element}(label,opt = {}, *args)
- unless opt.is_a?(Hash)
- args.unshift(opt)
- opt = {}
- end
- opt = {:for=> @object_name.to_s+'_'+args.first.to_s}.update(opt)
- options = (args.last.is_a?(Hash)) ? args.pop : {}
- args.push(options)
- content_tag('label', label, opt) + super(*args)
- end
- }
- end
- end
使い方は
- <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
- <%= f.text_field "苗字", :first_name %>
- <%= f.text_field "名前", :last_name %>
- <% end %>
という具合に。
ポイントは
- ActionView::Helpers::FormBuilderを継承する(当たり前)
- (field_helpers - %w(hidden_field))という感じで、hidden_field以外のメソッドをオーバーライドする。もちろんオーバーライドしたいものだけでもかまわないですけど。
- ヘルパーのメソッドを使いたい場合は適切なヘルパーをincludeする。
こんなところでしょうか。 つかってみるとなかなか便利です。 是非お試しあれ。