rubyのリハビリ
setup ruby
世の中、窮屈なpythonばかりなので、久しぶりにrubyにカムバックしてみる。オイラーが真面目に(仕事で)使っていたのは、ruby1.4,1.6の時代。
使っていたと言っても、関数プログラミング的に使っていただけで、classなんて考えるだけ無駄って態度でしたよ。cgi.rbとMySQLあたりで、掲示板を作ったりって奴ね。あの頃はperlが主体だったけど、反骨精神でrubyだったのさ。
gemが出て来て、わけわかめになりかけたので、足を洗ってしまった。最近のrubyはどうなってるの? 下記はFreeBSDに有った案内。
devel/ruby-gems: gem - RubyGems package manager devel/rubygem-irb: irb - Interactive Ruby devel/rubygem-rake: rake - Ruby Make devel/rubygem-rdoc: rdoc - Ruby Documentation System sysutils/rubygem-bundler: bundler - Tool that manages gem dependencies for ruby applications
bundlerってのは、gemの機能を補う、レール屋さんの道具だな。rubyを世に広めたRailsの意向には逆らえませんと言うやつか。
OpenBSDは去年の10月のリリースだったので、ruby 3.0 は、来ていない。有るのは2.5, 2.6, 2.7だ。しょうがないので、3.0を自前で入れた。
irbで補完をしてほしいので、
ob$ cat .irbrc require "irb/completion"
を設定。それからemacsとの連携だな。ruby-modeはemacsがデフォで用意してたので、irbとの連携が出来るように、inf-rubyを入れた。
調子に乗って、ruby-modeでも補完をしたいってんで、gg(ググル)したよ。
irb(main):001:0> ruby/3.0.0 isn't supported by this pry-doc version => "robe on 43734"
rubyが新し過ぎて、時代が追いつていないのか。泣く泣く、rubyをバージョンダウンさせて、pkgからruby 2.7.1 に戻した( ruby27-ri_docs
が別パッケージになってるので、忘れずに入れる事)。そしたら、robeが使えるようになった。
(add-hook 'ruby-mode-hook 'robe-mode) (add-hook 'robe-mode-hook 'ac-robe-setup) (eval-after-load 'company '(push 'company-robe company-backends)) (add-hook 'ruby-mode-hook (lambda() (company-mode) (setq company-auto-expand t) (setq company-transformers '(company-sort-by-backend-importance)) (setq company-idle-delay 0) (setq company-minimum-prefix-length 3) (setq company-selection-wrap-around t) (setq completion-ignore-case t) (setq company-dabbrev-downcase nil) (global-set-key (kbd "C-M-i") 'company-complete) (define-key company-active-map (kbd "C-n") 'company-select-next) (define-key company-active-map (kbd "C-p") 'company-select-previous) (define-key company-active-map (kbd "C-s") 'company-filter-candidates) (define-key company-active-map [tab] 'company-complete-selection) ;; (define-key emacs-lisp-mode-map (kbd "C-M-i") 'company-complete) ;; any ))
M-x inf-ruby M-x robe-start するとrubyと強調動作で補完が効くようになる。
最近のruby事情
いきなり3.0を入れて、しょうもない理由でバージョンダウンしちゃったけど、今のトレンドってか、どんなバージョンが主に使われているの? 公式ページを見ると、2.4はサポート終了って書いてあるけど。
ob$ w3m -dump http://ftp.jaist.ac.jp/pub/OpenBSD/6.8/packages/amd64/ >LOG ob$ grep ruby25 LOG | wc 2 10 180 ob$ grep ruby26 LOG | wc 208 1040 18720 ob$ grep ruby27 LOG | wc 68 340 6120
公開されてるパッケージ数を単純にカウントしてみた。主流はruby 2.6ですかい。2.7はこれから盛り上がる予想だな。ruby 3.0が主流になるのは何時だろう。
gem
Find, install, and publish RubyGems が、検索サイトね。CLIでも、引けるのか。オイラーが持ってるruby本(20年も前のやつ)の375ページにクラスブラウザーの例が出てた。相当品は有るか? 少しclassめいた事に手を出したいと言う、密かな希望有り。
ob$ gem search -r classbrow *** REMOTE GEMS *** ClassBrowser (1.0.3)
webで引くと
ClassBrowser is an interactive class browser that lets you view the current ObjectSpace's class and module hierarchy.
こんな説明しか出てこない。極めて不親切。後は、ruby ClassBrowserでggしろってか。
ClassBrowser is an interactive ruby class browser.
ob$ doas gem uninstall ClassBrowser Remove executables: ClassBrowser in addition to the gem? [Yn] y Removing ClassBrowser Successfully uninstalled ClassBrowser-1.0.3
望んでいた物と違ったので、削除。
ob$ gem info -r ClassBrowser *** REMOTE GEMS *** ClassBrowser (1.0.3) Author: Tom Underhill Homepage: https://github.com/tomun/ClassBrowser A Ruby class browser
こういう大事な事は、きちんと書いておいて欲しいぞと。正直gemsのHPは、人気度の情報しか無い。これはきっとmatzさんの差し金だろう。名前重要、素敵な名前が決まるまでコードは書くな。みんなが納得出来る名前なら、みんなDLしてくれるよとな。
オイラーは、フェースブックもツイターもラインもやっていない情報弱者だから、こういうHPは無用の長物だな。勿論、gitなんてのにも無縁です。
debug
rubyの標準添付物にdebugが有るのは知っていたけど、それは昔の事。今はもっと優れたものが有るかと思ってggしたよ。そしたら有った。 Ruby初心者のためのByebug ですって。
詳しい使い方は、 Byebug に出てた。
ob$ byebug27 -h byebug 11.1.3 Usage: byebug [options] <script.rb> -- <script.rb parameters> -d, --debug Set $DEBUG=true -I, --include list Add to paths to $LOAD_PATH -m, --[no-]post-mortem Use post-mortem mode -q, --[no-]quit Quit when script finishes -x, --[no-]rc Run byebug initialization file -s, --[no-]stop Stop when script is loaded -r, --require file Require library before script -R, --remote [host:]port Remote debug [host:]port -t, --[no-]trace Turn on line tracing -v, --version Print program version -h, --help Display this message
オイラーの場合は、byebye bug の為にdebuggerを使うんじゃなくて、観光が主目的なんで、一巡りのtraceが有るのは有り難いな。
ruby/tk
そもそもなんでrubyかと言うと、前回やったC語で綺麗なグラフってのでposscriptが使われていた。そんなの機動性に優れたrubyとかでいいじゃん。
rubyで関数を書いておき(モジュールかな)、それを呼び出す事で、ps語を発生させれば、ps語を知らない(あるいは後置法が嫌い)な人も簡単に使える。ggしたけど、そういう発想の人はいなかった。
ならば、100歩譲って、ghostscriptのruby版を書けないか? たたき台はforthかなあ。そして、描画環境は ruby/tk のキャンバスぐらいかなあ。作ったらなんだか楽しそう。
手始めに、forthだな。おあつらえ向きなのが見つかった。 Forthを作るのじゃ 作者さんも言ってるけど、結構楽しいって、気持ち分かりますよ。
じゃ次は、ruby/tk だな。昔のrubyには、tkが同梱されてた(結構大きくて困ったけど)。どうやら、ある時期から分離されたみたい。必要ならgemを使って別途入れてください。 debian(32Bit)機にruby3.0を入れたので、その環境でやってみる。
debian:tmp$ sudo gem install tk Building native extensions. This could take a while... ERROR: Error installing tk: ERROR: Failed to build gem native extension. : checking for tcl.h... no Search tk.h checking for tk.h... no Search Tcl library.....Search Tcl library.....*** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. Provided configuration options: --with-opt-dir : To see why this extension failed to compile, please check the mkmf.log which can be found here: /usr/local/lib/ruby/gems/3.0.0/extensions/x86-linux/3.0.0-static/tk-0.3.0/mkmf.log extconf failed, exit code 1 Gem files will remain installed in /usr/local/lib/ruby/gems/3.0.0/gems/tk-0.3.0 for inspection. Results logged to /usr/local/lib/ruby/gems/3.0.0/extensions/x86-linux/3.0.0-static/tk-0.3.0/gem_make.out
ヘッダーファイルが見つからんって言われたけど、また得意のtcl-debが入っていませんじゃないよ。ちゃんと、/usr/include/tcl8.6/tcl.hとかが有るからね。
gemとかpipとかでこういうのが発生すると、パッケージングシステムを理解しなきゃ解決出来ないので、一見便利、実は困ったものだで、好きになれないのさ。
gemのマニュアルもどきにヒントが書いてないかしら。上のエラーからすると、configuのオプションを与えればよさそうと推測出来るんだけど。。。
debian:tmp$ gem help install : $ gem install some_extension_gem [build fails] Gem files will remain installed in \ /path/to/gems/some_extension_gem-1.0 for inspection. Results logged to /path/to/gems/some_extension_gem-1.0/gem_make.out $ gem install some_extension_gem -- --with-extension-lib=/path/to/lib [build succeeds]
こんな事が書かれていた。– でセパレートしてから、オプションを渡せだな。多分、みなさんが苦労してるだろうから、ggしてみる。
お気楽 Ruby/Tk 超入門 に例が載ってたので、32Bit用に変更。
debian:tmp$ sudo gem install tk -- --with-tcltkversion=8.6 \ --with-tcl-lib=/usr/lib/i386-linux-gnu \ --with-tk-lib=/usr/lib/i386-linux-gnu \ --with-tcl-include=/usr/include/tcl8.6 \ --with-tk-include=/usr/include/tcl8.6 \ --enable-pthread
何とか、インストール完了。テスト代わりに、キャンバス(Canvas) の例を実行してみた。 ちょいと使うには、十分だな。資料も豊富に見つかるし。
これで終わってはもったいないので、どんな具合にsoファイルが作成されたか確認(基点は、/usr/local/lib/ruby/gems/3.0.0)
debian:3.0.0$ sudo find . -name '*.so' ./gems/tk-0.3.0/ext/tk/tcltklib.so ./gems/tk-0.3.0/ext/tk/tkutil/tkutil.so ./gems/tk-0.3.0/lib/tcltklib.so ./gems/tk-0.3.0/lib/tkutil.so ./gems/byebug-11.1.3/ext/byebug/byebug.so ./gems/byebug-11.1.3/lib/byebug/byebug.so ./extensions/x86-linux/3.0.0-static/tk-0.3.0/tcltklib.so ./extensions/x86-linux/3.0.0-static/tk-0.3.0/tkutil.so ./extensions/x86-linux/3.0.0-static/byebug-11.1.3/byebug/byebug.so
gemsの下には、ruby様提供のデフォルトモジュールの名前も登録されてる。gemsで一元管理してるのかな。
minips-ruby
検索の途中で、面白いものを見つけた。
postscriptのインタプリタ。出力は、svg。(e)psはブラウザーは喰ってくれないけど、svgなら、ちゃんと咀嚼して絵を書いてくれる。しかもpdfと同様に、拡大・縮小しても綺麗さは保たれる。このコードを書かれた作者様は、慧眼だなあ。
結構長いコードだ。gemで見つけられなかったClassBrowser代わりに、grepしちゃえ。
debian:minips-ruby$ egrep '(class|module)' -n minips.rb 5:module MiniPS 16: def self.private_module_function(name) 17: module_function name 18: private_class_method name 21: module Util 25: module_function :deg2rad 30: module_function :rad2deg 38: module_function :color_to_hex 42: class Scanner < StringScanner 73: module Parser 76: class EofError < SyntaxError 230: class Value 279: class Num < Value 300: class Str < Value 363: class Operator < Value 379: class Procedure < Value 408: module_function :define_op 1010: private_module_function :arcbody 1107: class Paint 1114: class PathF < Paint 1123: class PathS < Paint 1134: class Text < Paint 1147: class GraphicsState 1171: class VM 1475: class SVG
モジュールとクラスについて、再学習かな。あと、肝心のps語はどんなのをサポートしてる?
debian:minips-ruby$ grep -n def minips.rb 16: def self.private_module_function(name) 22: def deg2rad(d) 27: def rad2deg(r) : 408: module_function :define_op 411: define_op("pstack"){|vm| 416: define_op("stack"){|vm| 421: define_op("pop"){|vm| vm.pop_op} : 887: define_op("setrgbcolor"){|vm| 893: define_op("setcmykcolor"){|vm| raise "not implemented"} 894: define_op("setfont"){|vm| : 1080: define_op(".o"){|vm| 1083: define_op(".g"){|vm| 1093: define_op(".writesvg"){|vm| :
頭がドットで始まるps語は、ちゃんと見て桶ワードだろう(多分)。それから、以前ps語を使ってた時、透過が出来ない仕様って事に気が付いた。svgでは、サポートしてるのだろうか?
折角なのでサンプルを眺めてみる。美しい雪片は好きですか? ってのはps語のお約束みたいなものだな。オイラーは、これでps語入門を果たしたのであった。コードを見ると、不確実性が無いものになってた。勿論、再帰を使って書かれている。それはいいんだけど、
-rw-r--r-- 1 sakae wheel 225 Feb 22 06:39 peano-curve.ps -rw-r--r-- 1 sakae wheel 198616 Feb 22 06:39 peano-curve.svg -rw-r--r-- 1 sakae wheel 1056 Feb 22 06:39 penrose-tiling.ps -rw-r--r-- 1 sakae wheel 623781 Feb 22 06:39 penrose-tiling.svg -rw-r--r-- 1 sakae wheel 246 Feb 22 06:39 snowflake.ps -rw-r--r-- 1 sakae wheel 266217 Feb 22 06:39 snowflake.svg
ps語に比べて、出力結果のsvgファイルが、随分肥大化してるな。何で? それは再帰が深いから、、では、もったいないので、svgなファイルを開いてみる。図形と言っても、人間が読める(かも知れない)文字で表現されている。 下記は、その一部だ。
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="540" height="540" viewBox="0 0 540 540"> <path fill="none" stroke="#0066CC" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" d="M50 390L55.0 390.0L57.5 385.6698729810778L60.00000000000001 390.0 L55.000000000000014 390.0L57.5 394.3301270189222L60.0 390.0L65.0 390.0 L67.5 385.6698729810778L65.0 381.3397459621557L69.99999999999999
読みやすいように、ちょいと改行を入れている。やけに、小数点以下の桁数が多く(13桁)ないかい。このデータは、ポイントのはず。こんな精度が必要な人は、お札をコピペして偽札を作る人しかいません。
そう、密かに埋め込まれているマイクロ文字がそれだ。今、半官放送局が、財務省の肝入りで、次期1万円札の宣伝を始めたな。どんな隠れ文字を使って来るか見ものだぞ。
この不要な小数点以下の文字をカットしてしまえば、飛躍的に小さなファイルになるであろう。折角なので、コード解析して、改変(悪)してみるか。
ps語で書いている分には、1.23 4.567 moveto なんて、変人じゃない限りやらないだろう。そうすると、マシンが勝手にやってる事になる。それは、何処で? そんなの、前回やったアフィン変換の所に決まっているだろう。ああ、行列演算ね。
前回は、色々言語で行列演算をやったけど、python語のやつが抜けていた。python loveに方にお詫びして、例を引いてきておきました。 [AI・機械学習の数学]行列の基本と、回帰/ニューラルネットワークでの表現
これを見るまでもなく、numpyがpython一番人気を作り出している立役者って事が分かります。
話が逸れた。行列演算してる所を探せばいいんだな。目星を付けたので、byebugの実習に突入します。
ob$ byebug27 minips.rb -i [1, 10] in /tmp/minips.rb 1: #!/usr/bin/env ruby => 2: require 'strscan' 3: require 'matrix' 4: 5: module MiniPS 6: PRODUCT_NAME = "miniPS" 7: VERSION = "0011" 8: DEFAULT_BBOX = "0 0 595 842" 9: 10: FONTMAP = { (byebug) b 1325 Created breakpoint 1 at /tmp/minips.rb:1325
gdbの代わりにbyebugを起動。すかさずBPを置きます。尚、期待してたtraceオプションは、役立たずでしたよ。ちゃんとコードを読んで、的確にBPを桶って事です。
(byebug) c minips>45 rotate minips>100 200 moveto Stopped by breakpoint 1 at /tmp/minips.rb:1325 [1320, 1329] in /tmp/minips.rb 1320: @gs.ctm = @gs.ctm * m 1321: end 1322: 1323: def transform_point(v) 1324: t = @gs.ctm * Vector[v[0], v[1], 1] => 1325: Vector[t[0], t[1]] 1326: end 1327: 1328: def real_point 1329: transform_point @gs.point
そして、少し使ってみます。movetoした座標が、すかさずアフィン変換されるんだな。
(byebug) bt --> #0 MiniPS::VM.transform_point(v#Vector) at /tmp/minips.rb:1325 #1 MiniPS::VM.real_point at /tmp/minips.rb:1329 #2 block in block in <module:MiniPS> at /tmp/minips.rb:971 #3 MiniPS::Operator.[](vm#MiniPS::VM) at /tmp/minips.rb:370 #4 MiniPS::VM.eval1(expr#Hash) at /tmp/minips.rb:1379 #5 block in MiniPS::VM.block in eval_prog(prog#Array) at /tmp/minips.rb:1419 #6 Array.each at /tmp/minips.rb:1402 #7 MiniPS::VM.eval_prog(prog#Array) at /tmp/minips.rb:1402 #8 MiniPS::VM.eval_string(src#String) at /tmp/minips.rb:1425 #9 #<Class:MiniPS>.start_repl(opts#Hash) at /tmp/minips.rb:1461 #10 <top (required)> at /tmp/minips.rb:1578
何はともあれ、お約束の、ここに至るまでの道筋確認。頼りになるのが行番号だけだとは、頼りないぞ。別端末でemacsでも立ち上げておいて、M-x goto-line するのが良いかな。
(define-key global-map (kbd "C-c g") 'goto-line)
度々使うなら、emacsにこんな設定をしておくと、ラクダのperlだ。
(byebug) eval v Vector[100, 200] (byebug) @gs.ctm Matrix[[0.7071067811865476, -0.7071067811865475, 0], [0.7071067811865475, 0.7071 067811865476, 0], [0.0, 0.0, 1]] (byebug) eval t Vector[-70.71067811865473, 212.13203435596427, 1.0]
して、その時の状況。変数を検査する時は、変数名をそのまま入力すればいいんだけど、byebugのコマンド名と被る場合が有る。そんな時はevalすれば良い。不安だったら、変数の前にevalを付けるって覚えておけ。
(byebug) v [v]ar <subcommand> Shows variables and its values var all -- Shows local, global and instance variables of self. var args -- Information about arguments of the current scope var const -- Shows constants of an object. var global -- Shows global variables. var instance -- Shows instance variables of self or a specific object. var local -- Shows local variables in current scope. (byebug) v local self = #<MiniPS::VM:0x0000029e78fd7078> t = Vector[-70.71067811865473, 212.13203435596427, 1.0] v = Vector[100, 200]
これ、被った例。helpが出て来た。ちょいとサブコマンドを指定してみた。全部出すと、場合によっては、眼が眩むので注意。
(byebug) var instance @current_page = [] @dsc = {} @dstack = [#<MiniPS::Value:0x000055eb5189ff70 @type=:dict, @value={"null"=>#<MiniPS::Value:0x000055eb511c03f8 @type=:null, @value="null">, "true"=>#<MiniPS::... @gs = #<MiniPS::GraphicsState:0x000055eb51b422c8 @color=[0, 0, 0], @linewidth=1, @font=nil, @point=Vector[100, 100], @ctm=Matrix[[0.7071067811865476, -0.7071... @gstack = [] @nametable = {} @ostack = [] @pages = [] @rand = #<Random:0x000055eb51b2c798> @saved = nil @stderr = #<IO:<STDERR>> @stdout = #<IO:<STDOUT>> @step = false
ああ、これは使えるか。
で、改変を、取り合えず下記のようにした。
Vector[t[0].round(), t[1].round()]
マイクロ文字に挑戦したい輩は、round(6)とか、勝手にやってください。そんな事もあろうかと、次期1万円札には改変防止の為、RFIDが埋め込まれる予定です。この間ユニクロで買い物したら、自動レジって事で、籠を台に載せるだけで、物品名やら価格が提示された。女房は、魔法みたいって驚いていたぞ。
安いユニクロ製品でも、こういう事が簡単に出来るんだから、お札で出来ない訳が無い。協力、大日本印刷って事で、密かにチップが発注されてるだろう。チップなら日立って事も有るかな?
ob$ chmod 755 minips.rb ob$ ./minips.rb snowflake.ps writing ./snowflake.svg ... done ob$ ls -l snowflake.* -rw-r--r-- 1 sakae wheel 246 Feb 22 06:39 snowflake.ps -rw-r--r-- 1 sakae wheel 56912 Feb 22 07:17 snowflake.svg
激変、before after って程ではないけど、スリムになったな。
最後に .g をちょいと見易くする改造。require 'prettyprint' しておいて、該当箇所のpをppに変えればおk。
minips>.g [pages] [] [current page] [] [current graphics state] #<MiniPS::GraphicsState:0x000055c9b01a7a00 @color=[0, 0, 0], @ctm= Matrix[[0.7071067811865476, -0.7071067811865475, 0], [0.7071067811865475, 0.7071067811865476, 0], [0.0, 0.0, 1]], @current_path= [[:moveto, Vector[1.4210854715202004e-14, 141.4213562373095]], [:lineto, Vector[2.842170943040401e-14, 282.842712474619]]], @font=nil, @linecap=0, @linejoin=0, @linewidth=1, @point=Vector[200, 200]> [current graphics stack] []
gdbを駆使しても、追うのが面倒なghostscriptを読み易いruby語で表現し、かつ近代的なデバイスのsvgに対応下さった、作者様に感謝します。ありがとうございました。