guile in geiser

1 2 3 と来たら、次の数字は何? そりゃ、4でしょ。ブブー。次は、11 12 13 ... でもいいし、10 20 30 ... でも良い。と言う事で、正解は、数列クイズに正解無しでした。

初期の『数学ガール』の冒頭付近に載ってた、肩慣らしでした。オイラーも引っかかってしまったよ。日頃オイラーを自称してるオイラーも肩無し。

何の断りも無しにオイラーって自称を使ってきたけど、ここらで説明しとく。昔々の現役時代、出張である所に長期滞在をしてた。そこには営業所があり、そこの営業マンと仲良くなった。社会勉強と称して、色々な所に飲みに連れて行ってもらった。

その彼が、神田の生まれ、自分の事をオイラって呼んでた。時は流れ、その彼を敬愛するオイラーは、オイラと数学の天才オイラーが似てる事に気付いた。オイラーみたいになりたいな。せめて名だけでもって事で、オイラーさ。と言う、再帰的な定義でした。

関数 sin(x)が、下記のようなべき級数に展開出来ると仮定する。この時の数列 (ak) を求めよ。

        ∞
sin x = Σ ak x^k
        k=0
	
mean

sin x = a0 + a1 x^1 + a2 x^2 + a3 x^3 + ...

関数を研究する最強の武器の一つに微分が有るそうな。微分ってのは、関数から関数を作り出す計算。そう捉えるんですか!

天下りで、sin/cos の微分は、下記のようなループと言うか、繰り返しになるそうな。

sin x  ->  cos x  ->  -sin x  ->  -cos x  ->  sin x ...

後で証明を見ておけ。

(sin x)' = (a0 + a1 x^1 + a2 x^2 + a3 x^3 + ...)'

cos x = a1 + 2a2 x^1 + 3a3 x^2 + 4a4 x^3 + ...

次はcosを微分と繰り返して行くと、しまいには

          x^1   x^3   x^5   x^7
sin x = + --- - --- + --- - --- + ...
           1!    3!    5!    7!

このようになるそうな(途中は超省略)。

これをテーラー展開と言うとか。 逆に考えると、右辺の数列に名前を付けた。そう、sin って名前ね。 そういう見方は、新鮮だな。

(%i4) taylor(sin(x), x, 0, 20);
              3    5      7       9        11          13             15
             x    x      x       x        x           x              x
(%o4)/T/ x - -- + --- - ---- + ------ - -------- + ---------- - -------------
             6    120   5040   362880   39916800   6227020800   1307674368000
                                          17                 19
                                         x                  x
                                 + --------------- - ------------------ + . . .
                                   355687428096000   121645100408832000

maximaで、sin(x)を20次まで展開してみた。

テイラー展開

テイラー展開・マクローリン展開の証明と使い方

more guile

scheme@(guile-user)> (apropos "pretty")
(ice-9 pretty-print): pretty-print      #<procedure pretty-print (obj #:optional port* #:key port width max-expr-width display? per-line-prefix)>

勘でaproposしたんよ。そしたら、綺麗に印字出来ますってさ。これは、.guileに入れとけ。

scheme@(guile-user)> (apropos "gc")
(guile): gc-disable     #<procedure gc-disable ()>
(guile): gc-stats       #<procedure gc-stats ()>
(guile): logcount       #<procedure logcount (_)>
(guile): after-gc-hook
(guile): gc-enable      #<procedure gc-enable ()>
(guile): gcd    #<procedure gcd (#:optional _ _ . _)>
(guile): gc-run-time    #<procedure gc-run-time ()>
(guile): gc     #<procedure gc ()>
(guile): gc-dump        #<procedure gc-dump ()>

guileは、追加しなくても、元から使えるのだろうね。基本部分だからさ。

scheme@(guile-user)> (gc-stats )
$3 = ((gc-time-taken . 75) (heap-size . 4485120) (heap-free-size . 1708032) (heap-total-allocated . 14350688) (heap-allocated-since-gc . 249832) (protected-objects . 0) (gc-times . 10))
scheme@(guile-user)> (pretty-print (gc-stats ))
((gc-time-taken . 75)
 (heap-size . 4485120)
 (heap-free-size . 1425408)
 (heap-total-allocated . 14887736)
 (heap-allocated-since-gc . 786880)
 (protected-objects . 0)
 (gc-times . 10))

早速、綺麗に見易く表示。

scheme@(guile-user)> (apropos "iota")
(guile): iota   #<procedure iota (count #:optional start step)>
scheme@(guile-user)> (iota 3 10)
$4 = (10 11 12)

数列発生器。この等差発生器は素直です。途中で思わぬジャンプなんてありません。使い方が一発で分かるように、素敵な引数名が伺えます。

まて、これって、emacsでguiserを使ってコードを書いている時に、案内に出て来る奴とちゃうかな?

guile in geiser

guileのホームページには、有志提供のモジュールやアプリが掲載されているけど、オイラーの箏線に触れる物は残念ながら無かった。(有志の方、ごめんなさい)

で、身近にあるguileのアプリと言えば、geiserで決まりだ。こう書くと、道具に入れ込んでしまい、さっぱり仕事をしない職人さんと思われるだろうけど、事実なので否定はしない。

emacs/init.elに、下記を登録しておくと、最新のパッケージを導入出来る。

(add-to-list 'package-archives
            '("melpa" . "http://melpa.milkbox.net/packages/") t)

geiser-20200128.1856/ が、入った。guile-3.0に対応してるのかな? 2.2以上をお勧めって書いてあったから、多分大丈夫。guile側のコードは、

debian:geiser$ wc *
   27   122  1015 completion.scm
  258   996  9693 doc.scm
   58   237  2093 emacs.scm
  144   420  5269 evaluation.scm
  104   368  3577 modules.scm
   52   193  1593 utils.scm
   84   301  2926 xref.scm
  727  2637 26166 total

このぐらいだから、見て行くには丁度良いサイズだろう。取っ掛かりは、

debian:geiser$ grep apropos *
completion.scm:    (sort! (map symbol->string (apropos-internal prefix)) string<?)))
modules.scm:           (apropos-fold (lambda (module name var init)
modules.scm:                             (apropos-fold-accessible (current-module))
modules.scm:                             apropos-fold-all)))
/usr/local/bin/guile -q -L /home/sakae/.../geiser-20200128.1856/scheme/guile/

emacsのバックエンドで起動してるguile。man guileすると

       -L DIRECTORY
              Add DIRECTORY to the front of Guile's module load path.

       -q     In interactive mode, suppress loading the user's  initialization
              file, ~/.guile.

こんなオプションで起動させてた。manよりinfoって書いてある。あんたはGNUっ子だなあ。

最初の実行は、geiser-eval--send/waitの引数で行われる。

(defun geiser-guile--startup (remote)
  (set (make-local-variable 'compilation-error-regexp-alist)
       `((,geiser-guile--path-rx geiser-guile--resolve-file-x)
         ("^  +\\([0-9]+\\):\\([0-9]+\\)" nil 1 2)))
      :
    (geiser-eval--send/wait ",use (geiser emacs)\n'done")

C-c C-d C-d geiser-doc-symbol-at-point

defineの所にカーソルを合わせて、上記のキーバインドを発すると、画面が転換して

(define ...)

A macro in module (guile).

[manual] [source]

マニュアルの所にカーソルを合わせてRETすると

Definitions are valid in some, but not all, contexts where expressions
are allowed.  They are valid only at the top level of a <program> and at
the beginning of a <body>.

A definition should have one of the following forms:

   * (define <variable> <expression>)

   * (define (<variable> <formals>) <body>)

こんな風にinfoを引いてくれる。じゃ、期待のsourceの方は?

Couldn’t find edit location for ’define’

mini-bufferにスマナソウにこんな報告が出てきた。 このエラーを出してるのは、 geiser-edit.el

(defun geiser-edit--try-edit-location (symbol loc &optional method)
  (let ((symbol (or (geiser-edit--location-name loc) symbol))
        (file (geiser-edit--location-file loc))
        (line (geiser-edit--location-line loc))
        (col (geiser-edit--location-column loc)))
    (unless file (error "Couldn't find edit location for '%s'" symbol))

指定されたloc若しくはsymbolが、どのファイルに存在してるか割り出し(まるで、googleみたいに全てお見通し)、そのファイルへ飛んで行く(飛んでった先で編集するのが主目的)

この場合、運悪くdefineが含まれるファイルが、同一dirに無かったと言う事だな。

geiser/guileのdir内にある、xref.scmのobject-signatureを探してみると

(object-signature name obj)

A procedure in module (geiser doc).

[manual] [source]                                                      [back]

geiser docに照準を合わせてRETすると

(geiser doc)

Procedures:

        - (autodoc ids)
        - (module-exports mod-name)
        - (object-signature name obj)
        - (symbol-documentation sym)


[manual] [source]                                                      [back]

今度はそのモジュールの内情を調べるモードになった。 これはもう、大規模なシステムを開発する時の機能だな。オイラーみたいに10行プログラマー(replの波打ち際で遊ぶ人の事って、悪魔の辞典に書いてある)には、猫に小判、豚に真珠な機能ですよ。

何もしなくても、etagsで飛び回る事が普通に出来ちゃうからね。この機能で十分過ぎてお釣りが来ますよ。

,help geiser

geiserから起動したguileでは、コマンドが下記のように追加されてた。

scheme@(guile-user)> ,help geiser
Geiser Commands [abbrev]:

 ,geiser-no-values                 - No-op command used internally by Geiser.
 ,geiser-newline                   - Meta-command used by Geiser to emit a new line.
 ,geiser-eval module form args ()  - Meta-command used by Geiser to evaluate and compile code.
 ,geiser-load-file file            - Meta-command used by Geiser to load and compile files.
 ,geiser-start-server              - Meta-command used by Geiser to start a REPL server.

emacs.scmに定義が有った。例えばこれ

(define-meta-command ((geiser-newline geiser) repl)
  "geiser-newline
Meta-command used by Geiser to emit a new line."
  (newline))

気になるdefine-meta-commandは、(system repl command)に所属してるそうです。マニュアルは引けなかったけど、代わりにソース嫁って態度。command.scmの定義があるみたい。

(define-syntax define-meta-command
  (syntax-rules ()
    ((_ ((name category) repl (expression0 ...) . datums) docstring b0 b1 ...)
     (add-meta-command!
      'name
      'category
      (lambda* (repl expression0 ... . datums)
        docstring
        b0 b1 ...)
         :

/usr/local/share/guile/3.0/system/repl/command.scm こんな所まで、監視対象にしてくれているのね。さっぱり知りませんでした。 このファイルには、debug/gc/profiler等の面白い機能が満載なので、見ておくと吉。

ちょいと拡張されたgeiser上のreplで、サーバーを動かしてみる。

scheme@(guile-user)> ,geiser-start-server
(port 34605)
scheme@(guile-user)> In thread:
Wrong type to apply: ()

scheme@(guile-user)>

起動すると、ポート番号を教えてくれるので、別端末からncとかを使って接続してみればよい。

(base) [sakae@c8 tmp]$ nc localhost 34605
GNU Guile 3.0.0
 :
scheme@(guile-user)> (+ 1 2 3 4)
$4 = 10
scheme@(guile-user)> (exit)

使い終わったらら(exit)でguileとの接続を切り、ncをCtrl-dで終了させれば良い。 threadで動いているので、切断されると、emacs側のreplにもその旨が出て来るけど、継続して使える。

comm

emacsからguile側にどんなコマンドが流れるのだろう? 昔を思い出して、パケットをさらけ出し、モニターしてみる。

ob$ guile --listen
GNU Guile 2.2.6
 :

こんな具合に先にサーバーを立てる。次に、 M-x geiser-connectして、セッションを開始。 題材は、

(use-modules (system foreign))

(define gettimeofday
  (let ((f (pointer->procedure
            int
            (dynamic-func "gettimeofday" (dynamic-link))
            (list '* '*)))
        (tv-type (list long long)))
    (lambda ()
      (let* ((timeval (make-c-struct tv-type (list 0 0)))
             (ret (f timeval %null-pointer)))
        (if (zero? ret)
            (apply values (parse-c-struct timeval tv-type))
            (error "gettimeofday returned an error" ret))))))

こんな、ダイナミックリンクな奴。

ob$ doas tcpdump -i lo0 -w LOG port 37146

現場で盗聴開始。ログが収録出来たら、wireshark LOG してから Analyze -> Follow -> Tcp Stremeメニューで、まとめてもらう。

(gettimeofday)
$7 = 1580680373
$8 = 243701

結果は、こんなものだったよ。サーバー側のreplから、今何時関数を発しても、同様だった。 考えてみるに、emacsが出て来る要素は無いわな。

M-x debug-on-entry geiser-eval--send/waitで待ち構えていて、最後のコッカを入力すると

Debugger entered--entering a function:
* geiser-eval--send/wait((:eval (:ge module-completions "(gettimeofday)")) nil $
  geiser-completion--complete("(gettimeofday)" t)
  geiser-company--prefix-at-point()
  geiser-company-backend(prefix)
   :

しょうがないので、M-x debug-on-entry して、今度は、geiser-repl--maybe-send なんてのを選んでみた。多分 maybe でしょうと言う、いい加減な理由。

Debugger entered--entering a function:
* geiser-repl--maybe-send()
  funcall-interactively(geiser-repl--maybe-send)
  call-interactively(geiser-repl--maybe-send nil nil)
  command-execute(geiser-repl--maybe-send)

ビンゴでしたねぇ。いい感だ。いや勘だ。 geiser-repl.el

(defun geiser-repl--maybe-send ()
  (interactive)
  (let ((p (point)))
    (cond ((< p (geiser-repl--last-prompt-start))
           (if (geiser-repl--is-input)
               (geiser-repl--grab-input)
             (ignore-errors (compile-goto-error))))
          ((let ((inhibit-field-text-motion t))
             (when geiser-repl-send-on-return-p
               (end-of-line))
             (<= (geiser-repl--nesting-level) 0))
           (geiser-repl--send-input))
          (t (goto-char p)
             (if geiser-repl-auto-indent-p
                 (geiser-repl--newline-and-indent)
               (insert "\n"))))))

この関数で、コッカの後ろにRETが補われ、guileに送る関数が確定。その一行を、command-executeに委ねて実行されるんだな。 command-executeは、simple.elに置いてある汎用コマンドだ。

余りコードも読まないで勘とemacsのdebug機能を使って、ここまで追い詰められたって素敵だな。これだから、Hackingは止められない。

C-c C-d C-m geiser-doc-module

モジュールを手軽に調べられる。

上記のキー入力でminibufferに質問が出て来るので、それに答えればよい。

Module name: (system foreign)

モジュール名を入力中も補完が効くので、へぇーこんなモジュールが有るんだと驚いて下さい。 下記は、一例。

(system foreign)

Procedures:

        - (alignof type)
        - (bytevector->pointer bv (offset))
        - (dereference-pointer pointer)
        - (make-c-struct types vals)
            :
        - (sizeof type)
        - (string->pointer string (encoding))

Syntax:

        - (define-wrapped-pointer-type ...)

Variables:

        - %null-pointer
        - double
            :
        - unsigned-short
        - void


[manual] [source]                                                       [back]

俯瞰するのには便利だ。