Scheme:初心者の質問箱
メーリングリストで質問したり、WiLiKiに自分のページを作ったりするのはちょっと… というシャイなあなたのためのスペースです。
あたらしい質問は、項目の先頭に追加していって下さい。
書き方を間違えても小人さんが直してくれるので、 こわがらなくてもだいじょうぶ。
長くなってきたので過去ログ:
- プログラムの中でGaucheのバージョンを得る方法
- raise したコンディションを捕捉できない?
- <class>オブジェクトを得る方法
- オブジェクトシステムで next-method がない場合のエラーに関して
- displayタイプの表示のpprint
- tls の使用
- hash-table の変更
- get-gauche.sh でのテストエラーに関して
- Windowsにおけるtools/build-standaloneについて
- tools/build-standaloneについて
- \0 区切りの文字列の read
- 対話的に起動した際、使用する文字コードを指定することは可能でしょうか
- 対話的に起動した際、C-r で履歴をインクリメンタルに検索する設定は可能でしょうか
- R7RSモードでの起動時の-lで指定したロードファイルの挙動について
- SRFI-49で開きカッコがネストしている場合の書き方
- 連想リスト(Association list)の形式について
- バックグラウンドで動かすにはどうすれば良いでしょうか
- Typo報告
- delete! の作用について
- shutdown 後に socket-status が返す値
- while 内の唐突な代入について
- Gauche の、Racket との違いと評価順序について
- ブロック内で call/cc とその他を並列にしたときの継続の中身
プログラムの中でGaucheのバージョンを得る方法
質問です。プログラムの中でGaucheのバージョンを得る方法はありますでしょうか。 スクリプトを実行するときに、そのスクリプトをバージョンいくつ以上で実行して欲しい、という使い方をしたいです。
- 齊藤(2025/03/11 23:54:58 UTC): Gauche のバージョンを得る手続きは implementation-version または gauche-version です。
- ありがとうございます。自力で見つけられなかったので早速のご回答助かりました m(__)m
- Shiro(2025/03/13 17:23:00 UTC): goshのプロンプトから正規表現でドキュメント検索ができます。ご参考まで。
gosh> ,doc #/\bversion\b/ gauche-version Environment inquiry:94 gauche-version of <gauche-package-description> Package metainformation:140 gauche.version Comparing version numbers:7 gdbm-version GDBM interface:105 implementation-version Portable runtime environment inquiry:13 ip-version IP packets:20 mime-parse-version MIME message handling:28 os-version Portable runtime environment inquiry:31 uuid-version UUID:22 valid-version-spec? Comparing version numbers:128 version of <gauche-package-description> Package metainformation:103 version-alist Environment inquiry:103 version-compare Comparing version numbers:112 version-satisfy? Comparing version numbers:134 version<=? Comparing version numbers:104 version<? Comparing version numbers:103 version=? Comparing version numbers:102 version>=? Comparing version numbers:106 version>? Comparing version numbers:105 zlib-version Zlib compression library:241
- なるほど、,docや ,info を正規表現と使うとうまく検索できそうですね。ありがとうございます。
raise したコンディションを捕捉できない?
質問の連投すみません。例外処理について質問させてください。 以下のように独自に定義した <new-condition> コンディションをguardの本体のところで例外として発生させて捕捉したいと考えています。ドキュメントを読んで、以下のようなシンプルな場合は、raiseもerrorも似たように使えるのかなと思ったのですが、errorした場合は捕捉される ("reporting <new-condition>"と表示される)のですが、raiseした場合は*** ERROR: unhandled exception: #<class <new-condition>> となり捕捉できていないようです。
ドキュメントの読み方が悪いのかもしれませんが、raise を使った場合うまく捕捉できない理由を教えていただけませんでしょうか。
(import (scheme base) (gauche base) (scheme write)) (define-condition-type <new-condition> <condition> new-condition?) (guard (e [(condition-has-type? e <new-condition>) (display "reporting <new-condition>")(newline)(flush) #f]) ;; (error <new-condition>) ;; properly handled (raise <new-condition>) )
- 齊藤(2024/12/06 06:35:21 UTC): <new-condition> は <new-condition> という型 (を表すオブジェクト) であって <new-condition> 型に属するオブジェクトではないからです。
(raise (make <new-condition>))
というようにすれば期待する動作になると思います。
- [質問者] なるほど、conditionの型をmakeしたものをraiseする必要があるのですね。ありがとうございます。たしかにドキュメントみるとerror関数とraise関数の引数はそれぞれ、"Function: error condition-type keyword-arg … string arg …"、"Function: raise condition" と書いてあって、condition-typeとconditionで区別されていました。
- [質問者] また、ふと、ドキュメントにconditionはSRFI 35?を参考にしていると書いてあったことを思い出して、そうするとオブジェクトシステムのないScheme実装ではどうしているのだろう、、と思い、もう一度ドキュメントを探すと、make-condition という関数も用意されていて使えるようですね。いろいろ勉強になりました。
<class>オブジェクトを得る方法
たとえば 'my-klass という引数を渡して、<my-klass>のオブジェクトを返すような関数を作りたい場合に、以下のように eval を使うのが良いでしょうか? どうやって<class>オブジェクトを得るかという質問になると思うのですが、試行錯誤した末に以下のようなevalを使う方法にたどり着きました。ご意見・添削、お願いできれば幸いです。
(import (scheme base) (gauche base)) (define (instantiate-class name) (make (eval (string->symbol (string-append "<" (symbol->string name) ">")) (current-module)) )) (define-class <my-klass> () ()) (instantiate-class 'my-klass)
- 齊藤(2024/12/06 06:25:04 UTC): 一般論としては避けられるものなら eval は避けるに越したことは無いです。 この場合は module-binding-ref を使うのが適当な場面ではないかと思います。
(import (scheme base) (gauche base)) (define (instantiate-class name) (make (module-binding-ref 'r7rs.user (string->symbol #"<~|name|>")))) (define-class <my-klass> () ()) (instantiate-class 'my-klass)
- [質問者] ありがとうございます。module-binding-ref でうまくいきました。ありがとうございます!
オブジェクトシステムで next-method がない場合のエラーに関して
オブジェクトシステムにおいて呼び出し候補のメソッドで一番最後のメソッドから(next-method)を呼んだ場合に ERROR: no applicable method for #<generic xyz-method (5)> のエラーが発生します。 仕様だとは思うのですが、このエラーが発生しないようにする方法や、次のメソッドが存在するかどうかを調べる方法 (next-method-exists?)のような関数、 あるいは、no applicable method エラーのみを補足するような方法ありますでしょうか。
(guard (e [else 'no-next-method]) (next-method)) としたのですが当然、次のメソッドでエラーが起きた場合に必要なエラー処理ができなくなってしまい不都合が生じてしまいました。
クラスとメソッドをセットで生成するマクロを書いていて、継承関係にあるメソッドを順に呼びたいのですが、上記のエラーに対応する方法があれば、マクロを呼び出す段階で、next-methodを呼ぶかどうか指定しなくてよくなるのですが。
- Shiro(2024/11/27 06:58:32 UTC): 「どんな引数にもマッチするメソッド」を定義しておけば、最終的に必ずそれが呼ばれます。例えば
(define-method foo (a b) ...)
という定義があれば、2引数で他のメソッドにマッチしない場合にこれが呼ばれます。 引数の個数もわからなければ、不定長引数(define-method foo (:rest args) ...)
で。
- [質問者] ご回答、ありがとうございます。思っていたことができました。CLOSではLeast specificなmethodからnext-method してもエラーにならないようなので、Gaucheのドキュメントにこの解決方法の記載があると他のLisperにも親切かなと思いました。 いずれにしろ、Gaucheのオブジェクトシステムも素晴らしいです!
displayタイプの表示のpprint
(display '("a" "b"))は(a b)と表示され、(write '("a" "b"))は("a" "b")と表示されます。
(pprint '("a" "b"))は("a" "b")と表示されるので、writeと同じタイプの方の表示です。
displayと同じタイプの表示でプリティプリントをするにはどうすればいいでしょうか?
- 質問者です。その後、make-write-controlsを発見しました。しかし、(display '("a" "b") (make-write-controls :pretty #t))と実行すると、("a" "b")と表示されてしまいます。バージョンを書き忘れていましたが、0.9.15です。
- たびたびすみません、質問者です。上記の挙動の意図を考えていて、例えば、(display '("a" "\n" "b"))が実行されるとプリティプリントでのインデントが崩れることを考慮したのではと気がつきました。ですのでこの質問は取り下げさせていただきます。失礼しました。
- hamayama(2024/11/15 13:51:21 UTC): gauche.pputil のソースで、
write を使っている所を display に置き換えると、一応動作するようでした。(use gauche.pputil) (with-module gauche.pputil (define-class <pp-context> () (;[writer :init-form write :init-keyword :writer] [writer :init-form display :init-keyword :writer] [shared :init-form (make-hash-table 'eq?) :init-keyword :shared] [counter :init-value 0] ;shared label counter [controls :init-keyword :controls]))) (with-module gauche.pputil (define (pprint obj :key (port (current-output-port)) (controls *default-controls*) width length level indent ((:newline nl) #t)) (let1 controls (write-controls-copy controls :width width :length length :level level :pretty #t :indent indent) ;(write obj port controls) (display obj port controls) (when nl (newline port)))))
- 質問者: 情報ありがとうございます!スクリプト内に上記のコードをコピペした直後に(pprint '("a" "b"))を追記して実行したところ、(a b)と表示できました。同スクリプト内で、writeのコメントを外して、displayの方をコメントアウトして実行しても(a b)と表示されるのが不思議ですが、どちらでも今回の目的は達成できます。ありがとうございました。 ← define-class側のコメントアウトを見落としてました。正しくは、define-class側のwriterの選択が支配的で、pprint内のwrite/displayはどちらもwriterで選択した動作になる、でした。
- Shiro)2024/11/23 06:47:59 UTC): pretty printをdisplay形式で表示したい、というニーズを今まで考えたことがなかったので未サポートです。確かにリスト構造を適切に折りたたむくらいならdisplayでも役に立つかもしれませんが、文字列中に改行文字などがあるとどっちにせよ表示は乱れてしまうので、
目的によって最適な表示は変わってしまうようにも思えます。
テキストのfill (
text.fill
) を拡張する方が良いのようにも思うんですが、 どんなユースケースがあるでしょう。
tls の使用
Gauche で curl の様なスクリプトを書こうと思い rfc.http モジュールを使って
(use rfc.http) (receive (status headers content) (http-post "httpbin.org" "/post" '(("id" "123") ("name" "foo")) ) (print content))
とすると
{ "args": {}, "data": "", "files": {}, "form": { "id": "123", "name": "foo" }, ...
と返ってくるのですが、https://httpbin.org に API を叩くにはどうすればいいか分からずにいます。
rfc.tls モジュールを使えばいいらしいということは分かったのですが、mbedtls をどうやって組み込めばいいか分かりませんので
./configure --with-tls=mbedtls-internal
としてビルドし直しました。最初の2、3回は
gosh> (make-tls) #<mbed-tls #f (unconnected)>
と返ってきましたが、その後
Segmentation fault (コアダンプ)
で gosh が止まってしまいます。どうも mbedtls が上手く組み込めないようです。
rfc.tls のページや Gauche release notes を読むと、どうも mbedtls が前提のようですが、その他の方法(例えば cert と 秘密鍵を直接指定して)で tls で接続することは出来ないのでしょうか?
ちなみに
gosh tools/get-cacert
は、やってみました。
- hamayama(2024/08/19 15:10:12 UTC): こちらの環境 (Windows 10) では、
http-post に :secure #t をつけると https で接続できるようでした。
(Gauche は --with-tls=mbedtls-internal でビルドされています)
(use rfc.http) (receive (status headers content) (http-post "httpbin.org" "/post" '(("id" "123") ("name" "foo")) :secure #t) (print content)) ; ; ==> { ; "args": {}, ; "data": "", ; "files": {}, ; "form": { ; "id": "123", ; "name": "foo" ; }, ; : ; : ; "url": "https://httpbin.org/post" ; }
- hamayama(2024/08/19 15:10:12 UTC)(2024/08/20 01:08:47 UTC): TLS通信についてですが、
セキュリティに関わる技術のため状況がしばしば変化しており、
以前は動作していたものが今は動作しないというようなことが起きています。
(例えば、https://github.com/shirok/Gauche/issues?q=tls 等 (関係ないものもまざっていますが…))
なので、ライブラリを使わずに簡単に実装できるものではないです。
- hamayama(2024/08/19 15:10:12 UTC): あとは、Gauche ではTLS通信を行わず、
他のソフトを間に入れる方法も考えられます。
だいぶ前ですが、stunnel というソフトをはさんで、Gauche = (http) => stunnel = (https) => site
のようにして、動かしたことがありました。ただ、今もできるのかは、よく分かりません。
hash-table の変更
こんにちは。
「ハッシュテーブルって連想リストみたいなものでしょ? じゃあ連想リストのような二重リストでよいのでは?」
と適当に
(make-hash) => '()
(hash-table-get ht key) => (car (assoc-ref リスト キー))
などとやってみましたが、hash-table-put! で躓きました。
(define (hash-table-put! ht key val) (set! ht (cons (list key val) ht)))
こうしても ht へ破壊的代入が出来ません。何となく、関数は適用するもので対象を変えるものではないのだろうから、関数内で set! してもそこを出たら元のままなのだろう、というのは納得出来るのですが(違うかもしれませんが)、では一方、何故ハッシュテーブルは hash-table-put! で変更可能なのか分かりません。「それがハッシュテーブルだから」ということなのかもしれませんが…… よろしくお願いします。
- hamayama(2024/07/28 01:20:14 UTC): set! をリファレンスマニュアルで探すと、
Function ではなく Special Form となっています。
http://practical-scheme.net/gauche/man/?l=ja&p=set!
Special Form は、特殊形式 (R7RSでは「構文」) とのことです。
http://practical-scheme.net/gauche/man/?l=ja&p=項目の形式
Special Form (構文) の説明としては (言語が違いますが) 例えば、以下があります。
https://ayatakesi.github.io/emacs/24.5/elisp_html/Special-Forms.html
Special Form (構文) を拡張する場合、マクロを使います。
https://docs.scheme.org/schintro/schintro_130.html
- hamayama(2024/07/28 02:32:45 UTC): マクロを使わずに関数 (手続き) でがんばる場合、
リストでくるんだり、クラスにしてから引数に渡す方法もあります。
(define (make-pseudo-hash-table) (list '())) (define (pseudo-hash-table-put! ht key val) (set-car! ht (cons (list key val) (car ht))) ;(set-car! ht (assoc-adjoin (car ht) key (list val))) ;(set-car! ht (assoc-set! (car ht) key (list val))) ) (define (pseudo-hash-table-get ht key) (car (assoc-ref (car ht) key))) (define ht1 (make-pseudo-hash-table)) (pseudo-hash-table-put! ht1 'key1 1000) (pseudo-hash-table-put! ht1 'key2 2000) (pseudo-hash-table-put! ht1 'key3 3000) (pseudo-hash-table-put! ht1 'key1 10000) (print (pseudo-hash-table-get ht1 'key1)) ; ==> 10000 (print (pseudo-hash-table-get ht1 'key2)) ; ==> 2000 (print (pseudo-hash-table-get ht1 'key3)) ; ==> 3000
- 質問者
d (describe) を当てて見ると set! は an instance of class <syntax> で set-car! は an instance of class <procedure> なのですね。(ちょっと吃驚)
なるほど、macro
(define-macro (hash-table-put! ht key val) `(set! ,ht (cons (list ,key val) ,ht)))
にすると何とかなりました。 いつもありがとうございます。
get-gauche.sh でのテストエラーに関して
Linux環境(Fedora 40)でget-gauche.sh を使って以下のようにインストールをしようとしています。
> ./get-gauche.sh --version 0.9.15 --prefix /path/to/be/installed
configureからコンパイルはうまくいくのですが、test で以下のようなエラーがでます。
Testing ffitest ... failed. 9 discrepancies found: test load-foreign: expects (#t (f_i () i) (f_i_i (i) i)) => got #<<error> ": no matching clause for (.struct _IO_cookie_io_functions_t () ((read (.pointer () (.type cookie_read_function_t () (.function () (.type __ssize_t () (long ())) ((__cookie (.pointer () (void ()))) (__buf (.pointer () (char ()))) (__nbytes (.type size_t () (u-long ())))))))) (write (.pointer () (.type cookie_write_function_t () (.function () (.type __ssize_t () (long ())) ((__cookie (.pointer () (void ()))) (__buf (.pointer () (char (const)))) (__nbytes (.type size_t () (u-long ())))))))) (seek (.pointer () (.type cookie_seek_function_t () (.function () (int ()) ((__cookie (.pointer () (void ()))) (__pos (.pointer () (.type __off64_t () (long ())))) (__w (int ()))))))) (close (.pointer () (.type cookie_close_function_t () (.function () (int ()) ((__cookie (.pointer () (void ()))))))))))"> test call-foreign f_c: expects 9 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_i: expects 42 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_f: expects 1.25 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_d: expects 3.14 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_v: expects #<undef> => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_i_i: expects 99 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_f_f: expects 0.125 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f"> test call-foreign f_d_d: expects 1.0 => got #<<error> "flib is supposed to be of type #<class <foreign-library>>, but got #f">
なお、ffitest をコンパイルする箇所は以下のように問題ないようです。 環境の問題でしょうか。 よろしくお願いします。
"../../src/gosh" -ftest "../../lib/tools/precomp" -e -P -o gauche--ffitest ./ffitest.scm mkdir -p test gcc -DHAVE_CONFIG_H -I. -I. -I../../src -I../../src -I../../gc/include -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -fomit-frame-pointer -c ./test/f.c gcc -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -shared -o test/f.so f.o gcc -DHAVE_CONFIG_H -I. -I. -I../../src -I../../src -I../../gc/include -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -fomit-frame-pointer -o gauche--ffitest.o -c gauche--ffitest.c gcc -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -shared -o gauche--ffitest.so gauche--ffitest.o -L"../../src" -lmbedtls -lcrypt -lrt -lm -lpthread "../../src/gosh" -ftest "../../ext/xlink" -l -g "gauche" \ -b "../.." -s . gauche--ffitest.so ffitest.sci gcc -DHAVE_CONFIG_H -I. -I. -I../../src -I../../src -I../../gc/include -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -fomit-frame-pointer -o text--line-edit.o -c text--line-edit.c link /home/******/Gauche/build-20240621_213915.8uqq6wZA/Gauche-0.9.15/ext/native/gauche--ffitest.so <- ../../src/gauche--ffitest.so link /home/******/Gauche/build-20240621_213915.8uqq6wZA/Gauche-0.9.15/ext/native/ffitest.sci <- ../../lib/gauche/ffitest.sci
- Shiro(2024/06/21 20:27:02 UTC): テスト中に環境のstdio.hなどヘッダファイルをパーズしてるんですが、
そこで見るべきパターンに抜けがあるみたいです。Gaucheのバグですが、
とりあえずそこは現状オフィシャルには使わない機能なので、get-gaucheに
--skip-tests
オプションをつけてインストールしてもらえますか。 https://github.com/shirok/Gauche/issues/1044 でトラックします。
- お返事ありがとうございます。 stdio.h から cookie_read_function_tの定義されているcookie_io_functions_t.h をincludeする箇所の違いをFedoraとUbuntuで比べてみました。 #ifdef __USE_MISC と #ifdef __USE_GNU という違いがあり、そのあたりに起因するのかと思いましたが、そういう自分にわかりそうな話でもないかもしれないです。。 Issueに挙げていただいてありがとうございます。 しばらくはテストスキップしてインストールしたいと思います。
- HEADでの修正ありがとうございます。 自分の環境(Fedora 40)で最新ソースに、./DIST gen; configure; make; make check してみまして、ffitest ... passed を確認できました。
Windowsにおけるtools/build-standaloneについて
前回質問したものです。おかげさまで0.9.15でLinuxとMacでは無事スタンドアロンの実行ファイルを作ることが出来ました。しかし、Windowsではld.exeが-liconvと-lzを見つけられないと言うエラーがが出てしまいます。
前回の時点では良く判っていなかったのですが、Windows版でスタンドアロン実行ファイルを作るのに必要なminGWは、自分はWindows版のnim2.0をインストールしていたので条件をクリアしていたようです。
c:/nim-2.0.0/dist/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/11.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -liconv c:/nim-2.0.0/dist/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/11.1.0/../../../../x86_64-w64-mingw32/bin/ld.exe: cannot find -lz collect2.exe: error: ld returned 1 exit status
nimでインストールされたminGW環境に不足があるのか、Gaucheのディレクトリに適切なpathが通っていないか、どちらかの原因ではないかと思うのですが、どうすれば良いでしょうか?
- Shiro(2024/05/01 18:57:53 UTC): build-standalone で作ったバイナリも、外部DLLには依存するので
別途インストールが必要です。msys2環境があるなら、パッケージマネージャpacmanを使って
pacman -S mingw-w64-x86_64-zlib mingw-w64-x86_64-libiconv
で入ります。もし既に入っているなら、DLLのあるディレクトリにPATHが通ってない可能性があります。
- 質問者:ご返答ありがとうございます!scoopでmsys2インストールしてチャレンジしてみます。
tools/build-standaloneについて
ドキュメントに従ってスタンドアロン実行可能ファイルを作ろうとしたのですが、Linux、Windows、Mac全てで以下の様なアラートが出て失敗してしまいます。 gaucheのインストールはインストールスクリプト、またはWin版インストーラで行いバージョンは0.9.14です。 スクリプトの内容はhello worldなので、それ由来のエラーではないと思います。 アラートを見るとconfigP.hファイルが不足しているようなのですが、どうすれば良いでしょうか?
/usr/local/lib/gauche-0.98/0.9.14/include/gauche.h:45:10: fatal error: 'gauche/priv/configP.h' file not found #include "gauche/priv/configP.h" /* Only used while compiling Gauche itself */ ^~~~~~~~~~~~~~~~~~~~~~~ 1 error generated.
- Shiro(2024/04/10 05:17:43 UTC): あっ、そのヘッダはGauche内部でのみ参照されるべきものなので、 こっちのミスです。https://github.com/shirok/Gauche/issues/1013 でトラックします。
- Shiro HEADでは直っていますが、git repoからビルドするのが難しければ 言ってください。スナップショットリリースを作ります。
- 質問者:ご対応ありがとうございました!デバッグのお役に立てたのなら幸いです。全く急ぎませんので、お手隙の際にスナップショットリリースを作っていただけると嬉しいです。
\0 区切りの文字列の read
find(1) の -print0 で出力されるような \0 で区切られた文字列を、read-line が \n で区切られた文字列を読むような形で簡単に読み込む方法はありますでしょうか。現在自作の関数で一応読めてはいるのですが、もし簡単な方法があればと思い質問させていただきました。
Shiro(2024/02/24 12:47:16 UTC): 全部読み込んで文字列にしてから #\x00 でsplitするのではなく、 関数を呼ぶ度に次の#\x00まで読みたい、ということでしょうか。一発でやるのは多分ありませんが、 ジェネレータを使うのが楽かなと思います。
- まず入力を
port->char-generator
やstring->generator
でジェネレータにします (gauche.generator
) (generator->string (gtake-while #[^#\x00] ジェネレータ))
を呼ぶ度に、#\null で区切られた文字列が返されます。- ただ、最後を検出するのがちょいと面倒なんですね。最後まで到達したら空文字列が返りますが、
nul文字が2連続している空文字列と区別できません。ここを区別するには、ジェネレータから
#<eof>
が返るのを別に検出する必要があります。
質問者(2024/02/24 17:29:30 UTC): 了解いたしました。
ご回答ありがとうございました。
対話的に起動した際、使用する文字コードを指定することは可能でしょうか
Shiro (2024/02/23 09:37:46 UTC): 今は一般的な仕組みはないです。Windows consoleで使う場合に consoleのコードページを見て何かするコードはあるんですが、ハードコードされてます。 もう今はutf-8決め打ちでいいんじゃないかと思ってるんですが、 いや指定したいんだというユースケースがあれば教えてください。
- 質問者 (2024/02/23 16:59:33 UTC): 個人的にはunix系OS上のeuc-jpな環境でファイル操作や文字列操作をする際、対話的に起動したGaucheを便利に使わせていただいておりました。ただ、現在euc-jpな人はほとんどいないであろうことは理解しており、そのような機能をあえて希望するものではありません。また、いくつか前のバージョンからutf-8固定に移行することがアナウンスされていることも存じ上げており、当然の流れと考えていたため特に何か希望するということも致しませんでした。
スクリプトとして実行する場合 ";;coding: " で指定できるようなので、対話的にも可能だろうかと考え質問させていただいた次第です。
ご回答ありがとうございました。 - Shiro: なるほど。Windows Consoleと同じようにやればできなくはないんですが、 unicode以外をREPL入出力にした場合、そのコードに変換できない文字があった時に面倒なんですよね。 一律に置換すると情報が失われますし。 一応、backburnerに置いておきます。
対話的に起動した際、C-r で履歴をインクリメンタルに検索する設定は可能でしょうか
Shiro (2024/02/23 09:37:46 UTC): ああ、欲しいですね。今はないです。 https://github.com/shirok/Gauche/issues/989 でトラックします。
- 質問者 (2024/02/23 16:59:33 UTC): 今まで rlwrap を使って来ましたが、直接起動する環境へ移行しようと考えておりますので、この機能があれば助かります。ただ、急いではおりませんので気長に待たせていただきます。
ご回答、ご対応ありがとうございます。
R7RSモードでの起動時の-lで指定したロードファイルの挙動について
以下のようなgauche1.scm, gauche2.scm のファイルを用意したディレクトリで、
;; gauche1.scm (import (scheme base)) (load "./gauche2.scm")
;; gauche2.scm (display "Hello Gauche")
gosh -r 7 -l ./gauche1.scm
とR7RSモードでGaucheを開始しようとすると、 gosh: "UNBOUND-VARIABLE-ERROR": unbound variable: load とエラーがでます。
load関数自体は、普通にGaucheから利用できるのですが、lオプションで指定したロードファイルは何か特別な配慮が必要なのでしょうか。よろしくお願いします。
- 齊藤(2024/02/17 12:55:20 UTC): (scheme base) には load は含まれないからだと思います。
R7RS で load があるのは (scheme load) です。 - 回答ありがとうございます。loadのマニュアル ( https://practical-scheme.net/gauche/man/gauche-refe/Loading-Programs.html ) に、R7RS+ と書いてあったので、R7RSの(scheme base)に入っているような標準的な関数という意味なのかなと思い込んでしまいました。
- Shiro:あーなるほど、そこは紛らわしいかもしれないですね。
[R7RS+ load]
みたいな表記にしようかな。 - [質問者] どのライブラリをimportすればよいかがマニュアルでわかりやすいと初心者的には助かると思います。あとは、apropos関数で調べるのがよいのでしょうかね。ただ気づいた点として、(apropos 'load)で調べるとr7rs-load がリストアップされたので、例えば、R7RSのREPLでaproposするとR7RSでの関数名が表示されるとわかりやすいのかなとも思いました。
- Shiro:なるほど、aproposはrenameを考慮してないんだな。そこは改良の余地ありです。
今調べる方法としては、
,info load
という手もあります。loadのように複数のエントリがある場合は選択肢が出ます。 - Shiro: あと、-lオプションでなくスクリプトとして
gosh ./gauche1.scm
で起動するともうすこしエラーメッセージが親切で、load
があるモジュールを教えてくれます。-lオプションでもこのメッセージを出すのが良いかもしれません。*** UNBOUND-VARIABLE-ERROR: unbound variable: load NOTE: `load' is exported from the following modules: - scheme.load - scheme.r5rs While loading "./ttt.scm" at line 1 Stack Trace: _______________________________________ 0 (eval s #f) 1 (with-error-handler (lambda (e) (cond (else (let1 e2 (if (con ... 2 (load-from-port (if ignore-coding port (open-coding-aware-por ...
- Shiro: HEADで上記を実装しました。-lオプションからのエラーでもスタックトレース以外のメッセージが出ます。
- [質問者] HEADコンパイルして確認できました。早速のご対応ありがとうございました! あと ,info の機能知りませんでしたがとても便利ですね。 そもそもREPLでのトップレベルコマンドを知らなかったのと、まさかinfoドキュメントがREPLから見られるとは思いませんでした。ドキュメントへのR7RSのパッケージ名の追加もありがとうございました。
SRFI-49で開きカッコがネストしている場合の書き方
SRFI-49では、
(quote (1 (2 (3 4))))
を、カッコを使わずに
quote 1 2 3 4
と書けるというのはわかりましたが、
(quote (((1 2) 3) 4))
や
(quote (((1))))
を、カッコを使わずに書く場合はどう書くのですか?
- 齊藤(2023/12/15 07:44:31 UTC) : そういうときのために group というキーワードが用意されています。
quote group group group 1
SRFI-49 には let のバインド部分のところに使う形で例示されているので参照してください。 - ありがとうございます。そのためのキーワードが用意されていたとは。確かにこれがないとletは書けないですね。
連想リスト(Association list)の形式について
連想リストを作るときに、リストのリストにするのと、ドットペアのリストにするのとどちらが良いとか、どちらが普通などありますでしょうか? 例えば、Schemeの関数との相性がどちらが良いかなど。
'((1 "A") (2 "B") (3 "C")) ;; 形式1 '((1 . "A") (2 . "B") (3 . "C")) ;; 形式2
また、値の方に、複数の値の組をもたせたいような場合も、以下の形式のどちらがお勧めなどありますでしょうか。assoc したあとに、cdr で値のリストが得られる形式3の方が使い勝手良さそうにも思いますが、ペアという見た目では形式4の方が良いようにも思います。assoc の後にset-cdr! するときも、形式3の方が自然かなとおもいましたが、どうでしょうか。
'((1 "AA" "AB" "AC") (2 "BB" "BC" "BD") (3 "CC" "CD" "CE")) ;; 形式3 '((1 ("AA" "AB" "AC")) (2 ("BB" "BC" "BD")) (3 ("CC" "CD" "CE"))) ;; 形式4
ケースバイケースだと思いますが、Scheme初心者ということもあり、どちらでもよい場合に、どちらを使っておくのがおすすめか教えて頂けますと幸いです。
- Shiro(2023/12/09 12:20:32 UTC): 私は使い勝手の上ではドットペアのリストを好みます。形式2および形式3ですね。Gaucheだと
assoc-ref
のように直接マッチしたペアのcdrを返してくれるユーティリティもあります。 ただ、人が手書きする場合はリストのリストにした方が若干読み書きしやすいので、 設定ファイルなど人による読み書きを重視するなら形式1/形式4を使う場合もあります。 あとこちらの方法だと後でオプションをつけるなど拡張したくなった場合にやりやすい (例:((1 "A" :option xyz) ...)
) っていうのも手書き設定ファイルには向いてます。 - ありがとうございます。よくわかりました。どういう風に使い分けるかのイメージもつきました。必要に応じて使い分けたいと思います。
バックグラウンドで動かすにはどうすれば良いでしょうか
例えばシェルスクリプト hello.sh
#!/bin/bash while :; do # 5秒ごとに hello, と標準出力させる echo "Hello," sleep 5 done
と world.sh
#!/bin/bash self=$$ # このスクリプトの PID bash hello.sh & # バックグラウンドで hello.sh を動かす(表示させる) hello=$! # hello.sh が動いている PID read line if [[ $line == "change" ]]; then # change を標準入力したら、 kill $hello # hello.sh を PID ごと止め、 while :; do # world を3秒ごとに標準出力させる echo "world!" sleep 3 done else # change 以外を標準入力したら全部止める kill $hello kill $self fi
を書いて bash world.sh とすると hello, hello, ... (5秒以内に change と打ち込む) world! world! ...
と出来ます。これを scheme(Gauche)でやろうと試みました。gosh は一つのプロセスとして動いているので、そのプロセス内のスレッドを使い分けるしかないのかもと思い gauche.threads モジュールを使って thread.scm
(use gauche.threads) (define *hello*) (define (hello-thread) (let ((t (make-thread (lambda () (while #t (sys-sleep 5) (print "Hello, ")))))) (set! *hello* t) (thread-start! t))) (while (if (not (equal? (read) 'change)) (hello-thread) (begin (thread-terminate! *hello*) (while #t (sys-sleep 3) (print "world")))))
と書き
gosh thread.scm
で同様のことが出来ました。(最初に何か入力しなければいけませんが)
しかし gauche.threads のページに
「単に同時実行が必要なだけなら、call/ccによる協調スレッド テクニックが使えるでしょう」
とあります。call/cc のジャンプ(脱出)が使えるのかと思い
(define (hello) (while #t (sys-sleep 5) (print "Hello,"))) (begin (call/cc (lambda (break) (while #t (if (not (equal? (read) 'change)) (hello) (break "Change!"))))) (while #t (sys-sleep 3) (print "world")))
と書いて実行してみましたが当然、hello 自身のループから出て入力受け付けに辿り着くことなく、change を入力してもずっと hello, を出力し続けました。 これは
1. コードが悪い。実は call/cc でも可能
2. バックグラウンドで動かすには gauche.threads を使って別にスレッドを分ける必要がある
3. その他(delay force あたりで出来る気もします)
のどれかなのでしょうか。
- 齊藤(2023/11/05 07:26:53 UTC) :結論からいえば call/cc では出来ません。
ドキュメントが言う「同時」はちょっと語弊がある説明なのかもしれません。
協調スレッドについては資料はいくらでも見つかると思うので探してみるとよいと思いますが、 実行中の文脈を「明示的に切り替えながら」複数の処理を平行にこなす仕組みのことで、 JavaScript でいうジェネレータだとか Ruby でいう Fiber とかがそうです。
call/cc を使うとそういうものに相当することが可能であるという説明であり、 スレッドでやれることの「一部は」スレッドほど高級なものは必要ない場合もあるということを言っています。
- Shiro(2023/11/05 20:45:32 UTC): あれ、concurrentを同時って訳しちゃってますね。並行に直しときます。
元の質問に戻ると、call/ccでもできる、とういのは、call/ccを使ったコルーチンで書くということで、
その場合、各ルーチンは長期間プロセスを占有することを避けて、ちょっと作業をしたらメインループに
戻る、というふうにします。
sys-sleep
などとコルーチン内で呼び出すことはできません。 タイミング制御が必要なら「3秒後にコルーチンの続きを呼び出す」というような仕組みを 併用します。(コルーチンをコールバックと思えば、他の多くのプログラミング言語と同様です)。
- hamayama(2023/11/06 13:02:03 UTC): call/cc の使用例を作成してみました。(callcc.scm)
https://gist.github.com/Hamayama/8e66bc6a49ea9490f1cf4acae18b64f0
時間のかかる処理を中断して、他の処理に切り換えるような使い方になります。
この方式は、スレッドとは異なり、処理がブロックすると全体が止まってしまいます。
その代わり、排他処理等は不要になります。
(ただ、そもそも仕事の単位を関数に分けられれば、それを順番に呼べばよいので、実際にはあまり使わない気がしますが…)
(先頭の cond-expand は、Windows で char-ready? がブロックしてしまうのを簡易的に回避したものなので、
OS が Windows でなければ、気にしないでください)
Typo報告
GithubにIssueだすほどではないように思いましたので、ここに記載させていただきました。
srfi.227のドキュメントでopt-lambda が opt-labmda になっていました。 Macro: opt-labmda opt-formals body … Macro: opt*-labmda opt-formals body …
- Shiro(2023/10/23 03:52:35 UTC): ども。なお、issueに挙げてもらう方が楽なのでgithub気軽に使ってください。
- 了解しました、今後は、Githubの方に上げたいと思います。
delete! の作用について
こんにちは。gosh -V => (version "0.9.9") を使用しております。
(define abc '(a b c)) (delete! 'a abc) ;=> abc (a b c) (delete! 'b abc) ;=> abc (a c)
となるのですが、前者で abc が (b c) にならないのは何故でしょうか。
(remove! (^(elt) (equal? elt 'a)) abc) (remove! (^(elt) (equal? elt 'b)) abc)
でも同様に前者は abc の値が変わりません。よろしくお願いします。
- 齊藤 (2023/10/03 00:28:40 UTC) : ふたつの誤解があると思います。
まずひとつは、 delete! は in-place な更新をするわけではなく liner update をします。 つまり、この手続きの結果は返却値であり、処理の過程で元のオブジェクト (の一部) を再利用するかもしれないし、しないかもしれません。 元のオブジェクトがどのように利用されるのかは未定義です。 R7RS-small における「破壊的手続き」は in-place のことなんですが、他の由来を持つ手続きはそうではないことがあるので注意が必要です。
もうひとつの問題は、リテラルは破壊不可能なオブジェクトであるということです。 破壊を前提とした手続きに対して破壊不可能なオブジェクトを渡した結果は未定義です。 Gauche のバージョンによってはエラーや警告が通知されることもあるのですが、黙って変な挙動を示すこともあります。
- hamayama(2023/10/03 03:28:34 UTC): 参考になりそうな以下の記事があります。
Scheme:LinearUpdate
Gauche:immutableなデータの注意点
私も、この件については、ずっと知らないままプログラミングをしていて、
分かったときには、これまで自分が作ったプログラムは大丈夫だったのか?となって苦労しました。
プログラミングのはまりやすいポイントのひとつだと思います。
- hamayama(2023/10/10 01:06:53 UTC): 一応、上記のコードを動かすだけなら、
リテラル定数をやめるのと、戻り値を使えばよいので、以下のようになります。
(define abc (list 'a 'b 'c)) (set! abc (delete! 'a abc)) (print abc) ; ==> (b c) (define abc (list 'a 'b 'c)) (set! abc (delete! 'b abc)) (print abc) ; ==> (a c) (define abc (list 'a 'b 'c)) (set! abc (remove! (^(elt) (equal? elt 'a)) abc)) (print abc) ; ==> (b c) (define abc (list 'a 'b 'c)) (set! abc (remove! (^(elt) (equal? elt 'b)) abc)) (print abc) ; ==> (a c)
- 質問者 ありがとうございます。 やはり一度変えてみた結果を set! で元の定数へ破壊的代入するしか確定的な方法はなさそうですね。
shutdown 後に socket-status が返す値
突然お邪魔して,申し訳ありません.いつも楽しく Gauche を利用させていただいております.初心者です.
#!/usr/bin/env gosh (use gauche.net) (letrec ((socket (make-server-socket 'inet '8080)) (client (socket-accept socket))) (socket-shutdown client) (format #t "~A\n" (socket-status client)))
としましたところ,
shutdonw
と表示されましたが,これは「shutdown」のタイポでしょうか?それとも何か理由があるのでしょうか?Gaucheのバージョンは0.9.10です.場違いな書き込みでしたら,すみません.
- Shiro(2023/02/13 13:38:06 UTC): タイポです。ありがとうございます。 https://github.com/shirok/Gauche/commit/0dc84bb2aeb8bb8816b694b17e4efda49d73fb32
while 内の唐突な代入について
累積関数 factorial
(define (factorial n) (if (<= n 1) 1 (* n (factorial (- n 1)))))
を while で書き直すことを考えると
(define (w-fac n) (let ((b 1)) (while [> n 1] (set!-values (n b) (values (- n 1) (* n b)))) b))
こんな感じになると思います。(末尾再帰化と似ている) これを、段階を細分化し
0: b に1を代入し
1: n が1か否かで処理を分け
2: n=1 でない場合は b:=n*b n:=n-1 として処理を続け
3: n=1 の場合は b を返す
という while でさらに書き換えます。 (u の値がおよそ上記の番号に当たる)
(define (w-fac2 n) (let ((u 0) (b 0)) (while [< u 3] (cond ((= u 0) (set!-values (u n b) (values 1 n 1))) ((= u 1) (set!-values (u n b) (values (if (= n 1) 3 2) n b))) ((= u 2) (set!-values (u n b) (values 1 (- n 1) (* n b)))))) b))
そしてこれからが本題なのですが、while に入る前に u だけでなく b の値も決めておかなければ実行時に「bが未定義シンボルだ」とエラーが出ます。(当たり前といえば当たり前ですが) while 前に代入(ではなく束縛ですが)するのは状態を判断して処理を振り分ける u だけで出来ないかと色々試してみました。
しかし、例えば u=0 の場合に
(define b 0)
としてみると「トップレベル以外で define は無理」とエラーになります。また、関数の引数も最初のもの(factorial)が1つなので、b をあらかじめ与えられるようにすると「同じ関数を while で書き換える」とは違うように思います。
Python などで
def w_fac2 (n) : u = 0 while u < 3 : if u == 0 : u, n, b = 1, n, 1 elif u == 1 : u, n, b = (lambda a: 3 if a == 1 else 2)(n), n, b else : u, n, b = 1, n -1, n * b return b
と、いきなり b に代入するようなことは、Scheme(Gauche) では無理なのでしょうか。 何となく出来たら出来たで恐ろしい気もしますが(バグが紛れ込みそう)、出来ないことを明確に知りたいと思います。 よろしくお願いします。
- 齊藤(2022/12/12 09:02:13 UTC) 結論から言えばできません。
Scheme の理屈では手続きが呼び出されたときに仮引数として表れる変数と場所 (location) の束縛が行われ、 その場所に実引数の値が入るということになっています。 場所に格納するのはset!
を使えばよいのですが変数と場所の束縛に関与する方法はありません。
let
などのローカル変数を作る構文も理屈の上では手続きの呼び出しです。(let ((foo 1)) ほにゃらら)
というような式は((lambda(foo) ほにゃらら) 1)
であるかのように解釈されます。let
以外にもローカル変数を導入する構文はいくつかありますが、なんやかんやで手続き呼び出しの形として解釈されるように定義づけられていて、ローカル変数は全て仮引数なわけです。 あらゆるローカル変数は仮引数と考えて良いのでこれもまた束縛に関与する方法はないということになります。
- hamayama(2022/12/14 14:04:02 UTC): これは、もう言語のデザインということになると思います。
( R7RS 3.1 に変数の束縛構文と有効範囲の記述があります )
変数の束縛(他の言語では宣言/定義等)をしないで、使った時点で使用可能になるような言語もあると思いますが、
「タイプミスしたら、別の変数ができてしまった」とか、
「離れた2箇所で、たまたま同名の変数を使用したら、共通の変数になってしまった」というような、
分かりにくい不具合が出るケースも考えられます。
Gauche の、Racket との違いと評価順序について
一つ前の質問をした者です。齊藤さん、hamayamaさん、Shiroさん、丁寧なお答えを頂きありがとうございました。
call/cc について調べているとこの様なページを見つけました。 https://stackoverflow.com/questions/52807955/infinite-loop-while-using-call-with-current-continuation-in-scheme[
ちょっといじって
(define (f n) (let ((p (call/cc identity))) (sleep 1) ; 無限ループで分からなくならないように (display n) p)) ((f #\newline) (f "*"))
とすると call/cc パズルhttps://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E4%BD%BF%E3%81%84%E3%81%9F%E3%81%84%E4%BA%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E7%B6%99%E7%B6%9A%E5%85%A5%E9%96%80#H-2x6wuk[ と同じように、DrRacket では動きました。 が、Gauche で
(define (f n) (let ((p (call/cc identity))) (sys-sleep 1) ; 無限ループで分からなくならないように (display n) p)) ((f #\newline) (f "*"))
とすると * -> 改行 -> 改行 -> * 改行 -> 改行 -> * …… となります。 何故かを考えますと、まず Racket ではおそらく
簡略のため、並列された上から順序で評価される式を -> で書いています ([let ((p (call/cc identity))) 改行->p] [let ((p (call/cc identity))) *->p]) ; ① ([let ((p k1)) 改行->p] [let ((p (call/cc identity))) *->p]) ; ② 継続 k1 が生成される(identity を引数に取っているため call/cc が返す値は継続そのもの) 改行 & (k1 [let ((p (call/cc identity))) *->p] ;③ k2 が生成される (k1 [let ((p k2)) *->p]) ; ④ * & (k1 k2) ; ⑤ ([let ((p k2)) 改行->p] [let ((p (call/cc identity))) *->p]) ; ⑥ k1 が生成された時点の ② の k1 に k2 が当て嵌められる 改行 & (k2 [let ((p k3)) *->p]) ; ⑦ k3 が生成される * & (k2 k3) ; ⑧ (k1 [let ((p k3)) *->p]) ; ⑧ * & (k1 k3) ; ⑨ ...
と、② 式(ただし kn の n の値は変わっていく)に戻る度に遡るためのステップが一つずつ増えていくことで * が改行前に一つずつ増えて表示されるのだと思います。 対して Gauche では最初に * が表示されることから被適用される前の式より、適用する後ろの式から評価されるのだと推測しました。すると
([let ((p (call/cc identity))) 改行->p] [let ((p (call/cc identity))) *->p]) ; ① ([let ((p (call/cc identity))) 改行->p] [let ((p k1)) *->p]) ; ② k1 が生成される * & ([let ((p (call/cc identity))) 改行->p] k1) ; ③ ([let ((p k2)) 改行->p] k1) ; ④ k2 が生成される 改行 & (k2 k1) ; ⑤ ([let ((p k1)) 改行->p] k1) ; ⑥ k2 が生成された時点の④式の k2 に k1 が当て嵌められる 改行 & (k1 k1) ; ⑦ ([let ((p (call/cc identity))) 改行->p] [let ((p k1)) *->p]) ; ⑧ k1 が生成された時点の②式の k1 に k1 が当て嵌められる * & ([let ((p k3)) 改行->p] k1) ; ⑨ k3 が生成される (k3 k1) ; ⑩ ([let ((p k1)) 改行->p] k1) ; ⑪ ⑨式の k3 に k1 が当て嵌められる 改行 & (k1 k1) ; ⑫ k1 が生成された時点の②式の k1 に k1 が当て嵌められる ...
と、②式に戻りそこからの流れが繰り返され(ただし k2 -> k3 -> ...) 改行 -> 改行 -> * ... が繰り返されるのだと推測しました。 しかしこれには間違いがあります。
(define (f n) ...
と f を定義せずに
([let ((p (call/cc identity))) 改行->p] [let ((p (call/cc identity))) *->p])
と直接 let 式で行うと * -> 改行 * -> 改行 * -> 改行 * -> 改行 ... となってしまうのです。おかしい、何が違うんだ。同じことをやってるんじゃないのかよ……と紙に書いて色々考えた結果、一つの仮定にたどり着きました。
([let ((p (call/cc identity))) 改行->p] [let ((p (call/cc identity))) *->p]) ; ① ([let ((p (call/cc identity))) 改行->p] [let ((p k1)) *->p]) ; ② k1 が生成される ([let ((p k2)) 改行->p] [let ((p k1)) *->p]) ; ③ そしてここで適用する let 式が評価されてしまう前に、被適用の let 式の評価が行われる * & ([let ((p k2)) 改行->p] k1) ; ④ 改行 & (k2 k1) ; ⑤ ([let ((p k1)) 改行->p] [let ((p k1)) *->p]) ; ⑥ ③式に k1 が当て嵌められる * & ([let ((p k1)) 改行->p] k1) ; ⑦ 改行 & (k1 k1) ; ⑧ ([let ((p (call/cc identity))) 改行->p] [let ((p k1)) *->p]) ; ⑨ ②式に k1 が当て嵌められる ...
と、③式から * と改行の出力が行われて②式へ戻ることを繰り返しているのではないかという仮定です。
何故
(define (f n) ...
とすると適用する側の評価が行われる前に被適用側の評価が行われないか考えてみると、おそらく適用側の (f 引数) が明確に値を返すまで評価されるからだと思います。
これらが正しいとすると
・Gauche では適用側(引数)の方から評価が行われる
・let 式は let が束縛する変数が分かると評価は割と後回しにされる
・定義された関数(変数)に束縛された式は値を返すまで評価される
だと思います。しかし call/cc パズルの解説と同じページの「評価順序と継続」 https://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E4%BD%BF%E3%81%84%E3%81%9F%E3%81%84%E4%BA%BA%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE%E7%B6%99%E7%B6%9A%E5%85%A5%E9%96%80#H-1wby0gx[ によると
「つまり、Gaucheでは左から順に引数を評価するために、」 とあって大前提の
・適用側(引数)の方から評価が行われる
は、間違いのようです。 と、すると Gauche と Racket でこの違いは一体何によるものでしょうか。
- Shiro(2022/11/02 22:26:17 UTC): Gaucheでは「左から順に引数(リストの先頭以外)を評価」して、それから手続きの式(リストの先頭)を評価します。
- 質問者 あ、なるほど。リストの2番め、3番め、4番め……最後の要素と評価されて1番めが評価されるのですね。 ありがとうございます。
ブロック内で call/cc とその他を並列にしたときの継続の中身
begin 内で call/cc 式と他の式を並べてみました。
(define aaa/cc) (begin (call/cc (lambda (k) (set! aaa/cc k))) (print "aaa"))
この begin 式を評価すると
gosh> aaa #<undef>
となります。また
gosh> (aaa/cc) aaa #<undef>
というのも(一体どういう仕組みでこれが可能になっているのかはともかく)おそらく call/cc 式の次の (print 'aaa) が aaa/cc に set! で代入されているのだろうというのは推測できます。どういう仕組みでそうなるのかはともかく。 しかし
gosh> (aaa/cc 1) aaa #<undef> gosh> (aaa/cc 'a 'b 'c) aaa #<undef>
と、aaa/cc に引数を(幾らでも)渡しても同じように同じ結果を返します。継続は結局はクロージャ、あるいはクロージャの連鎖だと理解しているのですが、aaa/cc に代入された継続は一体どのようなものなのでしょうか。
よろしくお願いします。
- 齊藤(2022/10/23 01:41:02 UTC) 継続は継続であるとしか説明できないのでどのようなものかというのを端的な言葉には出来ません。
なのでここではかなり大雑把な形での説明になります。
継続という概念自体はどんな言語にでもあり、 Scheme 特有のものではありません。
継続はこれからする処理全てであり、そこに値が受け渡されるという形でプログラムが進みます。
call/cc はある時点での継続を手続きの形にパッケージしたもの (脱出手続き) として取り出す手続きです。
つまり取り出された手続きを呼び出すというのは取り出された時点から後の処理全てをするというわけです。
対話環境 (REPL) で式を評価した場合には read が暗黙に挿入されているので print を実行した後に read が呼び出されて次の入力を促されるという結果になります。
また、手続きの呼び出しも、呼び出しから戻ってくるのも Scheme の理屈では等しく継続の起動です。
手続きに複数の値を渡せるのなら戻ってくるときにも複数の値を戻せるのが筋だろうという形で整理されたのが「多値」です。
多値は待ち受けている継続が受け取る値の個数と一致しなければエラーなのですが今回の例では単に多値は捨てられる文脈になっているのでどんな引数を渡してもただ捨てられます。
- 質問者? ありがとうございます。ちょっと多値について調べてきます。
- hamayama(2022/10/23 14:20:19 UTC)(2022/10/25 01:13:58 UTC):
call/cc は、call/cc の継続を取り出しますが、
call/cc の次に来る命令というのは、バリエーションが考えられます。
いろいろ試してみてはどうかと。
あとは、R7RS の 6.10 制御機能 の call/cc の説明を読むとか。
( 継続の引数が破棄されるケースについて、記載されています )
;; (A) (define k1 #f) (begin (call/cc (lambda (k) (set! k1 k))) (print "aaa")) (k1 1)
;; (B) (define k1 #f) (print (call/cc (lambda (k) (set! k1 k) 0))) (k1 1)
;; (C) (define k1 #f) (list 100 (call/cc (lambda (k) (set! k1 k) 0)) 200 300) (k1 1)
;; (D) (define k1 #f) (call-with-values (lambda () (call/cc (lambda (k) (set! k1 k) 0))) list) (k1 1 2 3)
Shiro(2022/10/23 21:03:51 UTC): aaa/cc
に渡した値は、(call/cc (lambda (k)...
の戻り値になります。デバッグスタブ #?=
を使って表示させてみましょう。
(define aaa/cc #f) (define (foo) #?=(call/cc (lambda (k) (set! aaa/cc k))) (print "aaa"))
最初の呼び出し:
gosh> (foo) #?=(call/cc (lambda (k) (set! aaa/cc k))) #?- #<subr "continuation"> aaa #<undef>
ここで表示されているのは、(set! aaa/cc k)
の戻り値 (k
の値、つまり継続手続き) がそのまま出てきています。
gosh> (aaa/cc 1) #?- 1 aaa #<undef>
継続に1を渡してみました。すると、(call/cc ...
から1が返ってきて、foo
の残りが再び実行されます。
gosh> (aaa/cc 'a 'b 'c) #?- a #?+ b #?+ c aaa #<undef>
三つの値を渡すと、それが多値として(call/cc ...
から返ってきます。
なお、トップレベル式で捕まえる継続はR7RSでは明確に定義されておらず、処理系によって振る舞いが分かれるので、私の例のように関数の中で捕まえる方がわかりやすいです。