I am ruby

Table of Contents

I am ruby

> (map (lambda (x) (* x x)) '(1 2 3 4))
(1 4 9 16)
irb(main):001> [1, 2, 3, 4].map {|x| x * x}
=> [1, 4, 9, 16]

Ruby == Matz Lispって事で、ruby 1.4.X, 1.6.X時代は多用してきてたけど、class なんて考えるだけ無駄とばかり頑無視してたんだ。Try rubyって事で、Matz本を 20年ぶりぐらいに開いてみたよ。 そしたら、しおり代りに馬券が挟まっていた。

新潟11レース 単勝 2 アイアムルビー (まつもとゆきひろ matz)

Matzさんのサイン入り。多分、世界にひとつしか無い珍品であります。 そのMatzさんは、 rubyを開発した動機を尋ねられて、pythonのオブジェクト指向が中途半端だったからと 答るのが、常らしい。

777 + 888 と言う何の変哲もない式が(777とかは、正月だから縁起の良い奴を選んだ)、正式には、

irb(main):001> 777.+(888)
=> 1665

こういう事らしい。メソッドに + だ とか ? や ! はたまた <=> (その姿から宇宙船という愛称がある) なんて記号が 取り入れられてるもの、好印象だ。

history

時代ギャップの解消のため、rubyの進化を少し追跡してみる。

pub/ruby – history rubyの遺跡置き場

ruby – head 新しいrubyが生まれる現場

簡単な進化の流れ 【Ruby1.8以前から】Rubyの処理系とJIT【Ruby3.2のYJITまで】

ruby 1.8.X までが、古風な奴。1.9.1あたりから笹田先生のVMとかgemそれに多言語 処理用の文字 エンコーダー(utf-8)が充実してきて、手が つけられなくなる。これもそれも、Railsと言う黒船のせいだな。正に1.9.1は明治維新と 思うぞ。んな訳で、最後の古風な物を復元してみた。

普通、parse.yをコンパイルする為にyaccとかbisonが必要になるんだけで、コンパイル 結果のparse.cが同梱されたので、これらの導入は不要。拡張エリアの2点 (ext/{openssl, pty})は、現代の lubuntuとはAPIが違うみたいでコンパイルエラーになった為、除外した。

sakae@lu:ruby-1.8.7$ ./ruby -v
ruby 1.8.7 (2008-05-31 patchlevel 0) [x86_64-linux]

ソースが有るから安心と言うのは、半分正解で半分は嘘ある。まあ、これだけでも有ればgdb できるんで重宝します。えっ、ruby 4.0.0が有るだろうにって? こやつは、もう素人には 荷が重すぎて始末が悪すぎますよ(例えばBPで停止した時のbtが永遠と思える程不快)。

それはそうと、時代の変遷と共に、tar玉が大きく成長してる。どれぐらい肥大化が 進んでいるか、確認してみるか(誰か、やってるだろうけど)。

#!/bin/sh

chc=`find $1 -name '*.[ch]' | xargs ls | wc -l`
chs=`find $1 -name '*.[ch]' | xargs wc -l | grep total | sed 's/ total//'`

rbc=`find $1 -name '*.rb' | xargs ls | wc -l`
rbs=`find $1 -name '*.rb' | xargs wc -l | egrep 'total$' | sed 's/ total//'`

printf "%s\t%s\t%s\t  %s\t%s\n" $1 $chc $chs  $rbc $rbs

適当にtar玉を展開して、その中のC語成分(ヘッダーとソース)とruby語成分のファイル数と総行数を 計測する。下記の様に使う。

fu$ for d in ruby*
> do
> ./scan.sh $d
> done

結果に手動でタイトルを付けてみた。

                    *.[ch]          *.rb
                --------------    -----------
 Ver.           cnt     lines     cnt   lines
----------------------------------------------
ruby-1.0-971225 85      46594     84    10403
ruby-1.4.6      107     74501     164   30273
ruby-1.6.7      124     84973     202   42006
ruby-1.8.7      255     214463    1670  314740
ruby-1.9.1-p0   340     547236    1976  506859
ruby-2.0.0-p0   483     776185    2612  718151
ruby-3.0.1      1139    1020848   8171  762429  +383376
ruby-3.4.8      1008    1137215   8490  1254418 +464920
ruby-4.0.0      1045    1178464   8351  1249604 +421957

ruby-3.Xからは、どうした事か*.rbの総行数(total)が2個出現するんで、参考のため 2個目を + で、表示しといた。

なお、この現象はOpenBSD特有の問題かと思ってリナでも確認したら、こうなった。

sakae@lu:tmp$ find ruby-4.0.0 -name '*.rb' | xargs wc -l | egrep 'total$'
  370630 total
 153639 total
  965573 total
 181719 total

ruby3.Xからはunixを狂わす魔物が住んでいるんだな(と人のせいにして逃げる)。 想像だけど、xargsがリミット越へしてないか?

find ruby-4.0.0 -name '*.rb' -print0 | xargs -0 wc -l | awk 'END{print}'
find ruby-4.0.0 -name '*.rb' -exec wc -l {} + | awk 'END{print}'

xargsを利用しない、下の方法が美的だな。上の方法は、スペース・改行を含むファイル名でも安全 らしい。Windows対策って事だな。

RHG

そしてRuby界のレジェンドであります。

『Rubyソースコード完全解説』サポートページ

ruby本は、256倍XXXとかRails本とか結構入手してたけど、何故かこの本は所有していない。 おしげもなくWEBに公開されてる青木先生に感謝であります。

で、完全解説って語句を捉えて、こういうのも出てきた。まだ、るびまって発行 されているんですね。懐しいです。

YARV Maniacs 【第 1 回】 『Ruby ソースコード完全解説』不完全解説

gems

下記はFreeBSDのruby 4.0.0メンテナーご推薦のgem一覧。知らない物が多数 有って、浦島太郎ですよ。

benchmark - Performance benchmarking library
base64 - Encode and decode binary data using a Base64 representation
gem - RubyGems package manager
csv - Interface to CSV files and data
debug - Debugging functionality for Ruby
drb - Distributed object system for Ruby
erb - Templating system for Ruby
fiddle - libffi wrapper for Ruby
getoptlong -GetoptLong for Ruby
irb - Interactive Ruby
logger - Simple logging utility for outputting messages
minitest - Complete suite of testing facilities
mutex_m - Mixin to extend objects to be handled like a Mutex
observer - Implementation of the Observer object-oriented design pattern
ostruct - Class to build custom data structures, similar to a Hash
power_assert - Power Assert for Ruby
pstore - Transactional File Storage for Ruby Objects
racc - LALR(1) parser generator for Ruby
rake - Ruby Make
rbs - Language for type signatures for Ruby and standard library definitions
rdoc - Ruby Documentation System
readline - Loader for readline
reline - Alternative GNU Readline or Editline implementation by pure Ruby
repl_type_completor - Type based completion for REPL
rinda - Linda distributed computing paradigm in Ruby
syslog - Ruby interface for the POSIX system logging facility
test-unit - Unit testing framework for Ruby
typeprof - Type analysis tool for Ruby code
resolv-replace - Replace Socket DNS with Resolv
net-ftp - Support for the File Transfer Protocol
net-imap - Ruby client api for Internet Message Access Protocol
net-pop - Ruby client library for POP3
net-smtp - Simple Mail Transfer Protocol client library for Ruby
bigdecimal - Arbitrary-precision decimal floating-point number class
matrix - Implementation of Matrix and Vector classes
prime - Prime numbers and factorization library
nkf - Ruby extension for Network Kanji Filter
bundler - Tool that manages gem dependencies for ruby applications
abbrev - Calculate a set of unique abbreviations for a given set of strings
rexml - XML toolkit for Ruby
rss - Family of libraries that support various formats of XML "feeds"

歴史の所で、肥大度の計測に、慣れ親しんだshを使ってしまったけど、rubyの記事を 書いているんで、少々rubyの記事としては、そぐわないぞ。んで、ちょっとrubyしてみる。

require "find"

rbs = 0
Find.find( ARGV[0] ) do |f|
  rbs += File.size(f)    if File.extname(f) == '.rb'
end
puts rbs

findで*.rbを抽出して、そのファイル・サイズの合計を求めてみた。本当は、ファイルの 行数も欲しかったんだけど、そんな都合の良いメソッドは標準では提供されていなかった。 行数ったら、改行の数をカウントすればいいんだな。あれ? wcの改行定義はどうなってる? 後で、調べておこう。

else if (doline) {
        while ((len = read(fd, buf, _MAXBSIZE)) > 0) {
                charct += len;
                for (C = buf; len--; ++C)
                        if (*C == '\n')
                                ++linect;

忘れないうちにOpenBSD/wcの該当部分。MACからのファイルはどうなん? あやつの行末は 確かCRだったはずだけど。。 まあ、これさえ解れば、後は、こんなので良い。

File.read(f).count("\n")

そんな事より、ファイルの行数とサイズに相関は有るはずだけど、その係数はどれぐらい だろう? gemに提供されてるかな。

sakae@lu:tmp$ gem search -r statistics | wc
     41      86    1218

この中から選べって苦難な作業だな。 やっぱり、こういうのの世話になるのが楽そうだ。 The Ruby ToolBox

例えば、こんなのが引っかかってくる。星の数とリリースが継続してるかで、眼grep するのかな。後はテストの所を見て、どんな機能が提供されてるかかな。 ruby-statistics

いや、そんなのこそチャッピーに提案させれば楽そうだ。

AST / VM

pythonで出来る事はrubyでも可能なはず。まずは構文木。pythonには不得手な 一行野郎を、さりげなく使ってます。

ob$ ruby --dump=parsetree -e '777 + 888'
@ ProgramNode (location: (1,0)-(1,9))
+-- locals: []
+-- statements:
    @ StatementsNode (location: (1,0)-(1,9))
    +-- body: (length: 1)
        +-- @ CallNode (location: (1,0)-(1,9))
            +-- CallNodeFlags: nil
            +-- receiver:
            |   @ IntegerNode (location: (1,0)-(1,3))
            |   +-- IntegerBaseFlags: decimal
            |   +-- value: 777
            +-- call_operator_loc: nil
            +-- name: :+
            +-- message_loc: (1,4)-(1,5) = "+"
            +-- opening_loc: nil
            +-- arguments:
            |   @ ArgumentsNode (location: (1,6)-(1,9))
            |   +-- ArgumentsNodeFlags: nil
            |   +-- arguments: (length: 1)
            |       +-- @ IntegerNode (location: (1,6)-(1,9))
            |           +-- IntegerBaseFlags: decimal
            |           +-- value: 888
            +-- closing_loc: nil
            +-- block: nil

そして、compile.cでコンパイルされた、VMの実行コード。

ob$ ruby --dump=insn -e '777 + 888'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,9)>
0000 putobject                  777                       (   1)[Li]
0002 putobject                  888
0004 opt_plus                   <calldata!mid:+, argc:1, ARGS_SIMPLE>[CcCr]
0006 leave

笹田先生の説明によれば、数字の演算命令(+, -, <, 等)は高速化の為、特化命令を 用意してるらしい。通常のメソッドは、send命令で処理するらしい。gaucheのVMでは 同様な思想で、融合命令を設けているとか。下層の部分では泥臭い努力をしてるんだね。

VMは最初forthのごとくスタックマシンだったけど最近はgccの内部の表現のように RTLになってるらしい。いわゆるレジスタマシンね。

Ruby 2.6にJITコンパイラをマージしました からベンチマークのコードを頂いてきて、負荷試験してみる。

sakae@lu:tmp$ cat bm.rb
require "benchmark"

def calculate(a, b, n = 10_000_000)
  i = 0
  c = 0
  while i < n
    a = a * 16807 % 2147483647
    b = b * 48271 % 2147483647
    c += 1 if (a & 0xffff) == (b & 0xffff)
    i += 1
  end
  c
end

Benchmark.bm do |x|
  x.report("calculate") do |times|
    calculate(65, 8921)
  end
end

現代のruby(4.0.0)と江戸末期のruby(1.8.7)の対決。

sakae@lu:tmp$ ruby bm.rb
               user     system      total        real
calculate  0.862045   0.000000   0.862045 (  0.862135)

sakae@lu:tmp$ ~/GZ/MINE/bin/ruby bm.rb
      user     system      total        real
  5.790000   0.000000   5.790000 (  5.796307)

debug

ちょいとgdbしたくなった。何処にBPを置くか? 確実に止めるならシステムコールだな。 これならrubyの内部構造を知らなくても大丈夫だろう。

ruby語が確実にシステムコールを利用してる関数を想像する。きっとgets/puts あたりが、 read/writeシステムコールを呼び出しているに違いない。その方針でやってみる。

どうでもいいけど、OpenBSDのlibcには、getsが無い。stdinから無制限にread出来ちゃうのは 重大なセキュリティー脅威になるからだ。

DESCRIPTION
       Never use this function.

       gets()  reads  a  line from stdin into the buffer pointed to by s until
       either a terminating newline or EOF, which it replaces with a null byte
       ('\0').  No check for buffer overrun is performed (see BUGS below).

リナのマニュアルでgetsを引くと、絶対に使うなって書いてる。有るから使っちゃうんだぞ。 でも過去のしがらみを大事にしてるって事ですかね。安全なfgetsを使えとな。

sakae@lu:ruby-1.8.7$ gdb -q ruby
Reading symbols from ruby...
(gdb) b read
Breakpoint 1 at 0x11f70 (5 locations)
(gdb) r -e 'puts(gets)'
Starting program: /tmp/ruby-1.8.7/ruby -e 'puts(gets)'
  :
(gdb) bt
#0  __GI___libc_read (fd=0, buf=0x5555556bdad0, nbytes=1024)
    at ../sysdeps/unix/sysv/linux/read.c:25
#1  0x00007ffff7c927a5 in _IO_new_file_underflow (
    fp=0x7ffff7e038e0 <_IO_2_1_stdin_>) at ./libio/libioP.h:1030
#2  0x00007ffff7c955d2 in __GI__IO_default_uflow (
    fp=0x7ffff7e038e0 <_IO_2_1_stdin_>) at ./libio/libioP.h:1030
#3  0x000055555559448e in appendline (fptr=fptr@entry=0x5555556a4840,
    delim=delim@entry=10, strp=strp@entry=0x7fffffffc150) at io.c:1569
#4  0x00005555555946e4 in rb_io_getline_fast (fptr=0x5555556a4840,
    delim=delim@entry=10 '\n') at io.c:1665
#5  0x0000555555597b64 in rb_io_gets (io=<optimized out>) at io.c:1755
#6  argf_getline (argc=argc@entry=0, argv=argv@entry=0x0) at io.c:4613
#7  0x0000555555597c2b in rb_f_gets (argc=0, argv=0x0) at io.c:4680
#8  0x0000555555575e2b in rb_call0 (klass=klass@entry=140737352567120,
    recv=recv@entry=140737352557240, id=id@entry=7721, oid=7721,
    argc=<optimized out>, argc@entry=0, argv=argv@entry=0x0,
    body=<optimized out>, flags=<optimized out>) at eval.c:5899
#9  0x00005555555760f3 in rb_call (klass=<optimized out>,
    recv=recv@entry=140737352557240, mid=7721, argc=argc@entry=0,
    argv=argv@entry=0x0, scope=scope@entry=2, self=140737352557240)
    at eval.c:6146
#10 0x00005555555713a2 in rb_eval (self=self@entry=140737352557240,
    n=<optimized out>) at eval.c:3513
#11 0x00005555555733b5 in rb_eval (self=self@entry=140737352557240,
    n=n@entry=0x7ffff7e5ccf8) at eval.c:3502
#12 0x000055555557dc8d in eval_node (node=0x7ffff7e5ccf8, self=140737352557240)
    at eval.c:1436
#13 ruby_exec_internal () at eval.c:1642
#14 0x000055555557dd2d in ruby_exec () at eval.c:1662
#15 ruby_run () at eval.c:1672
#16 0x00005555555668bb in main (argc=3, argv=0x7fffffffdd78,
    envp=<optimized out>) at main.c:48
(gdb) c
Continuing.
hello
hello
[Inferior 1 (process 17118) exited normally]

どうやら目論見は当ったみたいだけど、一つ疑問が有る。readシステムコールって もっと沢山ヒットしてもよさそうなのに、ピンポイントでgetsでヒットした?

mruby

上記の1.8.7はリナ上で動く。しかしリナは32Bitを切り捨ててしまってるからなあ。FreeBSDの 32Bit版はどうか? –without-gcc オプションを付けてgccと縁を切ってみたけど 途中でエラーになってしまった。gccにベッタリなんだな。

そこで目を付けたのはパパがMatzであるmrubyだ。組み込み方面を目指しているんで、 gccベッタリと言う事はあるまい。OpenBSDのportsを覗いてみる。

mruby ISO standard って大書してあるからgccフリーって事でいいかな。

コンパイルするには、普通に動くrubyとbisonが必要とな。

do-build:
        cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} ${RAKE} --verbose

出たなmakeのruby版であるrakae。しっかりと、ふつrubyのお世話になっております。 これは、試してみる鹿。まずは、河豚板の上で実験。この場所なら、どんな悪さを しても痕跡が残る事は無いからね。

mruby is the lightweight implementation of the Ruby language complying to (part
of) the ISO standard. Its syntax is Ruby 1.9 compatible.

mruby can be linked and embedded within your application. We provide the
interpreter program "mruby" and the interactive mruby shell "mirb" as examples.
You can also compile Ruby programs into compiled byte code using the mruby
compiler "mrbc".

こんな説明が出てた。楽しそうであります。非力なpicoラズパイ上で動く microPythonの対抗馬です。

三者三様

Q: ハフマン符号の発生スクリプトをruby(3.4.6)で作成してください。 出力パターンは見易いように0を'.'で、1を'-'で表現してください。 インターフェースは、下記のようにおねがいします。

echo -n aaabbc | ruby gen.rb
a   3   --
b   2   .-
c   1   -.

rubyのコードと言えども、入出力は、stdin/stdoutにしておくのが、unix流だと 思うぞ。部品を提供するのが使命です。変に完成品にしちゃうと使い回しが 不便になるからね。

ChatGPT, Gemini, Grok の三者から回答を得たので、受け入れ試験してみる。

vm$ echo -n abbbbccddd | ruby chatgpt.rb
b   4   .
d   3   -.
a   1   --.
c   2   ---
vm$ echo -n abbbbccddd | ruby gemini.rb
b       4       .
d       3       -.
c       2       ---
a       1       --.
vm$ echo -n abbbbccddd | ruby grok.rb
b       4       .
d       3       -.
c       2       ---
a       1       --.

clude.aiにも参加してもらおうかな。色々な人(rubyist)が居れば更に楽しくなるぞ。

vm$ echo -n abbbbccddd | ruby clude.rb
b       4       .
d       3       -.
c       2       ---
a       1       --.

無料枠で登録したよ。一日の使用回数制限とか有りそうだから、ご意見番と思っておこう。

3人の結果が一致したら、まずは正しいだろう。人口衛星とかに搭載されてる コンピューターなんかでも、同じ手法を取ってるね(宇宙線による誤動作回避)。こういうのを多数決原理、 別名では民主主義といいます。

README

正しい答を導く質問力 なんて本を読んだ。

『正しい答えを導く質問力』を解説 ―「質問力」はAI時代を勝ち抜く最強スキル こんな要約も著者によってなされていて、AI時代っぽい。社員研修のまとめとして 書かれたのかな。

トヨタの社内では、Whyを少くとも5回は繰り返して本質に迫れと教育されるそうだ。 これを愚直にやってしまうと、詰問調になってしまうんで、言葉を選べとの事。

どんな質問をしたらいいのか浮かばないとむきには、5W4Hから選べばいいそうな。 5Wの方はいいとして、4Hの方。How, How many, How long, How much。

これを覚えておけば、何なく質問を繰り出せるぞ。合コンでも会話が弾むはずだ。 会話って、質問、回答、質問、回答の連続だからね。

両方が耳に入って流れが理解できる。携帯で話している人を不快に思うのは、 案外、片方の話しか耳に入らないからでは、なかろうか。そう、たまに独り言を 言いながら歩いている人に遭遇するけど、危ない人って避けちゃうのは、こういうのが 有るからかも知れない。

ああ、余計な繰り言だった。AIへの上手な質問も、この本の範疇だ。 良い質問は、良い回答を引き出す。ガベージ・イン、ガベージ・アウトである。 プロンプトの例って、よく紹介されてるけど、そこから良い質問を見い出すのは 存外と難しいぞ。

あと、勧められたのは、複数のAIに質問をして、比較してみるってのも賢い 使い方らしい。ハルシネーションの防止にもなるしね。

GitHubで人気の言語ランキング「PythonがTypeScriptに抜かれた」ことの意味

確かに型が有ったら、勝手な捏造はしにくくなるだろう。確認も容易になるしね。

サバイバルTypeScript


This year's Index

Home