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(ググル)したよ。

emacs で robe を使う方法

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

検索の途中で、面白いものを見つけた。

youz / 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に対応下さった、作者様に感謝します。ありがとうございました。


This year's Index

Home