guile
Table of Contents
guile udp
前回から、schemeを始め(再開)てみた。gauche一択でいいんだけど、OpenBSD とかではコンパイル失敗が決定してるんで、guileです。こちらがその The Guile Reference Manual です。
見様見真似で、UDPなパケットを投げるコード片を作成。
(use-modules (rnrs bytevectors)) (let ((s (socket AF_INET SOCK_DGRAM 0))) (connect s AF_INET (inet-pton AF_INET "127.0.0.1") 5555) (send s (string->utf8 "Hello OpenBSD\r\n") ))
最初は、途中でハングですよ。んで、なんでかなとネットを漁ってみたのさ。 そしたら、schemeとは直接関係ないけど、面白いものが出てきた。一期一会に なりそうなのでメモっておく。
お手本から変更した部分は、sendの所だ。変更した部分が怪しい。infoすると 、
-- Scheme Procedure: send sock message [flags]
-- C Function: scm_send (sock, message, flags)
Transmit bytevector MESSAGE on socket port SOCK. SOCK must already
be bound to a destination address. The value returned is the
number of bytes transmitted_it's possible for this to be less than
the length of MESSAGE if the socket is set to be non-blocking. The
optional FLAGS argument is a value or bitwise OR of `MSG_OOB',
`MSG_PEEK', `MSG_DONTROUTE' etc.
Note that the data is written directly to the socket file
descriptor: any unflushed buffered port data is ignored.
メッセージはバイトベクターだよってのを軽く考えていて、#"Hello.." とし てたんだ。これでもエラーにならずにハングになっていたので、UDP独自の作 法が有るかと、さ迷った次第。改めてバイトベクターを調べて、事無きを得た 次第。ちょいと悔しいので、ソース読めしてみます。
libguile/socket.c
SCM_DEFINE (scm_send, "send", 2, 1, 0,
(SCM sock, SCM message, SCM flags),
"Transmit bytevector @var{message} on socket port @var{sock}.\n"
:
"This operation is defined only for strings containing codepoints\n"
"zero to 255.")
#define FUNC_NAME s_scm_send
{
int rv, fd, flg;
sock = SCM_COERCE_OUTPORT (sock);
SCM_VALIDATE_OPFPORT (1, sock);
if (SCM_UNBNDP (flags))
flg = 0;
else
flg = scm_to_int (flags);
fd = SCM_FPORT_FDES (sock);
SCM_VALIDATE_BYTEVECTOR (1, message);
SCM_SYSCALL (rv = send (fd,
SCM_BYTEVECTOR_CONTENTS (message),
SCM_BYTEVECTOR_LENGTH (message),
flg));
if (rv == -1)
SCM_SYSERROR;
scm_remember_upto_here_1 (message);
return scm_from_int (rv);
}
*.go
/usr/local/bin/guild3.0なんてのが有るんだけど、manでは言及されていない。 でも、helpしたら、どうもコンパイル系って事が分ったので、ちと実験。
ob$ cat >z.scm (define (bai+ a b)(+ a (* b b))) ob$ guild3.0 compile z.scm wrote `/home/sakae/.cache/guile/ccache/3.0-LE-4-4.6/home/sakae/z.scm.go'
超簡単な関数を定義して、それをコンパイル。サフィックスがgoになるんか。
ob$ file z.scm.go z.scm.go: ELF 32-bit LSB shared object, no machine, version 1
ふーん、ELFフォーマットになるっては、良い選択と思うよ。
ob$ readelf -h /usr/local/bin/gdb ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: Intel 80386 :
アプリのELFヘッダーの例
ob$ readelf -h z.scm.go ELF Header: Magic: 7f 45 4c 46 01 01 01 ff 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian Version: 1 (current) OS/ABI: Standalone App ABI Version: 0 Type: DYN (Shared object file) Machine: None :
そして、こちらは、コンパイルされたコードのヘッダー例。emacsやsbclみた いに、独自形式を放棄してロードとかの扱かいを標準化するって見識が有るな あ。
で、逆変換もで きるみたいなんで、試してみる。
ob$ guild3.0 disassemble /home/sakae/.cache/guile/ccache/3.0-LE-4-4.6/home/sakae/a.scm.go Disassembly of <unnamed function> at #x98: 0 (instrument-entry 16354) at z.scm:1:0 2 (assert-nargs-ee/locals 1 1) ;; 2 slots (0 args) 3 (call-scm<-thread 1 62) ;; current-module 5 (static-ref 0 16345) ;; bai+ 7 (call-scm<-scm-scm 1 1 0 41) ;; define! 9 (make-non-immediate 0 16343) ;; #<procedure bai+ (a b)> 11 (scm-set!/immediate 1 1 0) 12 (make-immediate 1 2052) ;; #<unspecified> 13 (reset-frame 1) ;; 1 slot 14 (handle-interrupts) 15 (return-values) Disassembly of bai+ at #xd8: 0 (instrument-entry 16342) at z.scm:1:0 2 (assert-nargs-ee/locals 3 0) ;; 3 slots (2 args) 3 (call-scm<-scm-scm 2 0 0 4) ;; mul at z.scm:1:23 5 (call-scm<-scm-scm 2 1 2 0) ;; add at z.scm:1:18 7 (reset-frame 1) ;; 1 slot 8 (handle-interrupts) 9 (return-values) Disassembly of <unnamed function> at #x100: 0 (instrument-entry 16336) at z.scm:1:18 2 (assert-nargs-ee/locals 1 1) ;; 2 slots (0 args) 3 (static-patch! 16318 15) 6 (make-non-immediate 1 16314) ;; "bai+" 8 (call-scm<-scm 1 1 14) ;; string->symbol 10 (static-set! 1 16314) ;; bai+ 12 (static-patch! 16315 -22) 15 (reset-frame 1) ;; 1 slot 16 (make-immediate 0 2052) ;; #<unspecified> 17 (return-values)
仮想マシンのコードなんだな。じゃ、同じ関数をインタープリタ上で定義して、 それを逆アセンブルしてみる。
scheme@(guile-user)> (define (bai+ a b)(+ a (* b b))) scheme@(guile-user)> ,x bai+ Disassembly of #<procedure bai+ (a b)> at #x6c9218d8: 0 (instrument-entry 56) at (unknown file):1:0 2 (assert-nargs-ee/locals 3 0) ;; 3 slots (2 args) 3 (call-scm<-scm-scm 2 0 0 4) ;; mul at (unknown file):1:23 5 (call-scm<-scm-scm 2 1 2 0) ;; add at (unknown file):1:18 7 (reset-frame 1) ;; 1 slot 8 (handle-interrupts) 9 (return-values)
,c bai+ すれば、その場でコンパイルもできるのね。
debug, trace and profile …
replから、簡単にオペレーションできるdebuggerが用意されてる。
debug
scheme@(guile-user)> ,help debug
Debug Commands [abbrev]:
,backtrace [COUNT] [#:width W] [#:full? F]
[,bt] - Print a backtrace.
,up [COUNT] - Select a calling stack frame.
,down [COUNT] - Select a called stack frame.
,frame [IDX] [,fr] - Show a frame.
,locals - Show local variables.
,error-message [,error] - Show error message.
,break PROCEDURE [,br ,bp] - Break on calls to PROCEDURE.
,break-at-source FILE LINE
[,break-at ,bs] - Break when control reaches the given source location.
,step [,s] - Step until control reaches a different source location.
,step-instruction [,si] - Step until control reaches a different instruction.
,next [,n] - Step until control reaches a different source location in the current frame.
,next-instruction [,ni] - Step until control reaches a different instruction in the current frame.
,finish - Run until the current frame finishes.
,tracepoint PROCEDURE [,tp] - Add a tracepoint to PROCEDURE.
,traps - Show the set of currently attached traps.
,delete IDX [,del] - Delete a trap.
,disable IDX - Disable a trap.
,enable IDX - Enable a trap.
,registers [,regs] - Print registers.
残念な事に、このdebuggerではプリミティブな関数にはbreakが反応しない。 で、ちょっと細工をする。
(define (my* x y) (* x y)) (define (bai+ a b)(+ a (my* b b)))
何の事は無い。* をラッピングしただけ。そうしておいてmy*にBPを追いて実 行。
scheme@(guile-user)> ,br my*
Trap 0: Breakpoint at #<procedure my* (x y)>.
scheme@(guile-user)> (bai+ 3 4)
Trap 0: Breakpoint at #<procedure my* (x y)>
Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue.
scheme@(guile-user) [1]> ,bt
In current input:
10:39 1 (bai+ 3 _)
8:16 0 (my* 4 4)
scheme@(guile-user) [1]> ,q
$6 = 19
breakってTrapの仮の姿なのね。ちょっと新鮮な面持ちだよ。主関数を実行す ると、トラップが発生する。そしてreplは一段深いレベルに移行。バックトレー スしてみると、呼出の履歴が表示された。継続するには、replの深い階層から 抜け出る事って事で、,qを入力。フレーム番号1の所の _ は、まだ未評価です よって印なんだな。
trace
トレースも取れーす。
scheme@(guile-user)> (define (fact1 n)
(if (zero? n) 1
(* n (fact1 (1- n)))))
scheme@(guile-user)> ,trace (fact1 3)
trace: | (fact1 3)
trace: | | (fact1 2)
trace: | | | (fact1 1)
trace: | | | | (fact1 0)
trace: | | | | 1
trace: | | | 1
trace: | | 2
trace: | 6
一度 ,tr 設定した関数を解除する方法は提供されてないみたい。まあ、トレー スしなけりゃ、それで済む話だけどね。
scheme@(guile-user)> ,time (fact1 30) $10 = 265252859812191058636308480000000 ;; 0.034000s real time, 0.026000s run time. 0.000000s spent in GC.
profile
プロファイルも簡単に採取できる。
(use-modules (statprof))
(statprof (lambda ()
(map 1+ (iota 500000))
#f))
こんな、ありがちなコードをrepl上から実行。
Loading /tmp/z.scm ... % cumulative self time seconds seconds procedure 57.50 54565.94 0.18 ice-9/boot-9.scm:220:5:map1 20.00 0.06 0.06 1+ 17.50 0.07 0.05 ice-9/boot-9.scm:904:0:iota 5.00 0.02 0.02 %after-gc-thunk 0.00 0.31 0.00 /tmp/z.scm:2:15 0.00 0.02 0.00 anon #x68a610f4 --- Sample count: 40 Total time: 0.31 seconds (2.081 seconds in GC)
後、repl上の統計情報
scheme@(guile-user)> ,stat
GC times: 30 / 33 times
Memory size: current / limit
heap 243286016 / 369631232 bytes
Bytes allocated: diff / total
allocated 930464488 / 932018324 bytes
GC time taken: diff / total
total 2.43 / 2.44 s
Process time spent: diff / total
user 27.69 / 27.77 s
system 0.70 / 0.70 s
child user 0.00 / 0.00 s
child system 0.00 / 0.00 s
pp
コンパイル時の情報が保持されてる。
scheme@(guile-user)> %guile-build-info $6 = ((buildstamp . "") (CFLAGS . " -I/usr/local -I/usr/local/include -pthread") (LIBS . " -lm") ...
が、得意のアレになってて見るに耐えない。そんな時は、綺麗に表示してよね、 だ。
scheme@(guile-user)> ,pp %guile-build-info $7 = ((buildstamp . "") (CFLAGS . " -I/usr/local -I/usr/local/include -pthread") (LIBS . " -lm") (libguileinterface . "7:0:6") (guileversion . "3.0.9") (extensiondir . "/usr/local/lib/guile/3.0/extensions") (pkgincludedir . "/usr/local/include/guile") (pkglibdir . "/usr/local/lib/guile") : (prefix . "/usr/local") (top_srcdir . "/pobj/guile3-3.0.9/guile-3.0.9") (srcdir . "/pobj/guile3-3.0.9/guile-3.0.9/libguile"))
lang
scheme@(guile-user)>
今迄余り気にする事が無かったけれど、replのプロンプトにschemeって表示し てるのは何故? 気になったので調べてみると、
6.24 Support for Other Languages ================================ In addition to Scheme, a user may write a Guile program in an increasing number of other languages. Currently supported languages include Emacs Lisp and ECMAScript.
欲張りな事に、scheme以外にelispとJavaScriptもサポートしてるようだ。
scheme@(guile-user)> ,L elisp Happy hacking with Emacs Lisp! To switch back, type `,L scheme'.
elispに変身する。
elisp@(guile-user)> (eq 1 2) $1 = #f elisp@(guile-user)> ,L scheme Happy hacking with Scheme! To switch back, type `,L elisp'. scheme@(guile-user)> (eq 1 2) ;;; <stdin>:5:1: warning: possibly unbound variable `eq' ice-9/boot-9.scm:1685:16: In procedure raise-exception: Unbound variable: eq
elispにはeqって関数が存在するので、ちゃんと利用できる。でもscheme側に は、そんな関数が存在しないのでエラーになった。
scheme@(guile-user) [1]> ,q scheme@(guile-user)> (= 1 2) $2 = #f
脳内変換しろよとな。脳が沸騰しそうなので、封印しとく。
(guile-user)って表示されてるプロンプトの部分は?
scheme@(guile-user)> ,use (ice-9 readline) scheme@(guile-user)> ,m (ice-9 readline)
guile in C
皆さん、どんな風にguileを利用してるか調べてみた。
Guile 入門(pdf)
これいいな。最終的にはguileの核になってるlibguileを使うしか。組み込み の世界。作図ソフトで有名なgimpだったかもも、取り込んでいたはず。
guileの歴史によると、emacsをguileで実現したかったみたいだ。Lisp-2より Lisp-1であるschemeの方が綺麗だしね。でも諸般の事情で実現できなかったみ たい。
そんな不幸な例は、巷に溢れているね。例えば、西日本の60Hzと東日本の50Hz 問題。だれがどう考えたって不合理なのに、統一の機会を逸してしまった。ま あ、そんなものか。
CPS
Q: コンパイラーの技法の一つであるCPS変換について、説明して ください。
A: CPS(Continuation-Passing Style)変換は、プログラムの制御フローを明示的な継続(continuation)をパラメータとして受け取る形式に変換する技法です。CPS変換はコンパイラーの最適化や高度な変換技法の基盤としても広く利用されています。以下に、CPS変換の基本的なアイデアとその利点、適用例について詳しく説明します。
### 基本的なアイデア
CPS変換の基本的な考え方は、関数呼び出しの戻り値として「次に実行すべき処理」を示す継続を明示的に渡すことです。通常の関数呼び出しでは、関数が計算を行い、その結果を返します。しかし、CPS変換を適用すると、関数は結果を継続に渡し、制御を次の処理に移すようになります。
例えば、通常の関数呼び出し `f(x)` の場合、CPS変換後は `f(x, k)` となります。ここで `k` は継続を表します。`f` は計算を行った後、結果を `k` に渡して次の処理を引き継ぎます。
### 利点
- 明示的な制御フロー: CPS変換によって、プログラムの制御フローが明示的になります。これにより、コンパイラーはより効率的な最適化が可能となります。
- 再帰の単純化: CPS変換を使用すると、再帰的な関数呼び出しをループや明示的な継続呼び出しに変換できます。これにより、再帰を扱うコードの最適化が容易になります。
- 高度な変換: CPS変換は、コルーチンや非同期プログラミング、例外処理など、高度なプログラム構造の変換にも適用できます。
### 適用例
以下に、簡単なC言語の関数をCPS変換する例を示します。
通常の関数: ```c int add(int a, int b) { return a + b; } ```
CPS変換後: ```c void addcps(int a, int b, void (*k)(int)) { k(a + b); } ```
この例では、`addcps`関数は結果を継続 `k` に渡しています。継続 `k` は、計算結果を受け取り次の処理を行う関数を表します。
### まとめ
CPS変換は、プログラムの制御フローを明示的にし、コンパイラーの最適化や高度な変換技法の基盤として非常に有用です。明示的な継続を使用することで、再帰的なコードの最適化や高度なプログラム構造の変換が容易になります。CPS変換は、コンパイラー設計やプログラム解析、最適化の研究で広く利用されており、その重要性は今後も増していくでしょう。