R言語 (5)
scheme by haskell
前回はRをgdbした。そしたら、CADR とかそれっぽいのが出てきた。んな事で、少し遊んでみる。
久しぶりにhaskellさんの所へ行ったら、48時間でschemeを作りましょに刺激されて、新しいのを作ったぞワァーイってのが陳列されててた。
stackが全て面倒見てくれるから、cabal地獄は心配しなくていいよとこ事。なぞるだけなんだけど、Fork me on github のリンクを辿って、一式取り寄せ。
./build って叩くと、stackがちょっと古びたghcを取ってきてコンパイルを始めてくれる。しばし時間がかかるので、お茶でもしながら待とう。
インストール中にpandocなんてモジュールを用意してたぞ。何処かに聞いた名前だってんで、調べると
多様なフォーマットに対応!ドキュメント変換ツールPandocを知ろう
なる程、文法解析をhaskellのライブラリィに任せて、自在に文書フォーマット変換が出来るのね。正しいHaskellの使い方だな。
sakae@pen:~/hs-scheme$ stack exec docs # pandoc (for output/docs/home.wiki) # pandoc (for output/docs/10_conclusion.wiki) : # pandoc (for output/docs/00_overview.wiki) # pandoc (for output/scheme.html) Build completed in 0:02m
何となく変換してるっぽい。それよりdebugモードで走らせろ。
sakae@pen:~/hs-scheme$ stack repl * * * * * * * * The main module to load is ambiguous. Candidates are: 1. Package `scheme' component scheme:exe:docs with main-is file: /home/sakae/hs- scheme/Build.hs 2. Package `scheme' component scheme:exe:scheme with main-is file: /home/sakae/h s-scheme/exec/Main.hs You can specify which one to pick by: * Specifying targets to stack ghci e.g. stack ghci scheme:exe:docs * Specifying what the main is e.g. stack ghci --main-is scheme:exe:docs * Choosing from the candidate above [1..2] * * * * * * * * Specify main module to use (press enter to load none): 2 Loading main module from candidate 2, --main-is /home/sakae/hs-scheme/exec/Main. : [7 of 7] Compiling Main ( /home/sakae/hs-scheme/exec/Main.hs, interpreted ) Ok, modules loaded: Repl, Parser, LispVal, Eval, Cli, Prim, Main. Loaded GHCi configuration from /tmp/haskell-stack-ghci/54e11cc9/ghci-script *Main Cli Eval LispVal Parser Prim Repl> :browse Eval basicEnv :: EnvCtx safeExec :: IO a -> IO (Either String a) runASTinEnv :: EnvCtx -> Eval b -> IO b evalFile :: FilePath -> Data.Text.Internal.Text -> IO () fileToEvalForm :: FilePath -> Data.Text.Internal.Text -> Eval LispVal runParseTest :: Data.Text.Internal.Text -> Data.Text.Internal.Text getFileContents :: FilePath -> IO Data.Text.Internal.Text textToEvalForm :: Data.Text.Internal.Text -> Data.Text.Internal.Text -> Eval LispVal evalText :: Data.Text.Internal.Text -> IO () *Main Cli Eval LispVal Parser Prim Repl> :browse Repl mainLoop :: IO () *Main Cli Eval LispVal Parser Prim Repl> mainLoop Repl> (cdr '(a b c)) (b c)
mainが2つ有るから選べと言われて、schemeを選ぶと、コンパイルされて、モジュールがロードされた。で、Evalのモジュールを確認。それからReplのモジュール内の関数を調べ、それを起動したら、やっとユーザーが使えるreplが顔を出したとな。
*Main Cli Eval LispVal Parser Prim Repl> :browse LispVal data EnvCtx = EnvCtx {env :: LispVal.ValCtx, fenv :: LispVal.FnCtx} type role Eval nominal newtype Eval a = Eval {unEval :: Control.Monad.Trans.Reader.ReaderT EnvCtx IO a} data LispVal = Atom Data.Text.Internal.Text | List [LispVal] | Number Integer | String Data.Text.Internal.Text | Fun IFunc | Lambda IFunc EnvCtx | Nil | Bool Bool data IFunc = IFunc {fn :: [LispVal] -> Eval LispVal} showVal :: LispVal -> Data.Text.Internal.Text data LispException = NumArgs Integer [LispVal] | LengthOfList Data.Text.Internal.Text Int | ExpectedList Data.Text.Internal.Text | TypeMismatch Data.Text.Internal.Text LispVal | BadSpecialForm Data.Text.Internal.Text | NotFunction LispVal | UnboundVar Data.Text.Internal.Text | Default LispVal | PError String | IOError Data.Text.Internal.Text
同じ要領で、Lispの値を確認。値一般、関数の詳細、表示方法、各種の例外ってな事になってる。
rtags
/usr/lib/R/bin/rtags なんて言う、思わせぶりなコマンドが置いてあった。
## rtags -- tag source files recursively in a directory tree ## ## Examples: ## R CMD rtags -o TAGS /path/to/Rsrc/
冒頭に使い方の説明が出てた。C語、R語、Rdファイルに対する、TAGを作ってくれるようだ。
sakae@pen:~$ R CMD rtags -o TAGS /tmp/R-4.0.2/src/ Tagging R/C/Rd files under /tmp/R-4.0.2/src/; writing to TAGS (overwriting)... etags: no input files specified. Try 'etags --help' for a complete list of options. etags: no input files specified. Try 'etags --help' for a complete list of options. etags: no input files specified. Try 'etags --help' for a complete list of options. etags: no input files specified. Try 'etags --help' for a complete list of options. Done
何だろう、このエラー。
sakae@pen:/tmp/R-4.0.2/src$ find . -name paste.R ./library/base/R/paste.R sakae@pen:/tmp/R-4.0.2/src$ grep paste.R TAGS /tmp/R-4.0.2/src/library/base/man/paste.Rd,56
マニュアル用のRdは検出してるけど、R語のやつは無いな。ソースを読んで、余り難しい事はやらない事にした。
sakae@pen:/tmp/R-4.0.2/src$ R CMD rtags Tagging R/C/Rd files under /tmp/R-4.0.2/src; writing to TAGS (overwriting)... etags: no input files specified. Try 'etags --help' for a complete list of options. : sakae@pen:/tmp/R-4.0.2/src$ grep paste.R TAGS /tmp/R-4.0.2/src/library/base/R/paste.R,32 /tmp/R-4.0.2/src/library/base/man/paste.Rd,56
今度は大丈夫。
でも疑問、 素のetagsはR語を知ってるのか? 知らないよな。なら、秘密はスクリプト自身に隠されているはず。丁寧に見て行ったら、
if ${rfiles}; then SCRIPT_DIR=$(dirname $0) echo " require(utils) rtags(path.expand('${SRCDIR}'), pattern = '[.]*\\\\\.[RrSs]$', keep.re = '/R/[^/]*\\\\\.[RrSs]', verbose = ${VERBOSE}, ofile = '${ofile}', append = ${APPEND}, recursive = TRUE)" | "${RPROG}" --slave fi
shellスクリプトの中から、Rにパイプしてる。Rが動いた事を悟られないように、静かに実行してねってモードに指定。なる程ね。
An Etags-like Tagging Utility for R Description: ‘rtags’ provides etags-like indexing capabilities for R code, using R's own parser.
Rの事はRに任せておけ。正しい使い方ですなあ。それから、etagsでファイルが見つからないって警告は、*.hppとかを検索対象に(安全の為に)含めているからだな。あー、すっきりした。
Rprof
rtagsと同列に、便利スクリプトが陳列されてた。そのうちで興味を引いたやつ。
# ${R_HOME}/bin/Rprof -*- sh -*- for processing Rprof() files args= while test -n "${1}"; do args="${args}nextArg${1}" shift done ## NB: Apple's ICU needs LC_COLLATE set when R is started. echo 'tools:::.Rprof()' | R_DEFAULT_PACKAGES=utils LC_COLLATE=C "${R_HOME}/bin/R" --vanilla --slave --args ${args}
ファイルの冒頭がシェバングかと思ったら、微妙に違う。あくまでもRを起動しておいて、そこから動かせってスタンス。
> Rprof( filename = "Rprof.out", append = FALSE, interval = 0.02, memory.profiling = TRUE ) > source("ana.R") > main() > Rprof(NULL)
repl上で、こんな風にプロファイリングする。repl中に結果を見るなら、
summaryRprof( filename = "Rprof.out", memory = "both" )
とかすれば良い。後でじっくり見たいなら
debian:tmp$ R CMD Rprof Each sample represents 0.02 seconds. Total run time: 0.68 seconds. Total seconds: time spent in function and callees. Self seconds: time spent in function alone. % total % self total seconds self seconds name 94.1 0.64 0.0 0.00 "main" 61.8 0.42 0.0 0.00 "mkgr" 58.8 0.40 0.0 0.00 "doTryCatch" 58.8 0.40 0.0 0.00 "tryCatch" 58.8 0.40 0.0 0.00 "tryCatchList" : 2.9 0.02 0.0 0.00 "vctrs::vec_as_names" 2.9 0.02 0.0 0.00 "vec_as_subscript" % self % total self seconds total seconds name 17.6 0.12 17.6 0.12 ".External" 14.7 0.10 14.7 0.10 "lazyLoadDBfetch" 8.8 0.06 14.7 0.10 "exists" 5.9 0.04 5.9 0.04 "cmpSymbolAssign" : 2.9 0.02 2.9 0.02 "scan" 2.9 0.02 2.9 0.02 "structure"
こんな感じでおk。
debian:tmp$ strace -p 4852 -c strace: Process 4852 attached ^Cstrace: Process 4852 detached % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 40.48 0.000017 0 182 rt_sigaction 19.05 0.000008 0 13 _newselect 16.67 0.000007 0 57 write 7.14 0.000003 0 9 pselect6 4.76 0.000002 0 9 read 4.76 0.000002 0 20 ioctl 4.76 0.000002 0 36 rt_sigprocmask 2.38 0.000001 0 2 clock_gettime 0.00 0.000000 0 4 getrusage 0.00 0.000000 0 1 mmap2 ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000042 333 total
リナ系だとstraceをRにアタッチさせて、挙動を調べる方法も有るとな。これ、応用範囲が広そう。ハスけるは、裏で何に時間を喰っているかとかね。簡単に暴けるぞ。
tidyverse
R言語ではじめるプログラミングとデータ分析 なんて本の案内を見ていたら、Tidyverse なんてのを利用してた。既存のやつと何が違うの?
Tidyverse packages 色々なものの詰め合わせセットなのね。
有名らしくて、色々な人が注目してた。そのうち、興味を引いたのは、こちら。 R: tidyverseをふつうに使えるようになる(purrr) (膨大な記事があって、楽しい)
{purrr} mapを導入しよう やっとmapが出てきたんか。foldとかは、どうよ。別の名前で登場してるか。
R -d gdb
面白い物を見つけた。 R本体Cの関数をデバッグする(その1)
再現実験
paste0('hoge', 'fuga') を、 do_paste
で、捉えてみた。まずは、構造。
(gdb) p R_inspect(args) @16f98a8 02 LISTSXP g0c0 [] @16f93a4 19 VECSXP g0c1 [] (len=2, tl=0) @16f947c 16 STRSXP g0c1 [NAM(3)] (len=1, tl=0) @16f94e8 09 CHARSXP g0c1 [gp=0x60] [ASCII] [cached] "hoge" @16f9410 16 STRSXP g0c1 [NAM(3)] (len=1, tl=0) @16f94a0 09 CHARSXP g0c1 [gp=0x60] [ASCII] [cached] "fuga" @8816b8 00 NILSXP g0c0 [MARK,NAM(3)] $11 = (struct SEXPREC *) 0x16f98a8
そして、中身を確認。
(gdb) p Rf_PrintValue(args) [[1]] [[1]][[1]] [1] "hoge" [[1]][[2]] [1] "fuga" [[2]] NULL
リスト構造が伺えるな。
gdb 起動の仕組み
例では、いきなり裸のgdbが起動したけど、仕組みはどうなってる? 自前でコンパイルしたRが、~/MINIE/bin に入れてあるので、追跡調査。
debian:~$ cd MINE/bin/ debian:bin$ sh -x R -d gdb : + exec gdb --args /home/sakae/MINE/lib/R/bin/exec/R GNU gdb (GDB) 9.1 Copyright (C) 2020 Free Software Foundation, Inc. : Reading symbols from /home/sakae/MINE/lib/R/bin/exec/R... (gdb)
shellを -x でトレース。shの最後に Rの実体を引数にしてgdbを起動させてるのね。
(gdb) b main Breakpoint 1 at 0x2c579: file Rmain.c, line 28. (gdb) r Starting program: /home/sakae/MINE/lib/R/bin/exec/R [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". Breakpoint 1, main (ac=1, av=0xbffff384) at Rmain.c:28 28 Rf_initialize_R(ac, av);
なる程、 Rf_xxx
ってのは、ユーザーが使う関数( do_yyy
)以外の、裏方仕事関数なんだな。
shのトレースで、重要そうな環境引数が列挙されてた。
+ R_HOME=/home/sakae/MINE/lib/R + LD_LIBRARY_PATH=/home/sakae/MINE/lib/R/lib:/usr/local/lib:/usr/lib/i386-linux- gnu:/usr/lib/jvm/java-11-openjdk-i386/lib/client + R_binary=/home/sakae/MINE/lib/R/bin/exec/R
これらをexportしておいて、いきなりemacs 上で、M-x gdb して、
Run gdb (like this): gdb -i=mi $R_binary
こんな風に、gdbを動かせるぞ。
それから、こういうのも有る。 見えないRの関数のソースコードを読む
Rscript
Rscriptはバイナリーファイルになってる。Rとどういう関係なの?
debian:tmp$ echo 'print("Hello R")' > test.R debian:tmp$ gdb -q Rscript Reading symbols from Rscript... (gdb) b main Breakpoint 1 at 0x1120: file ./Rscript.c, line 124. (gdb) r test.R Starting program: /home/sakae/MINE/bin/Rscript test.R Breakpoint 1, main (argc_=2, argv_=0xbffff444) at ./Rscript.c:124 124 if(argc_ <= 1) { (gdb) c Continuing. process 6638 is executing new program: /bin/bash Breakpoint 1, 0x0041fe40 in main () (gdb) c Continuing. [Detaching after fork from child process 6680] [Detaching after fork from child process 6681] process 6638 is executing new program: /home/sakae/MINE/lib/R/bin/exec/R [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1". Breakpoint 1, main (ac=4, av=0xbffff314) at Rmain.c:28 28 Rf_initialize_R(ac, av);
ふむ、shで書かれたRの実体呼び出しに変わって、バイナリ版なのね。何故こうする必要が有る? スピードの問題?
src/unix/Rscript.c に答えは有るかな?
/* This is intended to be used in scripts like #! /path/to/Rscript --vanilla commandArgs(TRUE) q(status=7) This invokes R with a command line like R --slave --no-restore --vanilla --file=foo [script_args] */ /* execv exists and can be used on Windows, but it returns immediately and so the exit status is lost. HAVE_EXECV is defined under Windows. The main reason for using execv rather than system is to avoid argument quoting hell. */
Rでの引数受け取り方法が、地獄を見るって事?