Scheme:初心者の質問箱:log02
- (rfc.http) http-get での error について(Gauche-0.9.12)
- INSTALL.ja.adoc(Gauche-0.9.12.tgz)の誤植?
- set! の使い方について
- 移植可能な標準入出力バイナリポート
- rfc.zlib の window-bits について
- REPLでのincludeのパス
- sxml の変換について
- スクリプトで乱数を出すには
- ダウンロードのページのソースのリンクが切れてませんか?
- sintax-parseの機能改善をしたい
- hash-tableで叶えていることを非破壊の仕組みでやってみたい
- C でいう構造体、あるいはOOのインスタンス変数
- let の中の define
- mtmap (マルチスレッド map)
- REPL での define の挙動 (define の返り値)
- 継続が止まらなくなるのですが、理由が分かりません
- カッコで包むと不束縛変数でもエラーにならないのは何故でしょうか
- site(practical-scheme.net/gauche/) の リリース 0.9.10 のページ内にリンク切れ?
- (iota 100 1) を評価しましたが、途中で ...) と表示され手抜きをされます。どこで誰が制限を掛けているのですか?
- port-position はどうしたら使えるようになりますか?
- (fold and #t '(#t #t)) がエラーになるのはなぜですか?
- util.match モジュールは、デフォルトでロードされるモジュールですか?
- 関数定義内関数の実行
- atom関数はなぜ無いのか?
- gauche-gtkgl.soをロードできない
- GaucheをC言語から使いたいのですが、関数やマクロ名はどこを参照するのがよいでしょうか。
- 再帰(線形再帰)と末尾再帰(反復的プロセス)は常に互換できますか
- これはZコンビネータですか
- Gauche-alをMacで使いたい
- 関数に名前を付けるということ
- ラムダ計算のSchemeでの実行について
- Yコンビネータの斜め下
- Yコンビネータの二歩手前
- この場合の括弧とは2
- この場合の括弧とは
- Gauche 0.9.8 makeエラー
- 齊藤 (2018/11/30 18:10:35 UTC):モディファイアとは?
- readerを実装する際のコーディングスタイル
- lmap と ltake を組み合わせたときの lmap に渡した関数の挙動
- (define (f a) ( 関数 )) と (define f (lambda (a) (関数))) は同値ですか
- syntax-rules の <literals> について
- ラムダだけで再帰は不可能ですか?
- map についての質問です
- #N タグ(?)が何故使われているのかが分かりません
- 定義によって導入 (齊藤: 2018/04/21 02:59:58 UTC)
- 木構造に対するmap
- gauche.configureの動作について
- subr, closure, procedureの違い
- 自動整形の設定
- Windowsでの日本語名ファイルの取扱い
- syntax-rulesでcondlet
- Gauche(Scheme)初心者が約束通り再帰で躓いたので質問したく存じます。
- 質問の仕方
- 型によるプログラミングをする際の関数の宣言方法について
- macroexpand の再帰的な評価について
- Gauche(0.9.4_pre3)のr7rs環境について
- Window7(Gauche-mingw-0.9.3.3.msi)の gauche-package でエラー
- ファイルのアクセス日時のみ現在の日時にしたい
- refの仕様
- syntax-rules の ... の動作
- write の出力のカスタマイズ
- gauche/gdbm on openSUSE 11.x
- gauche-gdbm on FreeBSD 8.0
- 文字列上の繰り返しと部分文字列
(rfc.http) http-get での error について(Gauche-0.9.12)
gauche のスクリプトで、web ページにアクセスしてみたく、
(use rfc.http) (http-get server request-uri :secure #t) 例えば (http-get "artscape.jp" "/exhibition/traveling/index.html" :secure #t)
としてみたところ、サイトによって、
*** ERROR: TLS handshake failed: -26112 Stack Trace: _______________________________________ 0 (connect-socket) ...
となってしまうところがありました。 (Gauche のコンパイルでは --with-tls=mbedtls-internal をつけ、(http-secure-connection-available?) => #t は確認してあります。)
この原因としてはどういうことが考えられて、対応方法はなにかありましょうか。
- Shiro(2022/09/14 18:18:59 UTC): cipherがサポートされてないか、証明書のバリデーションに失敗した可能性が考えられますが、私の手元からだとmbedtlsでアクセスできるのでcipherの線は無さそうです。 プラットフォームは何ですか? 証明書のストアをうまくみられてないのかな。
- もしよければ、ext/tls/tls-mbed.cにこのパッチをあててみてください。エラーコードだけでなくメッセージが出力されるようになります。 https://github.com/shirok/Gauche/commit/46925c3fe736da51c0a8f1bd61eb2a972da89a59
- osmnaka(2022/09/14 22:22:23 UTC): 質問者です。ありがとうございます。以下が出力されました。
*** ERROR: TLS handshake failed: SSL - A field in a message was incorrect or inconsistent with other fields (-26112)
プラットフォーム、とは以下でいいでしょうか。$ ~/local/bin/gosh -V Gauche scheme shell, version 0.9.12 [utf-8,pthreads], x86_64-pc-linux-gnu ... (gauche.net.tls mbedtls) (Arch Linux でソフトウェアはディストリのパッケージでほぼ最新)
状況報告だけですが、取り急ぎ。
- osmnaka(2022/09/14 23:04:29 UTC): Shiro さんの環境ではアクセスできているということで、試しにレンタルサーバー上から試してみたところアクセスできました。
$ ~/local_test/bin/gosh -V Gauche scheme shell, version 0.9.12 [utf-8,pthreads], x86_64-unknown-freebsd13.0 ... (build.configure "--with-tls=mbedtls-internal" "--enable-multibyte=utf-8") ... (gauche.net.tls mbedtls)
- が、半日後再確認したところ、以下のエラーが発生し、上記アクセス可の確認できず...
*** ERROR: mbedTLS: tls-ca-bundle-path isn't set. It is required to validate server certs.
- が、半日後再確認したところ、以下のエラーが発生し、上記アクセス可の確認できず...
- osmnaka(2022/09/15 00:21:07 UTC): VPS に自宅と同じ Arch Linux の環境を作ってたので、そちらで試してみたら、同じエラーが出ました。OS(ディストリ) からみということでしょうか。ブラウザ(Chromium)ではアクセス出来てるので、そちらはあまり気にしてませんでした。。
- Shiro(2022/09/15 04:45:46 UTC): 昨日はシステムのmbedtlsでやってできたんですが、mbedtls-internalでやったらこちら(Ubuntu)でも再現しました。mbedtls側に何かありそうです。試しに最新のmbedtls-3.2.1で試してみてもだめだったんですが、2.27.0だとうまくいきました (ちなみにシステムのmbedtlsは2.16.3)。新しいバージョンになってチェックが厳しくなったのかもしれません。
とりあえず回避するには、
tools/tls/Makefile.in
のMBEDTLS_VERSION
を2.27.0にしてconfigureからやってみてください。
- hamayama(2022/09/15 04:52:20 UTC): 本件ですが、mbedTLS で、SHA-1 等がデフォルトで OFF になったためのようです。
https://github.com/Mbed-TLS/mbedtls/blob/development/docs/3.0-migration-guide.md#strengthen-default-algorithm-selection-for-x509-and-tls
それで、ext/tls/tls-mbed.c に以下のパッチを当てて、一応動作することは、確認できました。
https://gist.github.com/Hamayama/323401fac11fdc33144c07acc8bc4b26
デバッグ方法は、上記のパッチで、
#define DEBUG_LOG_ON によって、mbedTLS のログをファイルに出力できます。
( DEBUG_LEVEL と DEBUG_LOG_FILE は、適当に書き換えてください )
今回は、以下のログが取得されました。C:/Gauche/Gauche/tools/tls/mbedtls-3.1.0/library/ssl_cli.c:2801: server used HashAlgorithm 2 that was not offered C:/Gauche/Gauche/tools/tls/mbedtls-3.1.0/library/ssl_cli.c:3108: bad server key exchange message
HashAlgorithm 2 というのが、SHA-1 のことになります。
( 数値が以下の enum に対応します。
https://github.com/Mbed-TLS/mbedtls/blob/ce7f18c00be0c97d214fbb04be8b532b5d440e94/include/mbedtls/md.h#L55 )
それで、上記のパッチで、
#define ALLOW_MD_SHA1 によって、SHA-1 を有効にできます。
実行結果は、以下のようになりました。gosh$ (use rfc.http) gosh$ (http-get "artscape.jp" "/exhibition/traveling/index.html" :secure #t) "200" (("date" "Thu, 15 Sep 2022 04:01:08 GMT") ("server" "Apache") ("x-frame-options" "SAMEORIGIN") ("accept-ranges" "bytes") ("transfer-encoding" "chunked") ("content-type" "text/html")) ...
ただ、SHA-1 の使用は危険とのことで、よくなさそうですが。
https://ja.wikipedia.org/wiki/SHA-1
また、Chrome では、普通に見られるので、
サーバー側では、何か別のアルゴリズムに対応していて、
mbedTLS でそれをうまく認識できないとか、そういう問題もあるのかもしれない…
- osmnaka(2022/09/15 10:25:52 UTC): Shiro さん、hamayama さん、検討ありがとうございます。hamayama さんのパッチで、当方でもアクセス OK 確認できました。また、詳細な説明もありがとうございました。
お二人の検討であれっと思ったのは、自分の報告で、レンタルサーバ(FreeBSD13)上からはアクセスできた、って話、これも mbedtls-internal でコンパイルした gosh でしたので、今、再確認してみたところ、別のエラーが出てしまいました(詳細は上に追記しときます)。夢を見ていたのかなんなのか、今となっては不明ですが、レンサ上の動作報告は忘れてください。
- hamayama(2022/09/17 11:22:19 UTC): その後、もう少し調べたので。追記しておきます。
どうも、(A) 証明書の署名の SHA-1 と、(B) 鍵交換に使われるサーバー署名の SHA-1 の
2 種類があるみたいで、後者の (B) は、まだ許容されているようです。
例えば、Chrome で artscape.jp にアクセスして、
デベロッパー ツール (Windows では [F12] キー) を開き、
Security タブを選択すると、以下のように表示されます。Connection - obsolete connection settings The connection to this site is encrypted and authenticated using TLS 1.2, ECDHE_RSA with P-256, and AES_128_GCM. ・The server signature uses SHA-1, which is obsolete. Enable SHA-2 signature algorithm instead. (Note this is different from the signature in the certificate.)
前回のパッチだと、mbed_connect_common() に追加した、前半の 3 行のmbedtls_x509_crt_profile crt_profile_for_test = mbedtls_x509_crt_profile_default; crt_profile_for_test.allowed_mds |= MBEDTLS_X509_ID_FLAG( MBEDTLS_MD_SHA1 ); mbedtls_ssl_conf_cert_profile( &t->conf, &crt_profile_for_test );
が (A) の許可に対応し、残りの 1 行のmbedtls_ssl_conf_sig_hashes( &t->conf, ssl_sig_hashes_for_test );
が (B) の許可に対応するようです。
それで、(A) については、2016 年には SHA-1 の証明書を発行しないことになっており、
もう対応する必要性がないと思われます。
(参考URL:https://www.cybertrust.co.jp/blog/ssl/regulations/sha1ms.html )
このため、一応、上記のパッチでは、(A) の対応を削除しておきました。
- Shiro(2022/09/20 04:25:39 UTC): この件、mbedTLSの対応も含め今後アップデートがあるかもしれないので https://github.com/shirok/Gauche/issues/844 でトラックします。 何か追加情報などあれば遠慮なくissueにコメント足していってください。
INSTALL.ja.adoc(Gauche-0.9.12.tgz)の誤植?
「TLS/SSL のサポート」のコンパイルオプションの説明の部分の2箇所について、
`--with-tls=mbedtls` :: include only axTLS support
mbedtls と axTLS が整合してない気が。
`--with-tls=mbedtls--internal` :: During the build, MbedTLS source is downloaded
mbedtls--internal は - が一つ多い?
細かいところですが気づいてしまったのでご報告まで。
- Shiro(2022/09/14 18:18:59 UTC): ありがとうございます。修正しておきました。
set! の使い方について
かなり基本的なことかもしれませんがよろしくお願いします。
(define number_a) (define (change_a number) (set! number_a number)) (change_a 1) number_a
では number_a は 1 を返すわけですが、この関数(change_a)を一般化して
(define (change_number character new) (set! character new))
としてどんな変数も任意の数に(数とは限りませんが)変えられるように試みました。しかし
(change_number number_a 2) number_a
はそのまま 1 を返します。set! をこのように使うことはできないのでしょうか? number_a ではなく character に 2 が代入されるならまだ理解できるのですが。
Shiro(2022/08/17 01:52:51 UTC): set!
は関数ではなく、最初に渡されるパラメータは評価されずにそのまま変数名として扱われます。2番めの
(define (change_number character new) (set! character new))
では、character
という変数自体に、new
の値をセットします。character
という変数の中身が元は何であったかは関係ありません。change_number
の呼び出し側で何を渡そうが動作は変わりません。
このcharacter""という変数は、仮引数として導入されたもので、関数
change_number"""の中だけで有効で、抜けたら消えてしまいます。
質問者さんは他のプログラミング言語は何かご存知ですか? Schemeに限らず、ほとんどのプログラミング言語(引数が値渡しである言語)では同じ動作になります。例えばCで書くとこうなります:
int number_a = 1; void change_number(int character, int newvalue) { character = newvalue; }
移植可能な標準入出力バイナリポート
shimon(2022/08/07 09:44:54 UTC): 最近、Schemeでプログラムを書いてみたいと思い、手始めにcatコマンドなどの小さなプログラムを書いているのですが、標準入出力をバイナリポートとして扱う移植可能な方法が見当たりませんでした。例えば、current-input-port
はデフォルトの入力ポートを返しますが、R7RS smallの仕様ではこのポートはテキストポートとされています。Scheme処理系非依存かつプラットフォーム非依存(アプリケーション組み込みなどを想定せずにホストOS非依存ぐらいでもよい)な環境で標準入出力をバイナリポートとして扱う方法は存在するのでしょうか。標準はR7RS small + SRFIなどを想定しています。
GaucheやChibiなど一部のScheme処理系は処理系依存の仕様でテキストIOとバイナリIOを混在できることがあるようですが、例えばSagittariusは違うようです。
コード(qcat.scm):
(import (scheme base)) (define (cat iport) (let ((c (read-u8 iport))) (when (not (eof-object? c)) (write-u8 c) (flush-output-port) (cat iport)))) (cat (current-input-port))
実行(OS: Ubuntu 22.04, シェル: Zsh, Gauche: 0.9.10, Chibi: 0.9.1, Sagittarius: 0.9.8):
% echo foo | gosh -r7 qcat.scm foo % echo foo | chibi-scheme qcat.scm foo % echo foo | sagittarius -r7 qcat.scm Unhandled exception Condition components: 1. &assertion 2. &who get-u8 3. &message "binary-port" required, but got #<transcoded-port utf8-codec #<binary-input-port stdin>> 4. &irritants () 5. &stack-trace stack trace: [1] get-u8 [2] cat src: (read-u8 iport) "qcat.scm":4 [3] eval [4] main-loop [5] dynamic-wind [6] script
- hamayama(2022/08/08 02:20:39 UTC): 少し調べてみました。
R7RS small では、確実に stdout を binary-port に割り当てることは、できなさそう。
R6RS では、standard-input-port / standard-output-port / standard-error-port
が binary-port になっているもよう。
後は、SRFI-170 POSIX API で、
(fd->port 1 binary-output) などとして、stdout の bainary-port が得られそう。
サポートしている処理系が少ないが…
<参考URL>
https://small.r7rs.org/wiki/WG1Ballot4Results/
WG1Ballot4Results (R7RS)
Converting current-{input,output}-ports to binary (却下)
https://compassoftime.blogspot.com/p/scheme-8.html
時の羅針盤@blog - はじめよう Scheme 8 (R6RS)
https://www.scheme.com/tspl4/io.html
The Scheme Programming Language (R6RS)
Chapter 7. Input and Output
procedure: (standard-output-port)
returns: a fresh binary output port connected to the standard output stream
https://srfi.schemers.org/srfi-170/srfi-170.html
SRFI-170 POSIX API
https://docs.scheme.org/srfi/support/
SRFI Table (サポート状況)
- shimon(2022/08/10 09:52:31 UTC): 回答ありがとうございます。なるほど、どうも無さそうなのですね。新しいプログラミング言語を学び始めた時にやってみることとしてしばしばUnixコマンドやbrainfuckの実装が挙げられると思いますが、Scheme(というかR7RS small + SRFI)だと少なくない処理系が対応している規格の範疇では難しそうですね。個人的には、
current-input-port
などが返すポートをバイナリポートとしても扱える処理系に限定して実装を進めてみたいと思います。ありがとうございました。
- shimon(2022/08/10 10:00:23 UTC): また、比較的エレガントで小さな規格があるというSchemeの特性上、何らかのリファレンス実装に向いているかもしれないと思い、すると処理系依存の動作は避けたいとも考えていましたが、避けるのは難しいのかもしれませんね。
- Shiro(2022/08/17 02:01:28 UTC): これはR6RSの時に議論になったんですよね。textual/binaryを厳密に分けたまま、綺麗に標準入出力に対応するのって案外難しいんです。R6RSでも、
standard-input-port
とcurrent-input-port
を混ぜて使ったらどうなるかはわからない。 R7RSではいつかlargeで入れることになると思うんですが、まだ先だと思います。
- shimon(2022/08/21 10:46:50 UTC): まあ、簡単じゃ無さそうな気は素人ながらもします。R6RSの
standard-input-port
とcurrent-input-port
の話は、例えばCでいうとwrite
とfputs
(あるいはfdとFILE?)みたいなものでしょうか。個人的には、基本的な操作なので未規定な部分があるとしても欲しいなとも思いますが(素人考えですが、例えばwith-input-from-file
みたいなインターフェース)、textual/binaryが独立していない実装も許可されているようで実際そのような実装も少なくないようなので、現状手元ではbinary-port?
で標準入出力を事前に検査してみています。
rfc.zlib の window-bits について
いつもお世話になっています。 rfc.zlib を使って gzip で圧縮されたファイルを読もうとして、window-bits についてちょっと悩んでおり、ご教示いただけるとありがたく、、
Gauche のマニュアルから、window-bits の値は
8-15 | zlib |
24-31 | gzip |
40-47 | zlib/gzip 自動判定 |
を使うものと思って、
(port->string (open-inflating-port ip :window-bits 24))
としたところ、 ファイルサイズが 580 byte 前後以下の gz ファイルは読み込みできるのに対し、それ以上のサイズでは、
*** ZLIB-DATA-ERROR,IO-READ-ERROR: inflate error: incorrect header check
というエラーになってしまいました。 エラーメッセージでググっても、gz ファイルが不正、等しか見つけられず、、 それで、window-bits の値を変えてみてたところ、
<580 | 580< | |
16 | o | o |
24-27 | o | x |
28-31 | o | o |
32 | o | o |
40-43 | o | x |
44-47 | o | o |
という感じに。 Gauche のマニュアルからの予想では、24-31 と 40-47 では gz の展開OK、と思ってました。 zlib の挙動の詳細等わかっておらず、基本的に理解不足なのかと思いますが、どう考えたらいいんでしょうか。
- hamayama(2022/05/06 08:38:39 UTC): 以下のリンクによると、
window-size は、圧縮時より小さい値にしてはいけないようです。
https://docs.python.org/ja/3/library/zlib.html#zlib.decompress
- osn(2022/05/06 23:51:07 UTC) 質問者です。コメントありがとうございます。なるほど、zlibのドキュメントにも
If a compressed stream with a larger window size is given as input, inflate() will return with the error code Z_DATA_ERROR ...
とありますね。ただ、となると、展開時には圧縮時に使用した window-bits は通常は知らされてないと思いますので、展開時の値としてこの条件を安全に満たすのは最大値(31 or 47)のみとなりますね。。
REPLでのincludeのパス
EmacsでGaucheをREPLで使っていての疑問です。
ある階層で.scmファイルをEmacsで開き、その中で
(include "a.scm")
とすると
*** ERROR: include file is not readable: "a.scm"
となります。
端末で gosh と打ち "gosh > " プロンプトを出して同様に行っても同様なメッセジが返ってきます。
しかしフルパスで指定すると、例えば
(include "/home/user/***/a.scm")
とすると読み込まれます。
一方端末でscmファイルを gosh ***.scm とすると、その中のincludeは相対パスでも読み込まれます。
REPLでは相対パスでincludeできないのでしょうか。
- Shiro(2022/04/19 21:23:23 UTC): includeは、ファイルが相対パスで来た場合、「現在ロードしているファイルからの相対」で探しますが、REPLでは「現在ロードしているファイル」が無いので探せない、という状態です。まあプロセスのカレントディレクトリを使っても良いのですが、それだと実行環境によって結果が違うことが出てくるのがちょっと嫌ですね。
そもそもloadでは済みませんか? トップレベルでロードできるファイルならloadで充分で、includeは他のフォームの途中に挿入したいといった特殊な用途に使う想定です。
- 質問者: お答えいただきありがとうございます。カレントディレクトリではなくファイルが始点となるのですね。
load 、もしくは (include (string-append (current-directory) "/*.scm")) で……いや、やはり(load "./**.scm") で行きます。
sxml の変換について
こんにちは。Makikiを扱っていての疑問です。 単純に
(print (srl:sxml->html `(sxml (html (p 1)))))
としますと
<sxml> <html> <p>1</p> </html> </sxml>
と返って来ますが、Makikiで
(define-http-handler "/" (lambda (req app) (respond/ok req '(sxml (html (p 1))))))
としますと「"SXML Error: \"sxml->html - unexpected type of node: \" 1"」となってしまうのは何故でしょうか。ご存知の方がいらっしゃいましたら、どうぞお願い致します。
- 齊藤(2022/04/14 02:27:13 UTC) : Gauche-makiki の responce/ok の説明によるとその場合には
sxml:sxml->html
を用いて変換されるという規則が書かれています。 つまり端的に言えばsrl:sxml->html
とsxml:sxml->html
の挙動の違いであるということになります。
Gauche が元にしているOleg Kiselyov による SXML 仕様には要素として数値が現れる場合が含まれていないように見えます。 厳密に言えば要素として数値を使うべきではない (事前に文字列に変換しておくべき) のだろうと私は考えますが、仕様中に例として示されている中には数値として書かれている部分があるのでそういう拡張も (うっかり例として書いてしまう程度には) 一般的ということなのかもしれません。
- 質問者
お答えありがとうございます。
(number->string 数値)
と文字列に変換する一過程を加えることで表示できるようになりました。
スクリプトで乱数を出すには
たとえば random9.scm という名前で
(use srfi-27) (print (random-integer 10))
というファイルを作って gosh random9.scm を実行すると、0~9の乱数ではなく常に同じ数が返ってきます。
「これが噂の参照透過性か!」と思いましたがインタプリタで
(print (random-integer 10))
を評価すると
gosh> 乱数 #<undef>
と一回毎に違った数がランダムに返ってきます。(ランダムなので同じ数が返ってくることがありますが)
なので参照透過性などではなく単にメモリか環境下に入った値が返ってきているだけかもしれません。
これが当たっているか、そしてスクリプトでランダムを実現するにはどうすればいいか、ご存知の方がいらっしゃれば、お教えいただけませんでしょうか。
よろしくおねがいします。
- Shiro(2022/01/15 18:48:20 UTC): インタプリタでも、再起動すれば同じ乱数系列が出てきますよ。
インタプリタで複数回random-integerを評価して違う値が返ってくるのは、一つのスクリプト内で
複数回random-integerを呼び出すことに相当します。次の内容のスクリプトを実行してみてください。
(dotimes [10] (print (random-integer 10))
- 実行毎に違う結果を得たいなら、ランダムな種(seed)で初期化する必要があります
(これはGauche特有の話ではなく、どんなプログラミング言語でもソフトウェア乱数発生器に共通の仕組みです)。
srfi-27を使っているなら、スクリプトで
random-integer
を呼ぶ前に一回だけ(random-source-randomize! default-random-source)
を実行してください。
- 質問者
ありがとうございました。スクリプトを
(use srfi-27) (random-source-randomize! default-random-source) (print (random-integer 10))
としてみると、呼ぶ度ランダムな数が出るようになりました。
「(これはGauche特有の話ではなく、どんなプログラミング言語でもソフトウェア乱数発生器に共通の仕組みです)」とのことですので試しに
(use srfi-27) (print (random-integer 10)) (random-source-randomize! default-random-source) (print (random-integer 10))
と書き直してみると
gosh random9-2 8 0 gosh random9-2 8 1 gosh random9-2 8 8 ......
となり、「なるほど」となりました。
ダウンロードのページのソースのリンクが切れてませんか?
いつもお世話になってます。なにか勘違いしてたらすいません。(2022/01/10 01:16:19 UTC)
- Shiro(2022/01/10 14:17:59 UTC): リリースナンバーに
-p1
がつくケースにスクリプトが 対応してませんでした。ありがとうございます。
sintax-parseの機能改善をしたい
こんにちは、racketを勉強中なのですが、躓いてしまったのでどなたかご教授お願いしたいです。
(syntax-parse stx [(foo x) #:do [(define result (if (syntax-property #’x ’class) (if (x のプロパティが myid である) ’success1,#f) (if (x がクラス myid を持っている) ’success2,#f)))] #:when result (cond ((eq? result ’success1) パターンマッチ) ((eq? result ’success2) ’myid を付けた後, パターンマッチ)) ] [(foo y) #’’y])
現在このような方法でsyntax-parseのパターンマッチの機能改善を図っているのですが、現在もうどうして良いのかわからなくなってしまいました。本当に初心者なので、公式サイトで調べ回っている状態です。
- やったことだけしか書かれておらず、何を目指しているのかわかりませんし何を答えればいいのかわかりません。 以下のような形式になっているとわかりやすい質問になると思います。
- 問題は何か (現状)
- どうあるべきだと考えるか (理想)
- どのように解決しようとしたのか (行動) ← 今はこれだけが提示されている
- 解決できなかったのは何か (結果)
- Shiro (2021/12/12 09:05:17 UTC): syntax-parseをいじろうって時点で、たぶんここにふさわしい
内容を越えてしまいそうなので、別ページを作って詳しくやりたいことを書いてみてください。
私はRacketいじってないので答えられるかどうかわかりませんが…
今までRacket関係のページって無かったかな。Racket:syntax-parse? とかでどうでしょう。
hash-tableで叶えていることを非破壊の仕組みでやってみたい
quenzi: 個人のページから転記しました。
競技プログラミング(atCoder)で過去問にschemeで挑戦しているのですが、数を数える系が来ると大概処理時間オーバしてしまいます。
それ系はhash-tableを使えば行けるのですけど、これを自己を更新するやつ(!つき)なしでやるとすると、どういったアプローチがあるでしょうか?
例)
a b c d d a c c a e f a ...
それぞれ何個あるか教えてください
↓これだと話は早い
(hash-table-update! ht item (cut + 1 <>) 0))
ところが私は!付けないでやりたいと思っている。
よろしくお願いします。
- Shiro(2021/10/17 05:20:57 UTC): こちらに書いて頂いて構わないんですが、私が見落とすかもしれないので、Scheme:初心者の質問箱や、Chaton Gaucheに書いてもらうかそこから「ここに質問書きました」みたいに誘導してもらえるとありがたいです。
- さて、何らかの理由で非破壊的にやりたいということですね。Gaucheは 「性能が欲しければ遠慮なく破壊的操作使って」という方針なので、非破壊的操作はあまり最適化してません。 非破壊的ハッシュテーブルはGaucheRefj:scheme.mapping.hashがありますが、これは今のところ毎回ハッシュテーブルをコピーするので遅いです。
- O(log n)で良ければ、GaucheRefj:data.imapという変更不可な赤黒木があります。 計算量的には、関数型データ構造の木として普通の実装です。ただ、Gauche向け最適化はやってないので 果たして性能が出るかどうかはやってみないとわかりません。
- ハッシュマップ系の関数型データ構造としてはHAMTがけっこういけてると思うんですが、 Schemeだけでこれを実装して性能が出るかどうか…需要が大きければ組み込みでサポートしますが。
- quenzi: 思ったより回答を頂けたのが早くて驚きました。ありがとうございます。
- 何らかの理由ですが、どこかで「!は粋じゃない」というようなことを読んだかして、ならなるたけそうしようという程度のことでした。
- data.imapを使ってみました。
(define (inc key ht) (hash-table-update! ht key (lambda (n) (+ n 1)) 0) ht)
↓で良いかなと思いまいたが、処理が終わらなくなってしまい芳しくなかったです。単純な置き換えをしただけなのでほんとかどうかは怪しいですが。(define (inc key immap) (imap-put immap key (+ 1 (imap-get immap key 0))))
ともあれ、更新の仕方はどうあれhtと返すなら、使用感も変わりませんし、特にこだわるようなことでもなかったですね。m(__)m
C でいう構造体、あるいはOOのインスタンス変数
mtmap です。連続ですみません。違う話題のため新しい項目を立てました。
- Cでいう構造体(OOではインスタンス変数)に相当するものを実装したいと考えています。構造体の中身は高々数個のメンバ変数を含んでいるだけですが、メンバ変数が(多数のメンバ変数を持つ)別の構造体になっているものがあります。
- 直観的には、ハッシュテーブルを利用して、メンバ変数の名前とその中身を対応させる実装が考えられますが、ハッシュテーブルの中身に別のハッシュテーブルを入れると、内側のハッシュテーブルの参照が難しくなる(OOのメソッドチェーンのような書き方ができない)ような気がします。何か便利な方法はあるでしょうか?
- scheme らしい方法?として、入れ子になったリスト構造を設計して、car と cdr を駆使してメンバにアクセスする関数を書くということも考えられますが、こちらの方が自然(高速)でしょうか?リスト内の位置を意識しなければいけないことに加えて、少しまわりくどい気がしています。
- ハッシュテーブルの key として、シンボルを用いることができると思いますが、文字列を用いるよりも高速という理解で正しいでしょうか?key をシンボルにした場合、束縛を持つシンボルを key としても干渉は起こらないでしょうか?(これは私が確かめれば良い話かも知れません) 実は先日お尋ねしたリーダマクロの件は、ハッシュの key に括弧やコンマが含まれるため、シンボルではなく文字列にせざるを得ないという事情がありました
- ハッシュテーブルを(入れ子も含めて)リテラル表示する手続きはあるでしょうか?デバッグでハッシュテーブルの中身を確認したいと思うのですが、適当なものを見付けることができませんでした。
- これは別の話題かもしれませんが、下記の REPL の実行で、(display a)の表示が(A B C)となるのはどのような理屈でしょうか?A、B、Cがシンボルのように見えて違和感を感じました。
gosh> (define a '("A" "B" "C")) a gosh> a ("A" "B" "C") gosh> (display a) (A B C)#<undef>
どうぞよろしくお願い致します。
- Shiro(2021/08/28 07:35:06 UTC):
- 構造体: Scheme標準の方法としてrecordがあります。R7RSモードならそのまま使えます。
そうでなければ
gauche.record
をuseします。GaucheRefj:gauche.record。 Gauche専用になって良ければクラスの方が便利です。GaucheRefj:define-class。 Gaucheのオブジェクトシステムについてざっと知りたければGaucheRefj:オブジェクトシステムの紹介を。 - 入れ子になったレコードの参照は、Gauche専用で良ければGaucheRefj:~が便利です。
- ハッシュテーブルのキーは文字列よりシンボルの方がずっと高速です。同じ表示名のシンボルは メモリ上で単一のインスタンスになっていて、ポインタ比較だけで済むので。
- データとしてのシンボルと、コード中のシンボルは別の世界にあります。干渉することはありません。
- 普通のシンボルとして許されない文字を含むシンボルも、縦棒
|
で囲むことで使えます。gosh> (define |Weird symbol| '|(what?)|) |Weird symbol| gosh> |Weird symbol| |(what?)|
- ハッシュテーブルはリテラル表示可能とは限らないので(ハッシュ関数や比較関数をカスタマイズ できるので)標準のリテラル表示はありません。デバッグで確認したいだけならGaucheRefj:hash-table->alistが便利です。
display
はそういうものです。メッセージを表示したい時なんかに、文字列で組んだメッセージにいちいちダブルクオートがついたら変でしょう? そういった、ダブルクオートを表示したくない時のために使う手続きです。Schemeデータとして表示したい場合はwrite
を使ってください。
- 構造体: Scheme標準の方法としてrecordがあります。R7RSモードならそのまま使えます。
そうでなければ
- mtmap: 先程は慌てて先日のお礼も忘れコミットしてしまい、失礼いたしました。丁寧な解説ありがとうございました。
- record と hash-table->alist の紹介ありがとうございました。私にとってOOは大げさで、構造体が使えれば十分だと思っています。(ruby でクラスとかでこりはじめると考えることが増えてしまい、OOに疲れているというのもあります...標準のクラスを利用する以外はCのようなフラットな構造で、ボトムアップに組み立てられる方が好みです。自分だけのためのスクリプトですので)
- 縦棒で囲むことは知っていたはずなのに忘れていました。確かにこれでシンボルが使えそうです。
- display と write の違いの説明ありがとうございました。Gauche本を良く読むと 14.2.1 (見落としていました)と 14.2.2 で説明されていました。大変失礼致しました。
- Shiro(2021/08/29 03:20:32 UTC): Gaucheの(というかCLOS系の)オブジェクトシステムでは、 クラスは構造体がちょっと便利になったもの程度です。というのはメソッドがクラスに属さないので。 クラスを構造を決めるのだけに使って、それに対する操作は普通の手続きで書く、というのも 私はよくやります。「クラス指向」のオブジェクトシステムでは、 クラスにメソッドを束ねたり名前空間の役割も持たせたりしてるので、 クラスの責務とかカプセル化とか考えることが増えちゃうんですよね。
- mtmap: ありがとうございます。Gauche のオブジェクトシステムも触ってみたいと思います。
let の中の define
mtmap です。現在、自作の ruby プログラム(1000行ぐらい)を少しずつ gauche プログラムへ翻訳しながら、scheme の勉強を進めています。教えていただきたいことと、少しお願いがあり、発言させていただきます。
- 私の書き方か、emacs のインデントが悪いのかもしれませんが、scheme のコードは右に流れる(左下に空白ができる)傾向があると感じました。scheme に対して意地悪なコードになっていますが、以下の二つの疑似コードを御覧ください。
コード1:
(define (function x) (let ((variable 0) (long-name-function (lambda (x) (let ((long-name-variable (if (...) (begin ... ...) another-long-name-function))) ...)) ...))
コード2:
(define (function x) (let () (define variable 0) (define (long-name-function x) (let () (define long-name-variable (if (...) (begin ... ...) another-long-name-function)) ...)) ...))
- クラスを導入することなくローカルな関数を自然に使えるのは便利だと感じました。long-name-function は function 内で複数回呼ばれることを想定しています(計算量削減のためです)。以下がお尋ねしたいことです。
- コード2は scheme で許されている書き方でしょうか?(gauche では期待通りに動いているようです)
- 上記の二つのコードの書き方で、同じ動作が保証されるでしょうか?
- 言い替えれば、let の第一引数を使わなければできない状況はあるでしょうか?
- コード2は手続き言語的であることは承知しているのですが、emacs の scheme モードのデフォルトの設定でコード1は上記のようにインデントされてしまいます。コード1をより見やすくするためにどのようなインデントをされているでしょうか?
- 以下、厚かましいお願いで恐縮です。
- リファレンスマニュアルに検索フォームを設けていただく可能でしょうか?初心者の私は頻繁に参照することになり、索引からたどるのが少々しんどくなってまいりました。https://practical-scheme.net/gauche/memo-j.html のページにあれば十分です。
- これは提案なのですが、O'REILLY 「プログラミング Gauche」のコーナーを設けていただくことは可能でしょうか?本に書かれていない内容とか、初心者にとって(私だけかも)理解しづらい点など、気づいたことを書かせていただく場所があれば、私なりに貢献できるかもしれないと考えました。
- 「プログラミング Gauche」の付録Bにある emacs の設定を公開していただくことは可能でしょうか?タイプすれば良いだけの話なのですが、少々長いのでダメ元でお尋ねしています。
どうぞよろしくお願い致します。
- Shiro(2021/08/28 07:22:24 UTC):
- let中のdefine(internal define)について
- まず、コード2でfunction内の
(let () ...)
はいりません。 internal defineは(define (function x) <この部分>)
でも使えます。 - コード1とコード2は同じです。厳密には、internal defineはletrec*になります。下の2つが等価です。
(define (function) (define a 0) (define (b x) ...) expr...) ≡ (define (function) (letrec* ((a 0) (b (lambda (x) ...))) expr...))
- 単なるletとの違いは、letrec*ではbの本体内でaが参照できることです。(上のコード1では、 long-name-functionのlambdaの中からvariableは見えません。
- コード2は取り立てて手続き型言語的ではないと思います。定義を並べているだけなので(定義を並べるのは関数型でも常道です)。特にinternal defineを多用するのはSchemeらしい書き方です。
- どうしてもコード1のように書かざるを得ない場合、Gaucheでは
$
というマクロがあります。GaucheRefj:$
- まず、コード2でfunction内の
- リファレンスマニュアルの検索フォームについて
- 次のURLを、例えばgoshという名前のブックマークとして登録しておくと(bookmarklet)、ブラウザのurlフィールドで
gosh <TAB> <手続き名>
と打てば直接リファレンスマニュアルに飛べますpractical-scheme.net/gauche/man/?l=jp&p=%s
- 次のURLを、例えばgoshという名前のブックマークとして登録しておくと(bookmarklet)、ブラウザのurlフィールドで
- 『プログラミングGauche』サポートページ
- あああ、Karettaに置いてたのがサービス終了で消滅しちゃってたんですね。 しまった。正誤表とかどうしたかな。とりあえずWiLiKi内に新たにつくるのがいいかな。
- emacsの設定は徐々に進化してるので、githubかどっかにあげとくのが良さそうです。
- let中のdefine(internal define)について
- mtmap: 添削ありがとうございました。
- 添削していただいたコード2でも問題ないのですね。「定義を並べるのは関数型でも常道です」は目から鱗でした。私は勘違いをしていたようです。このほうがインデントがすっきりするので安心してそのように書くことにします。
- ブラウザの設定方法の御教示ありがとうございました。scheme 以前のことが分かっておらず申し訳ありませんでした。(追記)firefox ではブックマークの登録の最後の%sは不要のようです。
- mtmap: 助言を頂いて、調子に乗って該当しそうな (let () ... ) を手あたり次第削除してみたら、動かない場合があることが分かりました。
(define variable (let () (define ...) ...))
- 上記の場合は (let () ...) が必要ですよね?変数 variable の値を確定するために値を返す (let () ...) が必要だと理解しました。
- 手続き型言語から scheme に入ると、定義(束縛)の方法がいろいろあって、使い分けが難しいです。最初は、「ローカルに定義したい時は常に (let () ... ) 系を使う」と理解していました。このあたりをうまく整理して理解したいです。
- (追記) Gauche 本の pp.20-21 のdefineの説明を注意深く読む必要がありました。Shiro さんのお手を煩わせるのも申し訳ないので、まとめておきます。むしろ define が(いわゆる)変数の定義にも手続きの定義にも(語法を変えて)使用されていることが私を混乱させていました。もしも誤解があれば御指摘ください。
- define の第1引数がシンボル(変数名)の場合は、変数名を第2引数の式の値に束縛し第3引数以降はとらない(エラーとなる)。
- define の第1引数がリスト(手続き名)の場合は、手続き名を第2引数以降の一連の式に束縛する。
- (define proc (lambda (...) ...)) は変数名 proc を第2引数の式(無名の手続き)に束縛する。従って proc を評価する際に束縛された手続きが評価される。lambda の第1引数(...)は手続きの仮引数(ローカル変数)である。
- (define variable (let (...) ...)) は変数名 variable を第2引数の式(の評価値?)に束縛する。let の中で定義された変数はローカル変数となる。
mtmap (マルチスレッド map)
数日前に define に関する頓珍漢な質問をしたものです。お蔭様で racket のソースコードは理解できました。
気を良くしていろいろ調べていたら、racket には pmap というライブラリがあることに気づき、私がやりたかったことはこれであっさり解決できることが分かりました(残念)。勉強になったので無駄ではありませんでしたが。。。
さて、Shiro さんにいろいろ教えていただいたので、gauche で該当する手続きを書いてみました。下記のソースコードで何か問題があれば御指摘お願いします。
- 6行目の (lambda () (cons j (work ...))) は cons ではなく values の方がいいかもしれないと思いましたが、うまくできませんでした。cons の方が結果的にシンプルに見えますが、どちらが実行時間が短いでしょうか?
- テストの最初の行にある (define work (lambda (j) ... )) は mtmap と対応するracket の pmapp-c では (define work '(lambda (j) ... )) とする必要があるようです。racket にあわせることも考えましたが、私のスキルではうまくできなかったことと、mtmap のコードの方がシンプルだと感じて互換性を無視しました。
- racket では事前に workers の数を設定して、pmapp-c では workers の数を指定しない仕様になっていますが、私の mtmap では no-of-workers を指定する仕様にしました。おそらく racket は workers の数をグローバル変数でやりとりする実装になっていると思いますが、引数で指定するのとどちらが良いか判断できませんでした。mtmap の引数の順序も適当で、scheme らしい順序があるかも知れません。
- 以下のコードは、エキスパートの方にとってはすぐに書けるものかもしれませんし、簡潔性を重視する scheme の思想にそぐわない気がしますが、同様の機能が標準で備わっていればスクリプト言語として非常に便利なので、私のような超初心者を呼び込めるような気がしました(余計なことかも知れません)。もしも同様の機能が gauche になく、私のソースコードが使えるようでしたら、適宜改良して使用していただいて構いません。これがせめてものお礼になればいいのですが。。。
どうぞしくお願い致します。
(use srfi.1) (use srfi.27) (use control.thread-pool) (use control.job) (use data.queue) (use gauche.process) (define (mtmap work lst no-of-workers) (let* ((pool (make-thread-pool no-of-workers)) (list-length (length lst)) (jobs (list->vector lst)) (results (make-vector list-length))) (for-each (lambda (j) (add-job! pool (lambda () (cons j (work (vector-ref jobs j)))) #t #f)) (iota list-length)) (wait-all pool) (for-each (lambda (j) (let ((result (job-result j))) (vector-set! results (car result) (cdr result)))) (dequeue-all! (thread-pool-results pool))) (vector->list results))) ;;; TEST mtmap (define work (lambda (j) (let ((t (random-integer 20))) (run-process `(sleep ,t) :wait #t) (display (list j t)) (newline) t))) (display (mtmap work '(A B C D E F G H) 4)) (newline)
- すみません。Wiki の入力ボックスで直接改良をしたため何度も修正してしまいました。
- Shiro(2021/08/20 09:41:14 UTC): 実は並列mapは既にあるんですが、充分にテストしてないので まだドキュメントしてませんでした。需要があるようなのでドキュメントしてオフィシャルにしようと 思います。 https://github.com/shirok/Gauche/blob/master/lib/control/mapper.scm
- mtmapについて気づいた点をコメントします
- 一番気になるのは、今のコードだとスレッドプール用に作られたスレッドが 関数から抜けてもタスクキューを握ったまま待ち続けて生き延びるので、何度も実行してると 待ち状態のスレッドがどんどん増えてゆくことです。スレッドプールをたくさん作るなら、 使い終わった後にシャットダウンしないとなりません。
- ただ、スレッドプールは作るのにも結構コストがかかるので、何度も作ってシャットダウンして…
ってやるのは効率悪いです。よくあるのは最初に一個作ってそれをずっと使うことです。
それなら、シャットダウンせずにプログラムを終了しちゃっても構いません
(どうせスレッドも破棄されるので)。
そのこともあり、上記
control.mapper
では「既存のスレッドプールで実行する」か、 「引数のリストをスレッド数に分割して固定的に仕事を振ってしまう」かを選べるようになってます。 - 添字と結果をconsするのは問題ないです。consはとても軽いので、 高速に回るループ内とか、大量のリストを使い捨てになる場合だとかは気にする必要がありますが、 今回は入力リストの長さと同じ回数ですから。
- ワーカーに仕事を振るのには
jobs
というベクタ経由にしなくても、例えばこれでいけます:(for-each-with-index (lambda (j e) (add-job! pool (lambda () (cons j (work e))))) lst)
- ワーカースレッドの数ですが、仕事が計算中心ならコアの数より増やしてもあまり意味がないので、
コアの数を取ってきてデフォルト値として使うという手はあります。ポータブルな方法は無いですが
Gaucheでは
sys-available-processors
で取れます。
- 質問者(以後、mtmap と名乗らせていただきます。自虐的に define も考えたのですが、scheme の手続き?と紛らわしくなるので mtmap にします。) コメントありがとうございました。
- 既に実装されていたのですね。僭越でした。公開を期待しております。
- スレッドープールが一つだけというのは、racket の pmapp-c の前処理と後処理で同じようなことをしていると推測すると納得できます。実際のところは分かりませんが。
- ワーカースレッドの数をコアの数(あるいはコア数-1)にしてもいいかも知れせん。コアを別の並列計算に使いたいこともあるかなと思い、面倒でも数を指定できるようにしました。
- for-each-with-index は盲点でした。勉強になりました。
- 私の TEST にあるように、work の計算時間に幅があって、終了したら直ちに次のジョブを拾うことを想定していました。計算時間が一様ならば、最初にジョブを等分して、というのはあると思います。
- racket の pmapp-c では work の後の (lambda ...) をクオートしなければいけないのですが、これは pmapp-c がマクロで実装されているからでしょうか?他の処理系の話なので、雑談とお考えください。
- Shiro: RacketのpmappはRacket独自のplaceという仕組みを使った並行実行です。スレッドはメモリを共有しますが、placeは(Racketプログラムからみて)それぞれが完全に独立したメモリ空間に置かれ、メッセージパッシングで互いにやりとりします。
- クロージャには呼出側の環境情報が含まれますから、メモリを共有しない他のplaceに渡すことができません。メッセージとして渡せるのはデータとしてS式に書けるものだけなので、クオートしてデータとして渡すようになっています。
- mtmap: ありがとうございました。pmapp について納得しました。でも逆に、私の mtmap でなぜうまくいったのか分からなくなってしまいました。たまたま work の定義が干渉を起こさなかったからでしょうか?
- mapper.scm 拝見しました。95行目の (cut sort <> < car) が気になりました。結果をまずリストにしてからソートしているように見えるのですが、私の mtmap のように結果を vector の所定の場所に代入していく方が計算量が少ないような気がします。言語実装の観点からはどうなのでしょうか? (sort を使った方が scheme 的のような気がするのですが、手続き的に書いた方が速そうに感じることもあり悩みます。)
- racket の話ばかりで恐れ入ります。将来リードマクロを使いたいと考えているため、gauche と並行で勉強していました。gauche に(|...| や #, 以外の)リードマクロはあるでしょうか?できれば登録した(スペースと括弧以外の)任意の文字(列)を接頭辞にして、それに続く文字列をシンボルとして理解させ、必要があればS式に変換させたいのです。例えば、少し人工的ですが、"F" で始まる "F(x,y).a" などのようなものです(gauche が case-sensitive で "F" で始まる手続きが定義されていないことを仮定しています)。
- gauche は日本語のマニュアルがしっかり書かれているので、大変ありがたいです。並行実行が簡単にできることが分かったので、まずは gauche をちゃんと使いこなせるようにしたいと思います。version 1.0.0 を楽しみにしています:-)
- Shiro(2021/08/21 05:13:56 UTC): mtmapではスレッド実行ですので、クオートしない
(lambda ...)
を渡すのが正解です。メモリ上の(環境を含めた)クロージャがワーカースレッドと共有されます。 - はい、mapper.scmでは結果を順に並べるのにソートしていて、これはリストが長大になると重くなります。リストの長さがそれほどでもなく、個々のタスクの計算時間の方が重ければ、無視できるコストです。使い方としては後者の方を想定していますが、リストの長さによって切り替える方が良いかもしれません。
- CL的な完全なリーダマクロはありません。裏技的に独自リーダーを使うことはできなくはないです (undocumentedですが、 https://github.com/shirok/Gauche-lisp15 でM式を読み込ませるのに使ってます)、リーダー本体を自前で用意しないとならないです。
- リーダマクロはDSLでちょっとした拡張を書くのには便利なんですが、
影響範囲が静的に決めづらいので開発規模が大きくなるほど制御しにくくなるのと、
DSLも凝り始めるとパーザから書いちゃった方がすっきりすることがあるので、
どうしようか迷ってます。(静的に決め辛いというのは、CLのようにロード中にreadtableが
いじれたりすると、静的解析のためにコードは読むが実行はしない、といったことが不可能になるので)。
#!token
を使って、ファイル単位で宣言的にどのreadtableを使うか指定させるなら まだいけますが、その場合も指定のreadtableをどこから読んでくるかとかいくつか 面倒な問題があります。
- mtmap: ありがとうございました。mtmap を助言に従い書き換えればちゃんと動くことが分かり安心しました。確かに一般的には個々のタスクの計算時間の方が重いので mapper.scm で十分に思いますが,必要になったら自作の mtmap を修正して併用することにします。
- 実は手元に ruby で書いた自作DSLパーサがあるのですが、拡張していくうちに見通しが悪くなってしまい、いろいろ調べているうちにPEG->gauche->scheme->racketと変遷した次第です。とりあえず括弧とコンマを許すシンボルがあれば十分なので、教えていただいたURLとPEGを勉強してみます。
- 1週間近くおつき合いさせてしまいお手数をお掛けしました。しばらく大人しくしていようと思います。また分からないことがあればその時はよろしくお願いいたします。
- (追伸) 少し検討してみたのですが、私のDSLの場合、scheme の中に埋め込んでschemeのパーサを借りるよりも別ファイルを独自にパースした方が見通しがよさそうです。ですのでリードマクロについては忘れてください。
- Shiro: どうぞ遠慮せずにどんどん質問してください。ここに書かれた内容は 私とmtmapさんだけでなく、このサイトを読んでいる全ての人に共有されます。 中には似たような疑問を抱いてた人がいて役に立つかもしれません。 このやりとりは、皆に役立つナレッジベースの一部になるのです。
REPL での define の挙動 (define の返り値)
Scheme に興味を持って勉強を始めました。全くの初歩的な質問ですみません。
いろいろな処理系を触って見たのですが、REPL での define の挙動が違っていることに気づきました。
例えば、
(define a 1)
とすると racket/chicken/guile/gambit では何も表示せずに次のプロンプトが現れますが、gauche では "a" が表示されます。
続いて、これは蛇足に近いですが、
(display (define a 1))
とすると racket/chicken/guile/gambit ではエラーが表示されますが、gauche では "a#<undef>" が表示されます。
これは racket のあるプログラムで、define を REPL の出力(上記の最初の例では"1"、実際は何かを評価している)を抑制するためだけに使用している(と思われる)例を見て気づきました。定義された識別子(シンボル?)は他で一切使われおらず、説明もないのでそう推論しました(初心者の私にとって気づくのにとても時間がかかりました)。
以下の3点の質問をさせてください。
1) REPL の出力を抑制するために define を用いるのは scheme の一般的な方法でしょうか?
2) もしそうでないとしたら、(gauche を含めて) REPL の出力を抑制する一般的な方法があれば教えてください(初心者にとって出力抑制に define を用いるのは自然には思えないのです)。
3) (define a 1) が値を返すのは合理的に見えるのですが、何か深い理由があるようでしたら教えてください。
どうぞよろしくお願いいたします。
- Shiro(2021/08/15 03:02:47 UTC):
define
の「返り値」は規定されていないので、何を返すかは処理系の自由です。そもそも、規格を厳密に読むと(define ...)
は「式」ではないので、返り値を使おうとすること自体が間違いだからエラーにする、という選択も可能です。そのへん、Schemeでは規格で決めずにおいて、各処理系の裁量に任せるという部分がたくさんあります。ちなみにGaucheで変数名を返しているのは特に深い考えがあるわけじゃなく、REPLで何か打ち込んだら反応が欲しいからとりあえず返しておくか、程度のことです。 - なので、「REPLの出力を抑制するために
define
」というのは特定の処理系のみで使える話です。 - ポータブルにREPLで出力を抑制したい場合、評価する式の戻り値を「ゼロ個の値」にする、という方法があります。
(my-expression)
を評価したいけどプロンプトに戻り値を出したくなければ、(begin (my-expression) (values))
とします。ただまあ、ちょっとまどろっこしいですね。
- 質問者 素早く丁寧な回答ありがとうございました。教えて頂いた方法は「この定義何に使うんだろう?」と悩まなくてすむのでよいと思いました。また私は「式」とそうでないものの区別がついていなかったことが分かり大変勉強になりました。
- 補足です。chez も試してみましたが、racket/chicken/guile/gambit と同じでした。あと、教えて頂いた方法を racket で define と置き換えて見ましたが、(my-expression) の評価(実際は (thread ...) です)が何故か?表示されました。racket では define を使うしかないのかも知れません。これは(scheme に準拠していない) racket と私の理解度の問題だと思いますが、後で読む人のために報告させていただきました。
- Shiro(2021/08/15 10:33:04 UTC): おや、手元のRacket v6.11だと出ないです
Welcome to Racket v6.11. > (begin (+ 1 2) (values)) >
- 質問者 こちらは racket 8.2 ですが、おそらく (thread ...) の評価が原因だと思います。他の処理系のためにお手数をお掛けするのは本当に申し訳ないので、これ以上は自分で調べたいと思います。gauche も使ってみたいと思います(O'REILLYの本は何とか入手できました)。
- 気になさるといけないので申し上げておきますが、該当のファイルは https://docs.racket-lang.org/guide/concurrency.html の 18.4 Channels で例としてあげられているプログラムです。(define result-thread (thread ...)) が意味不明なのです。(define result-thread ) を外しても、REPL に何か表示されて動いているようなので、result-thread はダミーだと判断して、最初の質問に至りました。深追いは不要です。丁寧に対応していただきありがとうございました。
- Shiro(2021/08/16 00:17:44 UTC): あーなるほど。これはREPLでの出力を抑制したいとうよりは、後でresult-threadにthread-waitするつもりで書き忘れたか、定義と式評価を混ぜたくなかったか
(REPLでは定義と式評価を混ぜこぜに入力できますが、例えば
let
の本体内では まず定義の並び、それから式の並び、としなければならないのでそれに揃えたか)、かなあと思います。
- 質問者 ここまで面倒を見ていただいて大変恐縮です。(define result-thread ) としたのは、将来にこのスレッドを制御するための準備だったのですね。。。実際に最後に thread-wait だけを追加すると待ち続けてしまうようですが、気持ちは理解できました。最初の質問は全くの見当違いでしたが、どうか御容赦ください。周囲に scheme 使いはおらずどなたかに何かヒントをいただければという思いでした。おかげさまで多くを知ることができました。ところで、gauche では control.thread-pool が該当する機能でしょうか。ポータブルなコード書けるように勉強したいと思います。ありがとうございました。
継続が止まらなくなるのですが、理由が分かりません
継続を使って関数を作ってみようと思っています。例として2つの引数を取ってその足し算をする関数、add を書いてみました
(define stack) (define (pre-add a) (+ a (call/cc (lambda (cc) (set! stack cc) 1))))
として(多分、1 でも何でも数ならこの場合良いはず) stack を「次に(pre-add に渡した引数の)数値を足す手続き」としてみました。
gosh> (pre-add 3) 4 gosh> (stack 4) 7
なので add 関数は
(define (add a b) (pre-add a) (stack b))
でいいと思い
(add 3 4)
としてみると何も返ってこずgoshが止まらなくなります。何故なのでしょうか。
- Shiro(2021/07/16 21:17:07 UTC): call/ccは「その後に行われる計算」を継続として取り出しますが、この「その後に行われる計算」はREPLの対話一回毎に区切られます。なので、
pre-add
をREPLで直接実行した場合、stackに入る継続は(lambda (x) (+ a x))
だけですが(継続に渡した引数xが、call/ccから戻り値として返ってくる形)、add
手続きにまとめた場合、継続にはその後にくる(stack b)
の計算も含まれてしまうのです。(lambda (x) (+ a x) ;; pre-addの残り (stack b)) ;; その後の計算
stack
を呼び出すと、それはまたxから戻ってくる形になるので、再びstack
が呼び出され…と無限ループになります。
- 質問者
add にまとめると
(define (add a b) (+ a (call/cc (lambda (cc) (set! stack cc) 1))) (stack b))
こうなってしまいstack = (+ a [x] (stack b)) = (+ a [x] (+ a [x] (stack b)) b) = (+ a [x] (+ a [x] (+ a [x] (stack b)) b) b) ...
と無限ループになるという理解で合っていますでしょうか。
- Shiro(2021/07/18 23:18:24 UTC): はい、そうです。
- 質問者 丁寧なご回答、ありがとうございます。
カッコで包むと不束縛変数でもエラーにならないのは何故でしょうか
(define rei (+ a))
これに「aは不束縛変数だ」とエラーになるのは分かるのですが
(define (rei) (+ a))
は #<closure (rei)> となるのは何故でしょうか。 またこれを
(rei) ((rei) 1) (rei 1)
とするとやはり「aは不束縛変数だ」と「引数の数が違う」とエラーが返ってくるのですが、この関数(?)はどうやって使うのでしょうか?
- Shiro(2021/07/10 08:30:14 UTC):
(define (rei) ...)
とカッコで囲むと、rei
という0引数の関数を定義したことになります。関数の中身は、それを呼び出すまでは評価されません。 (でないと、例えば相互再帰する関数を定義するのがややこしくなります)。 - 式の中で
(rei)
と書くと、これはrei
を関数として引数なしで呼び出すことになります。このときはじめてa
の値を取りにゆき、まだ束縛されてないのでエラーが出ます。(define a 2)
などとしてa
を定義してからもう一度呼び出せば2が帰ってくるはずです。 (rei 1)
というのは、1を引数として関数rei
を呼び出す式です。rei
は引数を取らない関数なので、「引数の数が違う」というエラーになります。- 引っかかりやすいポイント1: 多くのプログラミング言語では、式の括弧は単なるグルーピングで、余分に重ねても意味は変わりません。Lisp/Schemeでは括弧は「関数呼び出し」か「特殊形式(構文)」という意味があり、余分につけると意味が変わります。
- 引っかかりやすいポイント2: Lisp/Schemeは前置記法なので、
(rei)
というのは他のプログラミング言語でrei()
と書くのと同じです。(rei 1)
は他のプログラミング言語でrei(1)
に当たります。((rei) 1)
はrei()(1)
です。(define (rei) (+ a))
は、例えばJavascriptならfunction rei() { return +a; }
と書くのと同じです。
site(practical-scheme.net/gauche/) の リリース 0.9.10 のページ内にリンク切れ?
ご報告だけですが、Boxes や 改善の String%20cursors (同様の複数)をクリックすると、Error になってしまいます。Boxes は英語のページではなりません。
Boxes Error couldn't open input file: "gauche-refe-draft/Index-_002d-Bian-Shu-Suo-Yin-.html": No such file or directory String%20cursors Error wrong number of arguments for #<closure (search-from-toc lang draft? name)> (required 3, got 2)
- Shiro(2021/02/26 01:56:24 UTC): ありがとうございます。今はどうですか?
- 質問者(2021/02/26 02:30:52 UTC):やや改善しましたが、以下の状況です。
- 日本語の Boxes :Error 変わらず。
- String%20cursors:Error にはならないが、どのトピックスのリンクも、ユーザーリファレンスのトップが表示されるのみ。
- (ブラウザは chrome で、キャッシュのクリアは実施)
- Shiro: リンク貼り付けてもらえますか? 私がやってみた範囲では再現できないので。
- 質問者(2021/02/26 04:34:24 UTC):お手数おかけします。
https://practical-scheme.net/gauche/gmemo/?%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%200.9.10
のページ中の、
- srfi-195: 多値ボックス (Boxes) http://practical-scheme.net/gauche/man/?l=jp&p=Boxes
- ...詳しくはString%20cursorsを見てください。... http://practical-scheme.net/gauche/man/?l=jp&p=String%20cursors -> http://practical-scheme.net/gauche/man/gauche-refj/index.html を表示
- Shiro:ああ、わかりました! 日本語マニュアルの節名で引く場合は日本語名じゃないとだめなんで、そこが英語のままだったのが原因。今はリンク直ってます。Boxesでエラーになるのだけ想定外なんで調査中。
- Shiro: Boxesのエラーも修正 (マニュアルのトップに飛びますが、それは指定された項目が無かった場合の動作です)
- 質問者(2021/02/26 06:17:03 UTC): 速やかな対応、ありがとうございました!(0.9.10 へのバージョンアップで、変更不可なペア、に引っかかってしまったのでした。)
(iota 100 1) を評価しましたが、途中で ...) と表示され手抜きをされます。どこで誰が制限を掛けているのですか?
- Shiro(2021/02/16 03:00:35 UTC): それはREPL (対話環境) がやってます。出力が長大になる場合、ずらずら表示されると煩わしいので。
全部見たい場合は,pa
と打ってください。直前の結果が省略なしで表示されます。
表示制限を変えたい場合は,pm
トップレベルコマンドが使えます。,help pm
でヘルプが出ます。
- 質問者 2021/02/16 04:53:25 UTC : なるほど! ありがとうございます。
port-position はどうしたら使えるようになりますか?
単に使おうとすると、 *** ERROR: unbound variable: port-position となってしまいます。 (use srfi-192) がいるのかとも思いましたが、srfi-192.scm は見当たらず、Gauche に組み込まれてるはず?とも、悩んでます。
- Shiro(2020/12/20 08:23:10 UTC):
port-position
もsrfi-192.scmも0.9.10に入っています。もしかして古いバージョンのGaucheではありませんか?
- 質問者(2020/12/20 09:14:35 UTC):大変失礼しました。ご指摘のとおりでした。比較的更新の早いディストリ(arch linux)を使ってたので、油断しました。言い訳です。お騒がせしました。
(fold and #t '(#t #t)) がエラーになるのはなぜですか?
ERROR:invalid application: (#<syntax null#and> #t #t) となってしまいます。
(fold + 0 '(1 2)) => 3 と同様のことをしているつもりなのですけども。
- Shiro(2020/11/20 02:32:43 UTC):
and
は関数ではないので、引数として渡すことはできません。Gaucheではたまたまand
を評価すると#<syntax null#and>
という値らしきものが返ってきますが、本来and
は単独で評価値を持つものではなく、(and ...)
という形式で使われた場合のみ意味を持ちます。これはif
やlambda
も同じです。
- 質問者:ありがとうございます。いつも便利に使わせてもらって感謝してます。なるほど。ドキュメントで special form となってるものは引数として渡せない、ってことですね。special form は引数を先に評価しない特殊な関数、くらいにしか思ってませんでした が、構文は関数とは違う、と。こういう点でも特別なもの、と思っておきます。ただ、 special form も引数で渡せたら便利な場合もあると思うんですが、Scheme の言語仕様的に不可、ってことでしょうか。
- Shiro(2020/11/20 09:27:51 UTC): 式
(and #f (destroy-the-universe))
では、最初の引数が#f
なので(destroy-the-universe)
は決して呼ばれません。しかしたとえば(define (apply-2 fn a b) (fn a b))
とかして(apply-2 and #f (destroy-the-universe))
を許すと、and
が見る前に引数が評価されて宇宙が破壊されてしまいます。つまり意味が変わっちゃうんですね。 それを「使う方が気をつけて使えば良い」とする設計もまあ方針としてはあり得るんですが、(apply-2 fn a b) ≡ (fn a b)
と定義したということは(apply-2 fn a b)
は(fn a b)
と常に置換可能である、ということだ、 とする方が一貫性がありますね。and
が渡せるようにするとその前提を壊してしまうわけです。
- 質問者:ありがとうございます。なるほどです。ただ、脱線かもですが、見る前に引数が評価されるのが一貫性に関し問題、ならば、apply-2 が special form で定義されてれば問題はなくせる気もちょっとしました。つまり、special form なら spefial form を引数にできてもよさそうな、とか、、(special form はユーザーは定義できないんでしたっけ。御本でも必須構文は5つだけ、ってありましたね。)
- Shiro: そのとおり。そしてユーザがspecial form(のようなもの)を定義する方法がマクロです。マクロにならspecial formや他のマクロを引数のように渡すことができます。
- 質問者:おぉ、マクロ自体が渡せないのは試して予想通り、と思ってたんですが、special form を渡すマクロは作れるんですね。試してみます。ありがとうございました。
util.match モジュールは、デフォルトでロードされるモジュールですか?
(all-modules)
は、現在ロード済みのモジュールのリストを返す関数だと認識しています。
gosh を起動直後に、(all-modules)
を呼ぶと、#<module util.match>
がリストに含まれていたので
util.match モジュールはデフォルトでロードされるのかと思ったのですが、
実際に match
マクロを呼ぶと unbound variable になります。
質問ですが、(all-modules)
はロードされていないモジュールも含まれるということなのでしょうか?
(all-modules)
のドキュメントには現在存在する名前付きモジュールのリストを返すと記載されており、存在の意味を掴みかねています。
- Shiro(2020/09/27 19:01:40 UTC): まず、モジュールがロードされていても、現在のモジュールにimport (Gaucheの意味で) していなければ束縛は見えません。
use
は「require
(モジュールがロードされていなければロードする)+import
(現在のモジュールからそれを見えるようにする)」という動作です。例えばあるモジュールが他のモジュールにuse
されていたためにロードされれた、という場合なら、現在のモジュールからは見えないかもしれません。 なおR7RSのimport
はGaucheのuse
と機能的にほぼ同じです。 - ただ、今回指摘されて気づいたんですが、
util.match
についてはちょっと特殊事情があって、Gauche自身で書かれた組み込み関数はコンパイル時にmatch
マクロで展開されてるものがあり、その時にutil.match
モジュールへの参照を含む識別子が作られちゃってるんですね。(衛生的マクロのために、マクロ展開後の式は各識別子がどのモジュールのものを指しているかという情報を保持しています)。そのため、Gauche初期化時に名前空間を示すためだけのutil.match
モジュールが作られていて、「ロードされていないのにモジュールが存在する」という状態が生じています。これはバグなのでfixします。 - 質問者: なるほど、ありがとうございます。理解しました。
- Shiro(2020/09/27 21:37:46 UTC): ちなみに、普通にgosh REPL起動した場合はREPLの初期化の一環として
util.match
はロードされているので、(all-modules)
に現れるのは問題ありません。match
が見えないのは単にuserモジュールがimportしてないだけです。gosh -q
で起動すると外部Schemeファイルを初期化時に一切ロードしないので、その状態で(all-modules)
にutil.match
が現れるのがバグです。
関数定義内関数の実行
(define (aaa) (define (tasu x y) (+ x y)))
あるいは
(define (aaa) (define (tasu x y) (+ x y)) 'done)
としたとき、関数tasuを実行するにはどうしたら良いでしょうか。
(define bbb) (define (qwe) (define (tasu a b) (+ a b)) (set! bbb tasu) 'done)
とすると
gosh> (qwe) done gosh> (bbb 1 2) 3
となるのですが
gosh> (tasu 1 2) *** ERROR: unbound variable: tasu
となるのは何故でしょうか。何となく
((aaa) tasu 1 2) とか (begin (aaa) (tasu 1 2))
などで実行する方法がありそうな気がするのですが、分かりません。
- Shiro(2020/09/20 21:15:00 UTC):
(define (aaa) (define (tasu x y) (+ x y)) ...)
というのは、次のコードと同じです。(define (aaa) (define tasu (lambda (x y) (+ x y))) ...)
つまり、tasuというローカル変数に、(lambda (x y) (+ x y))
という無名関数を束縛しています。 - ローカル変数はそのスコープの中からしか見えませんよね。なので
aaa
のスコープを抜けてしまったら、tasu
という変数は見えなくなります。 - けれども、
(set! bbb tasu)
とした場合、tasu
に束縛されていた無名関数がグローバル変数bbb
にも束縛されることになるので、aaa
のスコープを抜けた後でもbbb
から呼び出すことが出来ます。 - 次のコードは、
aaa
の中で作ったローカル変数をaaa
の戻り値にしています:(define (aaa) (define (tasu x y) (+ x y)) tasu)
- 実行してみると
#<closure ...>
というオブジェクトが返ってきます。それが「関数」の本体です。(表示されている(aaa tasu)
というのは単なるデバッグ情報で、aaa
の中でtasu
という名前で定義されたことを示していますが、tasu
という名前自体は関数にとって特別に意味があるものではありません。)gosh> (aaa) #<closure ((aaa tasu) x y)>
- 戻り値になった関数は、数値やリストと同様、普通のオブジェクトですから、普通に変数に束縛して呼び出すことができます。
gosh> (define bbb (aaa)) bbb gosh> (bbb 1 2) 3
- 関数内関数のひとつの使い道は、その関数が作られた時に見えている変数を「閉じ込めておく」というものです。次の関数を考えてみます。
(define (add-n n) (define (tasu x) (+ n x)) tasu)
- 上の
aaa
とよく似ていますが、add-n
はひとつ引数n
を取ります。そして、tasu
は二つではなく一つだけ引数を取って、add-n
の引数であるn
と足した値を返します。add-n
はその関数内関数tasu
を返り値とします。 - こうするとどうなるかというと、
add-n
" を呼んで作られた関数内関数は、その時のn
を覚えておいてくれるんです。gosh> (define add-1 (add-n 1)) add-1 gosh> (add-1 2) 3 gosh> (add-1 3) 4 gosh> (define add-10 (add-n 10)) add-10 gosh> (add-10 2) 12 gosh> (add-10 3) 13
add-1
、add-10
はそれぞれ、add-n
に渡された引数1と10を覚えていて、それに渡された引数を足して返す関数になっています。
atom関数はなぜ無いのか?
Common Lisp だと (atom list) とよく書いていたのですが、Schemeにはatom相当の手続きは無いのでしょうか?
- Shiro(2020/09/11 01:05:49 UTC): ありません。
atom
はもともとごく初期のLispにおいて、データ型にコンスセルとシンボルしか無かった時代に作られたものです。二種類しかないので、「コンスセルでないもの」をatom
で調べることに意味がありました。しかしそれから数値、文字列、ベクタなどデータ型が増えてゆくにつれ、atomであることだけ分かってもあまり意味がなくなりました。「コンスセルでないこと」を調べたいなら(not (consp x))
とすれば良いし、そうでなければsymbolp
、numberp
などで細かく調べる必要があることがほとんどだからです。 Common Lispにはおそらく互換性からatom
が残りましたが、Schemeでは削られました。(not (pair? x))
を使うのが良いでしょう。 - なお、Gaucheではスレッドセーフに(atomicに)アクセスできるデータ構造があり、
atom
はそのコンストラクタです( https://practical-scheme.net/gauche/man/?l=jp&p=atom )。これはClojureから採り入れました ( https://clojure.org/reference/atoms )。 - 質問者: おお
atom
は古のLispだったのか。ありがとうございました。
gauche-gtkgl.soをロードできない
gauche-gtk2のgtkglやglgdをインストールしましたが動きません。
gauche-gtkなどはdynamic-loadできるのですがgauche-gtkglやgauche-glgdはdynamic-loadするとfailed to linkと出てしまいます。
どうやら_gdk_gl_config_get_depthなどのシンボルがないとのとのことで
どこを直せばいいでしょうか?
error:
%make check cd src; /Applications/Xcode.app/Contents/Developer/usr/bin/make all make[1]: Nothing to be done for `all'. cd lib; /Applications/Xcode.app/Contents/Developer/usr/bin/make all make[1]: Nothing to be done for `all'. if test Xgauche-gtkgl.so != X; then (cd gtkgl; /Applications/Xcode.app/Contents/Developer/usr/bin/make all); fi make[1]: Nothing to be done for `all'. if test X != X; then (cd glgd; /Applications/Xcode.app/Contents/Developer/usr/bin/make all); fi cd src; /Applications/Xcode.app/Contents/Developer/usr/bin/make check /usr/local/bin/gosh -I. -I../lib test-gdk.scm >> test.log Testing Gdk ... 2020-08-08 20:30:21.086 gosh[24509:139763] *** WARNING: Method userSpaceScaleFactor in class NSView is deprecated on 10.7 and later. It should not be used in new applications. Use convertRectToBacking: instead. failed. discrepancies found. Errors are: test gdk-cursor: expects 2 => got -1 test gdk-visual: expects (24) => got (1 24 32) test gdk-visual: expects (4) => got (0 4) /usr/local/bin/gosh -I. -I../lib test-gtk.scm >> test.log Testing Gtk ... 2020-08-08 20:30:21.302 gosh[24511:139846] *** WARNING: Method userSpaceScaleFactor in class NSView is deprecated on 10.7 and later. It should not be used in new applications. Use convertRectToBacking: instead. failed. discrepancies found. Errors are: test callback: expects (#t #f) => got (#t #t) test destroy -> GC: expects #f => got #<<gtk-button> 0x10441bdb0> test destroy -> GC (cyclic): expects (#f #f) => got (#<<gtk-button> 0x10442a9c0> #<closure ((#f cb) . _)>) test destroy widget tree: expects (#f #f) => got (#<<gtk-button> 0x1044231b0> #<<gtk-button> 0x104423180>) if test Xgauche-gtkgl.so != X; then (cd gtkgl; /Applications/Xcode.app/Contents/Developer/usr/bin/make check); fi /usr/local/bin/gosh -I. -I../src -I../lib test.scm >> test.log Testing GtkGLExt ... *** ERROR: failed to link ./gauche-gtkgl.so dynamically: dlopen(./gauche-gtkgl.so, 10): Symbol not found: _gdk_gl_config_get_depth Referenced from: ./gauche-gtkgl.so Expected in: flat namespace in ./gau ... While loading "../lib/gtk/gtkgl.scm" at line 23 While compiling "./test.scm" at line 4: (use gtk.gtkgl) While loading "./test.scm" at line 4 Stack Trace: _______________________________________ make[1]: *** [check] Error 70 make: *** [check] Error 2
- Shiro(2020/08/08 18:27:58 UTC): エラーメッセージによれば
gdk_gl_config_get_depth
が無いと言っているので、バージョンが変わって以前あったAPIがなくなってしまったように見えます。 これはgauche-gtkglをアップデートしないとならないですね。 私はもう長らく使ってないのでどこまで手を入れる必要があるか見当がつかないですが、 とりあえずこの関数についてだけなら、gtkgl/gdkgllib.stub
の次の箇所で呼んでいるので:(define-cproc gdk-gl-config-get-depth (glconfig::<gdk-gl-config>) (call <int> "gdk_gl_config_get_depth"))
- もしAPIの名前が変更されただけなら、
gdk_gl_config_get_depth
のところを新しい名前にする - API自体が大幅に変えられていて該当するものが無くなっていたなら、この2行をコメントアウトする
- もしAPIの名前が変更されただけなら、
- 同じようなエラーが出たら同様に対応してみてください。
- 質問者: とりあえずエラーの出たところは全てコメントアウトしたところ動きました。
https://developer.gnome.org/gtkglext/stable/gtkglext-gdkglconfig.html#gdk-gl-config-get-depth
にgdk-gl-config-get-depthがあるんですがこれだと使えないんでしょうか?
- Shiro(2020/08/09 04:57:53 UTC): ふーむ、Ubuntuのaptでlibgtkglext1を入れるとちゃんと動きますね。ログを見ると環境はOSXのようですが、libgtkglextはどうやって入れましたか?
- 質問者: homebrew で入れました。infoはこんなかんじです。
% brew info gtkglext gtkglext: stable 1.2.0 (bottled) OpenGL extension to GTK+ https://projects.gnome.org/gtkglext/ /usr/local/Cellar/gtkglext/1.2.0_3 (78 files, 2MB) * Poured from bottle on 2020-08-08 at 18:26:35 From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/gtkglext.rb ==> Dependencies Build: pkg-config ✔ Required: glib ✔, gtk+ ✔ ==> Analytics install: 814 (30 days), 2,401 (90 days), 9,782 (365 days) install-on-request: 96 (30 days), 193 (90 days), 786 (365 days) build-error: 0 (30 days)
- hamayama(2020/08/10 02:07:29 UTC): この辺は違うでしょうか。
https://qiita.com/kjunichi/items/359f51338a9ddd04bade
(例示の openssl.pc の部分を、gtkglext-1.0.pc にして検索してみる)
Gauche-gtk2 の gtkgl/Makefile.in をみると、
pkg-config で gtkglext-1.0.pc を探しているようです。
https://github.com/shirok/Gauche-gtk2/blob/0220722c44ef85f2e1b9b14745702c1b923258e8/gtkgl/Makefile.in#L43
(バージョン 1.2 でもファイル名は 1.0 でよさそう)
- 質問者: homebrew で入れました。infoはこんなかんじです。
- 質問者: 試しましたがうまくいきませんでした。
% mdfind -name gtkglext1.0.pc|grep /usr/local/Cellar /usr/local/Cellar/gtkglext/1.2.0_3/lib/pkgconfig/gtkglext-1.0.pc % export PKG_CONFIG_PATH=/usr/local/Cellar/gtkglext/1.2.0_3/lib/pkgconfig % echo $PKG_CONFIG_PATH /usr/local/Cellar/gtkglext/1.2.0_3/lib/pkgconfig (エラーはおなじです)
ところで初めにpkg-configで以下のようにでたんですが、これってもとからgtkglext-1.0.pcがみつかっていたということですかね?
% pkg-config --libs-only-L gtkglext-1.0 -L/usr/local/Cellar/gtkglext/1.2.0_3/lib -L/usr/local/Cellar/gtk+/2.24.32_3/lib -L/usr/local/Cellar/glib/2.64.4_2/lib -L/usr/local/Cellar/gtk+/2.24.32_3/lib -L/usr/local/Cellar/pango/1.44.7/lib -L/usr/local/Cellar/harfbuzz/2.7.0/lib -L/usr/local/Cellar/atk/2.36.0/lib -L/usr/local/Cellar/cairo/1.16.0_3/lib -L/usr/local/Cellar/gdk-pixbuf/2.40.0_1/lib -L/usr/local/Cellar/glib/2.64.4_2/lib -L/usr/local/opt/gettext/lib
- hamayama(2020/08/10 16:30:48 UTC): ああ、元から正しいものが見つかっていたようですね。
あとは pkg-config --libs-only-l gtkglext-1.0 で表示されるライブラリに、
シンボル _gdk_gl_config_get_depth が存在するのかが気になりますが。
(例えば、/usr/local/Cellar/gtkglext/1.2.0_3/lib に複数のライブラリファイルがあって、
それらを追加で指定したらいけるとか。まあ、ないか。。。)
- 質問者: うーむ。直接gauche-gtkgl.soをロードしてみたところ_Scm_GObjectClass が見つからないとのことなんですが、_Scm_GObjectClass ってなんですか?
gosh> (dynamic-load "./gauche-gtkgl.so") *** ERROR: failed to link ./gauche-gtkgl.so dynamically: dlopen(./gauche-gtkgl.so, 10): Symbol not found: _Scm_GObjectClass Referenced from: ./gauche-gtkgl.so Expected in: flat namespace in ./gauche-gtk ... Stack Trace: _______________________________________ 0 (eval expr env) at "/usr/local/Cellar/gauche/0.9.9/share/gauche-0.97/0.9.9/lib/gauche/interactive.scm":269
- Shiro(2020/08/11 05:21:25 UTC): Gtkが使っているGObjectに対応するGauche側のクラスです。
gauche-gtk.soの中で定義されています。
lib/gtk/gtkgl.scm
を見てもらうとわかるんですが、(use gtk)
してからdynamic-load
してますよね。(use gtk)
の段階でgauche-gtk.soがロードされます。 もし既に(use gtk)
してるにもかかわらずそのエラーが出たとしたら、 OSXでのビルドが何かおかしなことになっているっぽいですね。こちらでも時間が出来たら検証してみます。
- 質問者: おぉ、たしかに
(use gtk)
するとでなくなりましたね。
/usr/local/Cellar/gtkglext/1.2.0_3/include/gtkglext-1.0/gdk/gdkglconfig.hにgint gdk_gl_config_get_depth (GdkGLConfig *glconfig);
とあるんですけどね・・・
- Shiro(2020/08/11 10:51:18 UTC): 試しに、
otool -L gauche-gtkgl.so
ってやってみてください。リンクされてるlibgtkglext.dylibのパスが出ると思います。それがbrewで入れたやつと合っているか…?
- 質問者: こうなりました。一番最初の
libgtkglext-quartz-1.0.0.dylib
あたりですかね。Gauche-gtk2/gtkgl % otool -L gauche-gtkgl.so gauche-gtkgl.so: /usr/local/opt/gtkglext/lib/libgtkglext-quartz-1.0.0.dylib (compatibility version 201.0.0, current version 201.0.0) /usr/local/opt/gtkglext/lib/libgdkglext-quartz-1.0.0.dylib (compatibility version 201.0.0, current version 201.0.0) /usr/local/opt/gtk+/lib/libgdk-quartz-2.0.0.dylib (compatibility version 2401.0.0, current version 2401.32.0) /usr/local/opt/glib/lib/libgmodule-2.0.0.dylib (compatibility version 6401.0.0, current version 6401.4.0) /usr/local/opt/gtk+/lib/libgtk-quartz-2.0.0.dylib (compatibility version 2401.0.0, current version 2401.32.0) /usr/local/opt/pango/lib/libpangocairo-1.0.0.dylib (compatibility version 4401.0.0, current version 4401.7.0) /usr/local/opt/pango/lib/libpango-1.0.0.dylib (compatibility version 4401.0.0, current version 4401.7.0) /usr/local/opt/harfbuzz/lib/libharfbuzz.0.dylib (compatibility version 20700.0.0, current version 20700.0.0) /usr/local/opt/atk/lib/libatk-1.0.0.dylib (compatibility version 23610.0.0, current version 23610.1.0) /usr/local/opt/cairo/lib/libcairo.2.dylib (compatibility version 11603.0.0, current version 11603.0.0) /usr/local/opt/gdk-pixbuf/lib/libgdk_pixbuf-2.0.0.dylib (compatibility version 4001.0.0, current version 4001.0.0) /usr/local/opt/glib/lib/libgio-2.0.0.dylib (compatibility version 6401.0.0, current version 6401.4.0) /usr/local/opt/glib/lib/libgobject-2.0.0.dylib (compatibility version 6401.0.0, current version 6401.4.0) /usr/local/opt/glib/lib/libglib-2.0.0.dylib (compatibility version 6401.0.0, current version 6401.4.0) /usr/local/opt/gettext/lib/libintl.8.dylib (compatibility version 11.0.0, current version 11.0.0) /usr/local/opt/gauche/lib/gauche-0.97/0.9.9/x86_64-apple-darwin19.2.0/libgauche-0.97.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1) /opt/X11/lib/libSM.6.dylib (compatibility version 7.0.0, current version 7.1.0) /opt/X11/lib/libICE.6.dylib (compatibility version 10.0.0, current version 10.0.0) /opt/X11/lib/libXext.6.dylib (compatibility version 11.0.0, current version 11.0.0) /opt/X11/lib/libX11.6.dylib (compatibility version 10.0.0, current version 10.0.0)
- Shiro(2020/08/12 18:49:56 UTC): リンクはされてるってことですね。
nm /usr/local/opt/gtkglext/lib/libgdkglext-quartz-1.0.0.dylib
の出力にgdk_gl_config_get_depth
出てきます?
- 質問者: 出てこないですね。
- Shiro(2020/08/13 18:39:53 UTC): Brewで入れてみました。他の
gdk_gl_config_get_*
は あるのに_get_depth
が無いですね。_get_screen
も無いか。これ以上はBrewのビルドがどうなってるか調べないと何ともいえないです。
- 質問者: うーむ。これで使ってみます。Shiroさんhamayamaさんありがとうございました。あ、あとc-wrapperとかもうまくいきませんでした・・・
Linuxのほうがいいかな。
GaucheをC言語から使いたいのですが、関数やマクロ名はどこを参照するのがよいでしょうか。
C言語でGaucheのリストを作って、Gaucheで書いたプログラムで操作できないか考えています。
具体的には、C言語プログラムでGaucheのリスト構造(データ)作る → Gaucheのプログラムを別途用意して、そのプログラムを適用する → 結果をC言語プログラムで受け取る
のような流れを考えています。
SCM_MAKE_INT()をはじめ、GaucheオブジェクトをC言語から作成できるようなので、それらを使おうと思います。 ただ、以下のような点にひっかかっているのと、そもそも、どこかに基本となるオブジェクトの生成や、リストを作成する関数の一覧があれば、助かります。
ひっかかっている点
- 整数は、SCM_MAKE_INT() を使うのか、Scm_MakeInteger() を使うのがよいのか?
- 浮動小数点は、 Scm_MakeFlonum() でよい?
- リスト作成や操作は、Scm_List() Scm_Cons() Scm_Car() Scm_Cdr() あたりでしょうか?
- どこかに、Cから基本となるGaucheオブジェクトの生成や、リストを作成・操作する関数の一覧ありますでしょうか。
よろしくお願いします。 (umehara)
- Shiro(2020/07/18 10:51:27 UTC): C APIは doc/gauche-dev.texi に書きかけです。docの下で
make gauche-deve.info
とするとinfoファイルができます。まだ書いてないところが多いですが、数値については書いてあります。 - 整数はScm_MakeInteger()が推奨です。SCM_MAKE_INT()はfixnumであることがわかっている場合に使えますが、速度が重要でなければScm_MakeInteger()の方がチェックもされるので気楽でしょう。
- リスト作成についてはまだ書いてないですね… Scm_List, Scm_Cons, Scm_Car, Scm_Cdr等を使ってください。これらもマクロの方が速いですが事前条件などがあるので、関数版の方が気楽です。
- CからScheme関数の呼び出し方は、doc/HOWTO-callback-scheme.adocも参照してください。
- 他にわからないことがあったら随時質問してもらえると、それに合わせてgauche-dev.texiを充実させてゆきます。
umehara (レベル3): ご丁寧に御回答ありがとうございます。返事遅くなってすみませんでした。
Web上でC言語からGuacheを使うための情報をさがすと、とあるページで、SCM_MAKE_INTを使っていたので、それに対応する浮動小数点をつくる関数・マクロをGaucheのヘッダからさがしました。SCM_MAKE_FLONUMのようなものが見つからず、一方で、Scm_MakeFlonumがみつかって、なぜ名前に一貫がないか(マクロと関数混在しているのか)と思い始め、わからなくなり質問させていただいた次第です。doc参照できました。C APIに関して探していた情報がありました。ありがとうございます!
※ 後から参照する方のために
以下、自分自身の環境でドキュメン出力で少し修正した点です。 doc以下で make gauche-deve.info する時に、うまく作れなかったので、以下のようにしました。まず、makeinfoコマンドがなかったので sudo apt install texinfo でいれました。makeinfoコマンドはtexinfoパッケージの中にあるようでした。また、doc 以下のMakefile内の、MAKEINFO に対する値が空欄になっていたので、 'MAKEINFO = makeinfo' とエディタで修正しました。 なお、生成された .infoファイルの読み方は、Emacsを開いて、'C-u C-h i' で読めました。
※ (追記:umehara) 以下のコードで数値のリストをGaucheに渡して、その計算結果を、Cで表示できました
同じディレクトリに、c_with_gauche.c と my-calc.scm を作って、後述のようにコンパイル・実行しました。Gauche自体はユーザ領域にインストールしており、共有ライブラリがダイナミックライブラリのサーチパスに入っていなかったので、実行前に追加などしています。
- c_with_gauche.c
#include <stdio.h> #include <gauche.h> int main() { GC_INIT(); Scm_Init(GAUCHE_SIGNATURE); ScmObj elem1 = Scm_MakeInteger(1); ScmObj elem2 = Scm_MakeInteger(2); ScmObj elem3 = Scm_MakeInteger(3); ScmObj elem4 = Scm_MakeInteger(4); ScmObj scm_lis_obj = SCM_LIST1( Scm_Cons(elem1, Scm_Cons( elem2, Scm_Cons( elem3, Scm_Cons(elem4,SCM_NIL))))); Scm_AddLoadPath(".", 0); ScmLoadPacket lpak; Scm_Load("my-calc.scm", 0, &lpak); static ScmObj my_proc = SCM_UNDEFINED; // static is specified following Shiro-san's reply. SCM_BIND_PROC( my_proc, "my-sum", Scm_CurrentModule()); ScmObj result = Scm_ApplyRec( my_proc, scm_lis_obj); if(SCM_INTEGERP(result)){ printf("The result is %ld.\n", Scm_GetInteger(result)); } printf("Finished\n"); return 0; }
- my-calc.scm
(define (my-sum lis) (print lis) (if (null? lis) 0 (+ (car lis) (my-sum (cdr lis))) ) )
- 上記ファイルを同じディレクトリに用意した場合、以下のようにコンパイルしました。 (OS:Linux Mint シェル:bash)
gcc c_with_gauche.c $(gauche-config -I) $(gauche-config -L) $(gauche-config -l)
- 最後に以下のように実行 (はじめの2行は、ダイナミックライブラリのあるパスを取得。'gauche-config -L'から、1行目で空白をトリム(xargsがトリムに使える)して、2行目で(0はじまりの)2文字目以降をLD_LIBRARY_PATHに割当。環境変数としてつかう。
libpath=$(gauche-config -L | xargs) export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${libpath:2} ./a.out
- 結果
(1 2 3 4) (2 3 4) (3 4) (4) () The result is 10. Finished
- Shiro(2020/07/20 09:56:46 UTC): makeinfoについては、configure時に探してあれば使うようになっています。最新のHEADのソースなら、makeinfo無ければconfigureの結果に
Documentation: No (requires makeinfo and gzip to build documents)
と表示されるようになっています。後からmakeinfoを入れたなら、もういちどconfigureを走らせればmakefileを手編集する必要はありません。 - 上のソースで、
my_proc
はstatic宣言してください。SCM_BIND_PROCはmy_proc
がSCM_UNDEFINED
だった時にScheme手続きを探しにいって、 結果をmy_proc
にストアします。2回目以降は探しにゆく必要がないわけです。 - CからSchemeオブジェクトを表示するには、
fprintf
と同じように使えるScm_Printf
が便利です。最初の引数にはFILE*
ではなくScmPort*
を 取りますが、SCM_CUROUT
(current-output-port) やSCM_CURERR
(current-error-port) が使えます。第二引数はprintf
と同じフォーマット文字列ですが拡張されてて、%S
や%A
でSchemeオブジェクトを表示できます。%S
はwrite
表示、%A
はdisplay
表示です。Scm_Printf(SCM_CUROUT, "The result is %S.\n", result);
- (umehara) コードレビューありがとうございます。出力やIOあたりも、いろいろ試してみたいと思います!
再帰(線形再帰)と末尾再帰(反復的プロセス)は常に互換できますか
普通の再帰を末尾再帰に書き換えること、そして末尾再帰(定義中で iter などで変数を加え、さらにその変数に初期値を与えるもの)を普通の再帰に書き換えることは、常に可能なのでしょうか?
- hamayama(2020/06/28 01:45:34 UTC): 質問の回答になっているのかはよく分からないのですが、
再帰を再帰でないプログラムに変換することは 常に可能なようです。
(参考: http://www.edu.cc.uec.ac.jp/~ka002689/prog18/siryou/r06.pdf )
だとすると、再帰関数 f1 を、非再帰関数 f2 に変換してから、
(define (f3 n) (if (= n 0) (f2 n) (f3 (- n 1))))
のようにすれば、再帰関数 f1 を末尾再帰関数 f3 にすることは、
常に可能ということになるのかもしれない。
- ただ、再帰を、単純に引数や変数をスタックに保存することでシミュレートしただけでは、
結局 f2 の実行で同じだけメモリを消費するので、
(やっていることは同じなので) 意味がないのかもしれない。。。
(CPS 変換について、似たようなことを https://sumii.hatenablog.com/entry/20070815/p1
で言われていました)
- 逆については、末尾再帰も一応は再帰なので、常に成立するのではないだろうか。。。
ただ、(すべての呼び出しを末尾呼び出しにする) CPS 変換の逆変換として、
DS 変換という考え方があるようです。(このようなことを意図されている?)
(参考: https://qiita.com/fgborges/items/ffe0e50ad630b434c625 )
- (クエス)ショナー?
ご回答と参考リンクをお教え頂きありがとうございます。
考えていたのは一つには末尾再帰より普通の再帰の方が直感的に分かるので、末尾再帰のコードを再帰にして読みやすくして理解しようとしたからです。
もう一つは reverse と append を(reverse と append無しに)作るときに append は末尾再帰で、reverse は末尾再帰でなしで作られるものか疑問に思ったからです。
これはZコンビネータですか
Zコンビネータは
Z = λf.(λx.f(λy.(xxy))) (λx.f(λy.(xxy)))
なわけで、(Z g) は
(Z g) = λx.g(λy.xxy)(λy.g(xxy)) = g(λy.((λx.g(λy.xxy))(λx.g(λy.xxy))y) = g(λy.(Z g) y)
となるのでSchemeで書くと
(define (Z g) (g (lambda (y) ((Z g) y))))
引数の g をラムダにして
(define Z (lambda (g) (g (lambda (y) ((Z g) y)))))
としたものはZコンビネータと呼べるのでしょうか。Zの定義の中にZが現れているのでそもそもの「匿名関数で再帰を実現」ではないような気がします。
しかしたとえば階乗計算用に fac
(define fac (lambda (f) (lambda (n) (if (= n 1) 1 (* n (f (- n 1)))))))
を用意してZに食わせると
gosh> ((Z fac) 3) 6
とfac の定義自体には fac を使わずに再帰ができているので、Zコンビネータ自体にZが現れていても「再帰させたい関数の名前を使わずに再帰させる」は実現できているので、Zコンビネータでいい気もします。
定義に自由変数(この場合Z)が含まれているのでコンビネータの定義上コンビネータではないのかもしれませんが……
- Shiro(2020/06/11 19:12:35 UTC):論理学の立場からはコンビネータは自由変数を含まない式ですから、(lambda (g) ...) の中にZが出てきちゃまずいです。
ただ、プログラミングにおけるカジュアルな用法として「1つ以上の関数を受け取って何らかの形でそれらを組み合わせた動作をする関数を返す」ものをコンビネータと呼ぶことがあり、そういうふうに使う側から見た場合はZがどう内部で実現されてるかは気にしなくて良いので、内部でこっそり「これから定義されるはずの名前」を使っちゃっても動作が変わらなければよしとする立場もあります。
ただし、副作用を許す系で(set! Z なんとか)
とした場合に動作が変わっちゃったら誤魔化してることがバレちゃいますから、どのへんまでの操作を前提とするかによってコンビネータと呼ぶべきかどうかは変わって来ると思います。
Gauche-alをMacで使いたい
Gauche-al http://www.koguro.net/prog/Gauche-al/Gauche-al-20051009.tar.gz からダウンロードしてきて
./configure make
するとエラーで
clang: error: no such file or directory: 'al_head.c'
src/ に空のac_head.cを作成してみたところ
./gauche-al.h:40:10: fatal error: 'AL/al.h' file not found
とまたエラーが出てしまいます。
https://github.com/Hamayama/Gauche-al-mg では
chmod +x DIST ./DIST ./configure make
すると
./gauche-al.h:40:10: fatal error: 'AL/al.h' file not found
だけで済みます。 おそらく、configure の
3685 for ac_header in OpenAL/al.h 3686 do : 3687 ac_fn_c_check_header_mongrel "$LINENO" "OpenAL/al.h" "ac_cv_header_OpenAL_al_h" "$ac_includes_default" 3688 if test "x$ac_cv_header_OpenAL_al_h" = xyes; then : 3689 cat >>confdefs.h <<_ACEOF 3690 #define HAVE_OPENAL_AL_H 1 3691 _ACEOF 3692 3693 case $target in 3694 powerpc-apple-darwin*) 3695 $as_echo "#define MacOSX 1" >>confdefs.h 3696 3697 AL_LIBS='-framework OpenAL' 3698 ;; 3699 *) 3700 ;; 3701 esac 3702 3703 fi 3704 3705 done
あたりがうまく行っていないようで gauche-al.h の
35 #if MacOSX 36 #include <OpenAL/al.h> 37 #include <OpenAL/alc.h> 38 #include <OpenAL/alut.h> 39 #else 40 #include <AL/al.h> 41 #include <AL/alc.h> 42 #include <AL/alut.h> 43 #endif
でelse以降を展開してしまっているようです
./configure の結果では、OSXと認識されてるっぽいのですが・・・
./configure ... checking build system type... x86_64-apple-darwin19.4.0 checking host system type... x86_64-apple-darwin19.4.0 checking target system type... x86_64-apple-darwin19.4.0 ....
どのようにすれば良いですか?
- Shiro(2020/05/19 21:41:02 UTC):あ、configureの検査が
powerpc-apple-darwin*
になってるので、x86_64-...
だと飛ばされちゃってるんだと思います。 本来はconfigure.acの方を直さないとなりませんが、とりあえずインストールするだけなら、 configureの該当個所を*-apple-darwin*
に変えてやってみてください。
- 質問者: configure を書き換えただけでは出来なかったので、configure.acを書き変えたら上のエラーは無くなったのですが、今度は
al-lib.c:817:8: error: use of undeclared identifier 'AL_DATA' case AL_DATA: al-lib.c:1569:82: error: use of undeclared identifier 'AL_SOURCE_ABSOLUTE' Scm_MakeBinding(SCM_MODULE(mod), SCM_SYMBOL(scm__rc.d1310[5]), Scm_MakeInteger(AL_SOURCE_A... ^ al-lib.c:1668:83: error: use of undeclared identifier 'AL_DATA' Scm_MakeBinding(SCM_MODULE(mod), SCM_SYMBOL(scm__rc.d1310[38]), Scm_MakeInteger(AL_DATA), ...
al-lib.stub には (define-enum AL_DATA) のように定義されてるんですが、うーん、わかりません。アドバイスお願いします。
- Shiro(2020/05/20 11:55:59 UTC): それはOpenALの方にAL_DATAやAL_SOURCE_ABSOLUTEの定義が無くなってるっぽいですね (
define-enum
は既にあるCの定数をScheme側に見せるものです)。 Scheme側でそれらの定数を使っていないなら、define-enumをコメントアウトすればコンパイルは通ると思います。 - いや、
case AL_DATA
のところは別途対応しないとだめか。OpenAL側の変更を追わないとわからないですね。名前が変わっただけかもしれない。
- 質問者:定義を消したら行けました!
一応最初から手順残しておきます
MacでGauche-alインストール方法
まず http://www.koguro.net/prog/Gauche-al/Gauche-al-20051009.tar.gz からダウンロード
ダウンロードしたディレクトリに移ってtar -zxvf Gauche-al-20051009.tar.gz cd Gauche-al-20051009
エディタで configure.ac を開いて powerpc-apple-darwin* を apple-darwin* に変更cd src touch al_head.c al_tail.c cd .. autoconf ./configure
エディタで al-lib.stubを開いて45 (define-enum AL_SOURCE_ABSOLUTE) ... 78 (define-enum AL_DATA) ... 140 case AL_DATA:
を↓に変更45 ;;(define-enum AL_SOURCE_ABSOLUTE) ... 78 ;;(define-enum AL_DATA) ... 140 /* case AL_DATA: */
そしたらmake make install
最後に確認cd example gosh play.scm
すると「ジャラーン」と鳴る
ちなみにですが、Gauche-gl ( http://practical-scheme.net/gauche/packages.html )
はバッチリでした!
環境 OS:Mac OSX Catalina 10.15.4
よろしくお願いします。
追記 Gauche-glについてなんですが、doc/ の Makefile で texi2html -number の -number がエラー
Option number is ambiguous (number-footnotes, number-sections) 詳しくは `texi2html --help' を実行してください。 make: *** [Makefile:52: gauche-gl-refe.html] Error 2
だったので
texi2html gauche-gl-refe.texi
としたら行けましたがどうでしょうか?
- Shiro(2020/05/19 21:41:02 UTC): ああ、texi2htmlが新しくなってオプションが変わってるんですね。Gauche本体はmakeinfo使うようになってるから合わせるか
関数に名前を付けるということ
(改行がうまくできずすみません)
「Yコンビネータの斜め下」にてSiro氏に説明していただいた「正格評価」「非正格評価」を知ろうとしWikipediaの「評価戦略」のページを読んでみましたが何が何やらでしたので、「Scheme:lambdaだけで再帰」のコードを自分なりに分かる範囲で変えてみて理解してみようとしました。
まず
上記waの部分を(X X)に直接「書き換える」かわりに、 引数waに (lambda (x) ((X X) x)) という関数を渡してやれば、 (wa (- n 1)) の部分が ((lambda (x) ((X X) x)) (- n 1))、すなわち ((X X) (- n 1))になります。
の部分がおそらくη変換なのだろうと推測し、(wa)に(X X)を渡すようにしてみました。名前を make-wa3Y に変えています。
(define make-wa3Y (lambda (X) [(lambda (wa) (lambda (n) (if (<= n 0) 0 (+ n (wa (- n 1)))))) (X X)]))
で、これは結局 make-wa2
(define make-wa2 (lambda (X) (lambda (n) (if (<= n 0) 0 (+ n ((X X) (- n 1)))))))
と同じことになるので
(make-wa3Y make-wa3Y) ≡ (make-wa2 make-wa2)
なので行けると思いましたが、
(make-wa3Y make-wa3Y)
は止まりませんでした。 何故止まらないのか? 言い換えると (make-wa2 make-wa2) は何故止まるのかを考えてみました。
まずmake-wa3Yをラムダ式にしてみますと λX.(λwa.λn.F (X X)) (ただしF = [n + wa(n-1) ※ただしn<=0ならば0]) なので(make-wa3Y make-wa3Y)は
λX.(λwa.λn.F (X X)) λX.(λwa.λn.F (X X)) =λX.(λn.G)λX.(λn.G) (ただしG = [n + (X X)(n-1) ※ただしn<=0ならば0] =λn.[n + {(λX.(λn.F) λX.(λn.F)}(n-1) ※〜] =λn.[n + (make-wa2 make-wa2)(n-1) ※〜]
で (make-wa2 make-wa2) と同じになって止まるはずではと思えました。 では何が違うのか考えてみますと make-wa2 = λX.λn.G が (make-wa3Y make-wa3Y) には出てこない、すなわち λX.(λn.F) = make-wa2 が式 (make-wa3Y make-wa3Y) の中で定義されていないから、ということでした。なので
λn.[n + {(λX.(λn.G) λX.(λn.G)}(n-1) ※〜] =λn.[n + (make-wa2 make-wa2)(n-1) ※〜]
が成り立たない、すなわち
λn.[n + {(λX.(λn.G) λX.(λn.G)}(n-1) ※〜] ≠λn.[n + (make-wa2 make-wa2)(n-1) ※〜]
なので (λX.(λn.G) λX.(λn.G)) がずっと計算され続けるからなのではないか、と推測しました。逆に make-wa2 を定義し名前を付け
(make-wa2 make-wa2)
とするとおそらく引数を与えられるまで展開(β簡約)されず一旦ここで止まり、例えば引数 a を与えられると
= ((make-wa2 make-wa2) a) =λn.[n + (make-wa2 make-wa2)(n-1) ※〜]a = [a + (make-wa2 make-wa2)(a-1) ※〜] = [a + λn.[n + (make-wa2 make-wa2)(n-1) ※〜](a-1) = [a + (a-1)λn.[n + (make-wa2 make-wa2)(n-1) ※〜]((a-1)-1) = ... = [a + (a-1) + (a-2) +...(make-wa2 make-wa2)0] = [a + (a-1) + (a-2) + ...0]
となって総和が求まるのではないかと考えました。つまり make-wa2 は名前を与えられている(定義されている)ので (make-wa2 make-wa2) は引数を与えられるまでは、その場で
(make-wa2 (λX.(λn.G)) = (λn.[n + {(λX.(λn.G) λX.(λn.G)}(n-1) ※〜] = (λn.[n + (λn.[n + {(λX.(λn.G) λX.(λn.G)}(n-1) ※〜](n-1) ※〜] = ...
と展開されないと考えたのです。
これを確かめるために
(make-wa3Y make-wa2) を評価してみると止まり ((make-wa3Y make-wa2) 10) = 55 に無事なりました。 これはおそらく
(make-wa3Y make-wa2) = λX.(λwa.λn.F (X X)) make-wa2 = λwa.λn.F (make-wa2 make-wa2) = λwa.λn.[n + wa(n-1) ※ただしn<=0ならば0] (make-wa2 make-wa2) = λn.[n + (make-wa2 make-wa2)(n-1) ※ただしn<=0ならば0]
となり引数が与えられるまで (make-wa2 make-wa2) が展開されないからだろうと考えました。 逆に (make-wa2 make-wa3Y) としますとこれは暴走しませんが、0 以外の正の数を引数を与えると暴走します。
((make-wa2 make-wa3Y) a) (ただし a>0) = ((λX.λn.G) make-wa3Y) a) = ((λX.λn.[n + (x x)(n-1) ※〜]) make-wa3Y) a) = ((λX.λn.[n + (make-wa3Y make-wa3Y)(n-1) ※〜]) a) = ((λX.λn.[n + {(λX.λn.G) (λX.λn.G)}(n-1) ※〜]) a) = ((λX.λn.[n + (make-wa2 make-wa2)(n-1) ※〜]) a)
となりそうですがSchemeは make-wa2 -> λX.λn.G は知っているけれど λX.λn.G -> make-wa2 は知らないから
= ((λX.λn.[n + {(λX.λn.G) (λX.λn.G)}(n-1) ※〜]) a) ≠ ((λX.λn.[n + (make-wa2 make-wa2)(n-1) ※〜]) a)
なのではないか、と推測しました。
くだくだと書いてきましたことを自分なりに要約しますと
・(make-wa2 make-wa2) が暴走しないのは引数が適用されるまで、名前を付けられた(定義された)関数は展開されないのではないか? それならば (make-wa3Y make-wa2) が (make-wa2 make-wa2) と同じく計算されることが説明できる。 ・名前を付けられていない式(例えば make-wa3Y = λX.{(λwa.λn.G)(X X)} であって、≠ λX.λn.G なので make-wa3Y ≠ λX.λn.G)は、それが引数になると裸のλ式として計算されてしまう。 ・名前を付けられている関数も、展開されるとその式が自分のことだと分からない。
だと推測しているということです。 これを考慮してWikipedia「評価戦略」を読み返してみると、「正格評価」の「値呼び」というのは引数が与えられるまで関数を計算しないことなのでは? また「作用的順序」では関数を作用させる以前に可能な限り関数を簡約することらしいが、関数に名前を付けていると(例えば (make-wa2 make-wa2) の場合)、そこで簡約が一旦止まるということではないか? と愚考しております。これらは妥当でしょうか?
……と考えましたが
make-wa3Y は純Yコンビネータの種で make-wa2 はZコンビネータの種で
(define YZ (lambda (f) ((lambda (X) (f (X X))) (lambda (X) (f (lambda (x) ((X X) x)))))))
とすると
(define wa (YZ (lambda (seed) (lambda (n) (if (<= n 0) 0 (+ n (seed (- n 1))))))))
では普通に (wa 10) = 55 になるので単にそういうことなのかとも思っています。 YZ = λf.(λx.f(xx))(λx.f(λy.xxy)) = \f.f((\x.f(\y.xxy))(\x.f(\y.xxy))) = Z だもんなあ……
- hamayama(2020/02/09 10:43:05 UTC): うむ。筆算に使っているラムダ計算の評価戦略 (最外最左簡約?) と、
Scheme の評価戦略 (正格評価) が異なるために結果が合わない、ということかと。。。
- Scheme の評価の方法については、
R7RS ( http://milkpot.sakura.ne.jp/scheme/r7rs.pdf ) の
「4.1.3. 手続き呼び出し」と「4.1.4. 手続き」が参考になるかもしれない。
- Scheme では、(lambda (arg) body) と書いただけでは body は評価されません。
それを括弧で囲って ((lambda (arg) body) para) として呼び出す(実行する)と、
まず引数 para が評価され、その結果を仮引数 arg にコピーしてから、body が評価されます。
- このため、(make-wa3Y make-wa3Y) の場合は、(X X) が 上記の引数 para の部分に存在するため、
すぐに評価されます。
- 一方、(make-wa2 make-wa2) の場合は、(X X) が、上記の body の部分に存在するため、
まだ評価されません。
- この (X X) の評価のタイミングの違いが、
無限ループする/しない の違いにつながっていると考えられます。
- ラムダ計算の評価戦略については、以下の資料を見つけました。
http://www.kb.ecei.tohoku.ac.jp/~sumii/class/keisanki-software-kougaku-2005/lambda.pdf
「1.6 簡約結果の一意性、合流性、簡約戦略」(P9-11) のあたりに、
簡約の仕方によって停止したりしなかったりする例が載っているようです。
ラムダ計算のSchemeでの実行について
1.
ある数に1を足す関数をy(y(a)=a+1)、ある数を2乗する関数をx(x(a)=x*x)とします。 これを組み合わせ、ある数に1を足して2乗する関数+1^2を作るとするとラムダでは
+1^2=(λx.x*x)(λy.y+1)
になると思います。
最初にyへ数を渡すので
+1^2(y)=λy.(λx.x*x)(λy.y+1)
で良いような気がしますがSchemeで
(define +1^2 (lambda (y) ((lambda (x) (* x x)) (lambda (y) (+ y 1)))))
と書いて引数を与えると
gosh> (+1^2 2) *** ERROR: operation * is not defined between #<closure ((|+1^2| |+1^2|) y)> and #<closure ((|+1^2| |+1^2|) y)>
と返ってきます。
「何故にまず +1^2 に +1^2 が適用されてるんだ?」とよく分かりませんでしたが
(define +1^2 (lambda (y) ((lambda (x) (* x x)) (+ y 1))))
では
gosh> (|+1^| 2) 9
と (2+1)*(2+1)=9 が返ってきました。これは最初のラムダ計算式が間違っていて
+1^2(y)=λy.(λx.x*x)(y+1)
こうなのでしょうか?
しかしこれならば
(define +1^2 (lambda (x) ((lambda (x) (* x x)) (+ x 1))))
でもよいわけで、こうだとラムダ計算式では
+1^2=λ.x(λ.x x*x)(x+1)
となりλ.xの連続を一体どう考えるといいのか分かりません。
- Shiro(2020/01/24 03:58:36 UTC): まず、最初の仮定に混乱があります。y(a) = a+1 は y = λa.a+1 です。変数の名前はなんでも良いんですが、λy.y+1 と書いた時のyは関数yではなく変数です。この関数を適用する、つまり y(1) を求める式は、(λa.a+1)1 です。同じく、x(a) = a*a は x = λa.a*a です。
- 次に、1を足して二乗する関数zは、1を足す方が先ですからz(a) = x(y(a)) です。これをλ式で書くと、 z = λa.(λa.a*a)((λa.a+1)a) となります。
2.
Zコンビネータ=λf.(λx.f(λy.(xx)y)) (λx.f(λy.(xx)y))
にfを適用して変換していくと(fにfを代入するので最初のλf.が省略されるだけですが)
(Z f)
=(λf.(λx.f(λy.(xx)y)) (λx.f(λy.(xx)y))f)
=((λx.f(λy.(xx)y)) (λx.f(λy.(xx)y)))
=f(λy.(λx.f(λy.(xx)y)) (λx.f(λy.(xx)y))y)
=f(λy.(λf.(λx.f(λy.(xx)y)) (λx.f(λy.(xx)y)))f)y)
=...
となるわけですが
(λx.f(λy.(xx)y))
が繰り返し出現するのでこの部分を定義してZを作られるのではと考え
(define Z- (lambda (x) (f (lambda (y) ((x x) y))))) (define Z (lambda (f) (Z- Z-)))
と試してみると
gosh> (Z 'g) *** ERROR: unbound variable: f
と返ってきました。
Z-で使っているfとZで使っているfは別の範囲で定義されているのでこうなるのは分かりますが
(define Z (lambda (f) (define Z- (lambda (x) (f (lambda (y) ((x x) y))))) (Z- Z-)))
と内部で定義する以外に同じ名前の変数を渡す(?)方法はありませんでしょうか?
Yコンビネータの斜め下
Scheme:lambdaだけで再帰 を拝読し
gosh> ((make-wa2 make-wa2) 10) 55
と返ってきた瞬間ポカンして以来、折に触れてYコンビネータのことを考えている者です。
あまりに訳が分からなくて、ラムダ計算の方からも分からないなりに探っております。
Siro氏にご教授頂いたコードを自分にも分かるよう分解し、一旦f(x)式に書き直してハードルを下げ理解を進めようとする一方、Yコンビネータの理論からも何とか理解を進め、山の両側からトンネルを掘って繋がることを期待するという方針です。(繋がるといいのですが)
WikipediaのYコンビネータの項
も読んでみましたが
Yg = (λf . (λx . f (x x)) (λx . f (x x))) g = (λx . g (x x)) (λx . g (x x)) = (λy . g (y y)) (λx . g (x x)) = g ((λx . g (x x)) (λx . g (x x))) = g (Y g)
をf(x)で書いてみると
Y(f) = {f(x(x))}(f(x(x))) (左の式のxに(f(x(x)))を代入すると) = f{f(x(x))}(f(x(x))) ({f(x(x))}(f(x(x)))はY(f)なので) = f(Y(f)) ∴ Y(f) = f(Y(f))
という理解で合っていますでしょうか。
ただしこれだと
Y(f) = f(Y(f)) = f(f(Y(f))) = f(f(f(Y(f))))...
となって止まらないのではないかと思いました。実際
(define Y (lambda (f) ((lambda (x) (f (x x))) (lambda (x) (f (x x)))))) (Y 'g)
で C+x C+e しますと何も返ってこず、やがてPCのファンが唸り始め操作を受け付けなくなりました。そうです、暴走です。
上述のWikipediaの続きを読むと、どうやら
Zコンビネータ
というものにしなくては止まらないらしいとのこと。
そこでSiro氏にお教え頂いたコードY
(define Y (lambda (f) ((lambda (X) (f (lambda (x) ((X X) x)))) (lambda (X) (f (lambda (x) ((X X) x)))))))
をラムダ計算式にしてみると
Y = λf.(λX.(f(λx.XXx))(f(λx.XXx))
となり、Xをx、xをyに置き換えるとZコンビネータの式と同じになります。(多分)
ということはこれは正確にはYコンビネータなのではなく、Zコンビネータなのでしょうか。
「Yコンビネータを知らん奴にη変換の(η変換が何なのかいまだ知りませんが)Zコンビネータを教えたって分かるわけない」というのは重々承知しておりますが、トンネル掘りの方向の微修正が必要になると思いますので、Zコンビネータという理解で合っているか否か、ご存知の方がいらっしゃいましたらお教えください。
- Shiro(2020/01/16 19:43:41 UTC): はい、正確にはZコンビネータです。定義どおりのYコンビネータは
Haskellのような非正格評価(引数の評価が必ずしも関数適用の前に起きない)言語でない場合、
(x x)
の評価が先に行われてそこで無限ループしてしまいます。 ただ、Scheme含む多くの正格評価言語において、評価を遅らせるために式をlambdaで包むことは よく行われるので、ラムダ算法の話をしてるのでなければZコンビネータの形式をYコンビネータ (あるいは正格評価版Yコンビネータ)と言っちゃったりします。 - η変換というのは
(lambda (x) (f x))
をf
に置き換える変換です。 (ここでf
はx
を含まない、関数に評価される式)。
JavaScriptでコールバックを指定する時に、callback = function (x) { myfunc(x) }
と書いてもcallback = myfunc
と書いても同じ、ということです。
ラムダ算法ならこれは等価な書き換えなんですが、SchemeやJavaScriptのような正格評価言語では、f
がいつ評価されるかに差が出ます。
Yコンビネータの二歩手前
以前、hamayama氏に https://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E5%88%9D%E5%BF%83%E8%80%85%E3%81%AE%E8%B3%AA%E5%95%8F%E7%AE%B1#H-aissbn に対して
(define (make-wa3 X) (define (F wa) (define (G n) (if (>= 0 n) 0 (+ n (wa (- n 1))))) G) (define (H a) ((X X) a)) (F H))
と書き直していただきました。 自分にはまだちょっと分からなかったので
(define (G n) (if (>= 0 n) 0 (+ n (wa (- n 1))))) (define (F wa) G) (define (H a) ((X X) a)) (define (make-wa3 X) (F H)) (define wa (make-wa3 make-wa3))
と各関数に分解してみました。順に評価したところ
gosh> (wa 10) 55
と、無事に出来ました。 これらをf(x)方式で書き直して
G(n)=(略) #1
F(wa)=G #2
H(a)={X(X)}(a) #3
make-wa3(X)=F(H) #4
wa=make-wa3(make-wa3) #5
と書き直すと
#3より
H=X(X) #3'
#2と#3'と#4より
make-wa3(X)=F(H)=F{X(X)}
よって#5と#2より
wa=make-wa3(make-wa3)=F{make-wa(make-wa3)}=F(wa)=G
で理解は合ってますでしょうか。 確かに5つの式を評価したあと
(G 10)
で
gosh> (G 10) 55
と返ってきましたが、正しい理解なのか分かりません。どこか誤解がありませんでしょうか。
- hamayama(2020/01/14 06:34:27 UTC): これは、まだ間違っています。
チェック方法としては、例えば (define wa (make-wa3 0)) のように、
make-wa3 の引数に make-wa3 以外を指定してみて、
それでも wa ができてしまうようなら NG だと思います。
- 問題点としては、未定義の変数を参照していることがあります。
例えば、(define (H a) ((X X) a)) の X が未定義です。
- 書き直す前の make-wa3 では、define が入れ子になっていて、
内側の X が、外側で定義されている X を参照するようになっていました。
(このように外側の変数を参照する手続きを、クロージャといいます)
↑↑↑正しくは、手続き (関数) と 外側の変数 (自由変数) の組のことを、クロージャというもよう
参考: https://esumii.github.io/min-caml/tutorial-mincaml-16.htm
(2020/01/16 12:38:14 UTC)
- これを、入れ子をやめて、さらに、未定義の変数をなくすように書き直すと、
以下のようになると思います。(未定義だった変数を、引数で渡すようにしました)
(追記: あらためて見ると、cut が内部 define 相当なので、入れ子をなくせていないです。
完全にフラットにするのは、自分には無理っぽい (2020/01/14 08:00:47 UTC))(define (G n wa) (if (>= 0 n) 0 (+ n (wa (- n 1))))) (define (F wa) (cut G <> wa)) (define (H a X) ((X X) a)) (define (make-wa3 X) (F (cut H <> X))) (define wa (make-wa3 make-wa3)) (wa 10) ;; ==> 55 (define wa (make-wa3 0)) (wa 10) ;; ==> ERROR
- ただ、元の質問が、lambda だけで再帰を記述可能か というものだったので、
名前を付けた時点で、目的が分かりにくくなったような気もします。
名前を付けてよければ、以下で済む話なので。
(理解するための過程ということで、役に立てばよいですが。。。)(define (G n) (if (>= 0 n) 0 (+ n (G (- n 1))))) (define wa G) (wa 10) ;; ==> 55
--
- hamayama(2020/01/15 17:42:47 UTC)(2020/01/16 12:38:14 UTC): リベンジということで、
グローバル変数を介して、フラットにしてみました。(define wa0 #f) (define X0 #f) (define (G n) (if (>= 0 n) 0 (+ n (wa0 (- n 1))))) (define (F wa1) (set! wa0 wa1) G) (define (H a) ((X0 X0) a)) (define (make-wa3 X1) (set! X0 X1) (F H)) (define wa (make-wa3 make-wa3)) (wa 10) ;; ==> 55
- この状態で、(disasm wa) と (disasm G) を実行してみると、
両者は同じものになっていました。
なので、質問者のいう wa=G というのは、正しいのかもしれない。
(コンパイラの最適化等で、変換されている可能性もありますが)
- そして、(disasm wa0) を実行してみると、wa や G とは違って、
((X0 X0) a) のような合わせ鏡の手続きを実行していました。
- これは、最初の質問の「wa0 の部分を lambda で書けないか」という問いに対する
答えを表しているように思えます。
(コンパイラの最適化等で、変換されている可能性もありますが)
- このことをふまえて、Shiro さんの Scheme:lambdaだけで再帰 を見直してみると、
もう少し理解が進むかもしれません。
(正直、私も完全には理解できていませんが。。。)
この場合の括弧とは2
しつこく申し訳ありません。前の質問へのご回答(有難うございました)を自分なりに解釈してみたところ、自分が理解していないのは、詰めてみますと、以下のようです。
(lambda () 1) ;gosh> #<closure #f> ((lambda () 1)) ;gosh> 1
1)前者が#<closure #f>(クロージャの偽)になるのはなぜか。
2)括弧がもう一重深い後者が数値になるのはなぜか。
愚問ですが、ここを理解したく存じております。
- hamayama(2019/12/25 07:46:20 UTC): 多分クロージャの知識等も必要なのでしょうが、
その前の段階として、以下のことを知っている必要があります。
他の言語では、式を括弧で余分に囲っても同じ意味になることが多いですが (例えば、((1+2)) == (1+2) 等)、
Lisp や Scheme では、同じ意味にはならず、違う意味になったり エラーになったりします。
そして、例えば (if 式1 式2 式3) のように、
他の言語に比べて 予約語のようなもの (例えば、if 式1 then 式2 else 式3 の then や else 等)
が少ないです。(これを無駄がないと捉えるか、分かりにくいと捉えるかは、好みもあるでしょうが)
なので、構文については、引数の数や順番、括弧の位置等を、正しく把握して記述する必要があります。
- hamayama(2019/12/25 08:15:20 UTC): 上記の例では、
(lambda () 1) は 1 を返す手続き (他の言語でいう関数) を作っていて、
括弧で囲った ((lambda () 1)) は、その手続きを実行するという意味になります。
(ちょうど、+ という手続きを実行するために、括弧で囲って (+ 1 2) とするのと同じです)
- Shiro(2019/12/26 22:32:11 UTC): hamayamaさんの説明のとおりですが、別視点から説明すると、
Schemeの括弧は単なる優先順位の指定ではなく、次のいずれかを指定する「構文」です:
(構文キーワード フォーム ...)
(式1 式2 ...)
- 最初の形式は決められた構文キーワード (
if
やlambda
など) が先頭に来る場合で、 構文キーワードによって特有の意味を持ちます。これはそれぞれ覚えるしかありません。 (マクロを使ってユーザが拡張することもできますが、ひとまず置いておきます)。 - 二番目の形式は関数呼び出しです。よくある言語で
式1(式2, ...)
と書かれるのと同じです。先頭が構文キーワードでなければこちらの形式とみなされます。 - ということは、ただ
(式)
と書いてあったらそれは無引数の関数呼び出し、 よくある言語では式()
と同じ、ということです。 - したがって、例えばJavaScriptで相当するコードを書き直して見ると、次のようになります。
(lambda () 1)
=>function () {return 1}
((lambda () 1))
=>(function () {return 1})()
この場合の括弧とは
おそらく分かる方には当たり前なことだと思いますが、現状私一人ではよく分かりません。
(define san () (+ 1 2)) ;gosh> *** ERROR: Compile Error: syntax-error
なのに
(define (san) () (+ 1 2)) ;gosh> san ;gosh> san ;#<closure san> ;gosh> (san) ;3
なのは何故でしょうか? ちなみに
(define san (+ 1 2)) ;gosh> san ;gosh> san ;3
これはこうなりました。
Shiro(2019/12/24 23:45:46 UTC): defineの基本形は
(define 変数 式)
です。最初の例は式の部分が()
なのにさらに余分な(+ 1 2)
がついてるのでエラーになります。
ところでdefineにはもうひとつの形式があって:
(define (変数 引数 ...) 式 ...)
これは次の形式の略記です。
(define 変数 (lambda (引数 ...) 式 ...))
二番目の例はこちらにあたります。略記せずに書くと:
(define san (lambda () ; これは無引数であることを示す() () ; これは空リストになる式だが、次にまた式があるので結果は捨てられる (+ 1 2)))
というわけです。
Gauche 0.9.8 makeエラー
ソースからのmake+installで躓いています。お力添えをいただきたく。 tarballを展開後、./configure、makeを実行すると以下のメッセージでabortしてしまいます。
TARGETLIB="`pwd`" gcc -std=gnu99 -g -O2 -Wall -Wextra -Wno-unused-label -fPIC -DPIC -Wl,--rpath="`pwd`" -L. -rdynamic -o gosh main.o -lgauche-0.97 -lutil -lm -lpthread ld: error: undefined symbol: GC_get_total_bytes >>> referenced by main.c:420 >>> main.o:(cleanup_main) ld: error: undefined symbol: GC_get_heap_size >>> referenced by main.c:420 >>> main.o:(cleanup_main) ld: error: undefined symbol: GC_init >>> referenced by main.c:685 >>> main.o:(main) collect2: ld returned 1 exit status *** Error 1 in src (Makefile:255 'gosh') *** Error 1 in /usr/src/Gauche-0.9.8 (Makefile:43 'all')
実行環境は macOS High Sierra 10.13.6 の VMware fusion 上で作成した OpenBSD 6.5 です。 ./configure 時に以下のメッセージが表示されていたのが気になっています。warning とその前後数行です。
checking if g++ supports -c -o file.o... (cached) yes checking whether the g++ linker (/usr/bin/ld) supports shared libraries... yes checking dynamic linker characteristics... openbsd6.5 ld.so checking how to hardcode library paths into programs... immediate checking for inline... inline configure: WARNING: "Explicit GC_INIT() calls may be required." checking for pthread_self in -lpthread... yes checking whether compiler supports -Wextra... yes checking whether compiler supports -Wpedantic... no checking for xlc... no checking whether compiler supports -fno-strict-aliasing... yes
よろしくお願いいたします。
- hamayama(2019/08/18 00:25:12 UTC): issue を検索してみましたが、似たような事例はないようです。
https://github.com/shirok/Gauche/search?q=openbsd&type=Issues
test の報告が多いので、ビルドまでは成功している人がいるようですが。。。
エラーの内容からすると、libgc の生成に失敗している感じでしょうか。
- Shiro(2019/08/18 00:59:08 UTC): そのwarningは問題ないです。エラーメッセージからするとgc/ 以下のオブジェクトがちゃんとリンクされていないっぽいですね
齊藤 (2018/11/30 18:10:35 UTC):モディファイアとは?
Scheme の言語仕様では、既にアロケート済みのストレージを改変するような手続きのことを mutation procedure という用語で表しています。
しかし、 SRFI-123 などでは modifier という語も使われています。 modifier とmutation procedure の間に区別はあるのでしょうか?
雰囲気としては、 modifier は accessor に対応づいているもの (Gauche の用語でいう setter に近い?) の印象で捉えているのですが、実際のところはどんなものでしょう?
- hamayama(2019/01/10 09:31:08 UTC): 何となく Scheme:用語集 のページを作ってみました。
readerを実装する際のコーディングスタイル
ポートに対してなにか処理を行うreaderやwriterを実装する際のスタイルに関する質問です。
readerやwriterを実装する際、call-with-fooとwith-fooのどちらにも対応させるためにオプショナル引数portをとる手続きとし、デフォルトではこれを現在のポートに束縛、そして内部の処理をwith-input-from-port/with-output-to-portでくるむことで、より低レベルなリーダーを引数なしで使えて便利なことに最近気がつきました。恣意的な例ではありますが、以下のようなイメージですね。
(define (read-2-chars :optional (port (current-input-port))) (with-input-from-port port (^[] (values (read-char) (read-char)))))
そこで質問なのですが…。 上記のように、処理の本体(ここでは(values ...))をサンクにするために(lambda () ...)でくるむのが、なんだか不恰好なように感じてしまうのです(もちろん、サンクにしなければならない理由はわかるのですが…)。中身が単純な手続きならばcutでどうにでもなりますが、たとえばmatchを使いたいとなるとそうもいきません(いかない、はず…)。
自分の場合、つい以下のように内部defineでサンクを別に定義してしまいたくなるのですが、あまり他ではこのようなスタイルを見かけないように思います(といっても初心者ですのであまり多くの例を見たことがあるわけではないのですが…)。
(define (read-2-chars :optional (port (current-input-port))) (define (thunk) (values (read-char) (read-char))) (with-input-from-port port thunk))
こういう際に推奨されるスタイルや、上記のようなスタイルのデメリットなどはあるのでしょうか? よろしくお願いいたします。
- hamayama(2018/10/24 12:39:51 UTC): thunk を分けることに特に問題はないと思いますが、あえてデメリットを挙げるとすると、
(1) current-input-port が port になる範囲(動的環境のスコープ)が、少し分かりにくくなる気がする。
(2) thunk の名前を考える必要がある。
があると思います。
これらと、ネストが浅くなって thunk の処理に集中できるというメリットを、
天秤にかけて判断することになると思います。
(余談ですが、個人的には、(2)の名前の付け方にしばしば苦労しており、
ユーザー視点と開発者視点を行ったり来たりしながら良い名前を探すものの、
いまいちしっくり来ない場合も多いです。。。)
あとは、call/cc に対する let/cc のようにマクロを使うことも考えられますが、
あまり数が多くなければ、逆に後から読んだときに迷うような気もします。
( https://github.com/shirok/Gauche/blob/f7eac18f4592ff6182c8f981342beb431430a9fe/lib/gauche/common-macros.scm#L229 )
- Shiro(2018/10/28 08:23:17 UTC): サンクはブロック、と思えば… まあ見た目がうるさいという感じは確かにあって、
{...}
を(^[] ...)
に読み替えるリーダマクロを入れようとしたこともあるんですが、そのためにカーリーブレースを使ってしまうのもなあ、と思い止まりました。
独自構文入れるなら#(...)
でもいいのかな…Clojureの#(...)
とは微妙に異なるのが混乱の元になりそうな気もするが…
- 質問者 (2018/10/29 05:53:56 UTC): お二人ともありがとうございます!
hamayamaさんに挙げていただいた問題点のうち、前者はまあ仕方ない(サンクのなかで(tell-port port)
みたいなport引数が必須の手続きを使ったりすると見た目がややこしいのでそういう場合は使わないくらい)、後者についてはnamed letでloop
という名前を付けるのと似た感じで、狭いスコープ内でならthunk
でいいのかなというところで、しばらく使ってみようかなと思いました。
ちなみに、実はオリジナルの構文として、test
に対するtest*
のようにwith-input-from-port*
とかも考えてみたのですが、Racketとかだと抜けたときにcloseするかどうかの区別に使っているっぽいので微妙かな…とも思ってやめた経緯があったりします。
Clojureの#(...)
のような構文はあるといいなと思うことがときどきありますね。cut
やpa$
ではcarの部分にメインの処理(として考えたい手続き)が来ないという見た目の問題もあったりするので…。
- Shiro(2018/10/30 10:31:21 UTC): Clojureの
#(...)
はネストできないというのがSchemer的には 結構厳しい制限と感じるんですよね。 いろいろいじっててネストが出来た時にどちらかfn
で書き直さないとならないのが思考を止める感じになるんで。
lmap と ltake を組み合わせたときの lmap に渡した関数の挙動
遅延シーケンスについて調べています。 以下のコードで add-10 の呼ばれるタイミングについて質問なのですが
(use gauche.lazy) (define (add-10 n) (print "call add-10 n: " n) (+ n 10)) (define (f n) (ltake (lmap add-10 '(1 2 3)) n))
gosh> (f 0) call add-10 n: 1 () gosh> (f 1) call add-10 n: 1 call add-10 n: 2 (11) gosh> (f 2) call add-10 n: 1 call add-10 n: 2 (11 12) gosh> (f 3) call add-10 n: 1 call add-10 n: 2 (11 12 13)
(f 3) を呼び出したときに call add-10 n: 3 のログが出力されないのは何故でしょうか? (このログが出ていないということは (add-10 3) が呼ばれていないと認識しているのですが……。 しかし戻り値 (11 12 13) をみると (add-10 3) は確かに呼ばれているようです。これが分からない)
環境: Gauche scheme shell, version 0.9.6 [utf-8,pthreads], x86_64-apple-darwin15.6.0
- Shiro(2018/09/02 06:59:40 UTC): 確認しました。これはGaucheの動作が怪しいですね。 エラー出力に出すとちゃんと出るので、確かに呼ばれているのだけれど標準出力を何かが食べてしまっているような。
- ああそうか、(add-10 3) が評価されるのって、出力ルーチンが結果を表示している最中です。まだ詳しく見てませんが、そこで出力が競合してるのが多分原因。
- 質問者(2018/09/02 13:34:52 UTC): なるほど、出力の問題であり、add-10 は正しく呼ばれているのですね。
(define (f a) ( 関数 )) と (define f (lambda (a) (関数))) は同値ですか
たとえば
(define add+1 (lambda (a) (+ a 1)))
と
(define (add+1 a) (+ a 1))
は"同値"でしょうか?(同じ引数に対して返ってくる値が同じという意味ではなくて、関数の意味・定義そのものが同じという意味において)
(define make-wa3 (lambda (X) ((lambda (wa) (lambda (n) (if (>= 0 n) 0 (+ n (wa (- n 1)))))) (lambda (a) ((X X) a)))))
と(個人的に大文字のXと小文字とxを混同しそうなのでxをaに変えてあります)
(define (make-wa3 X) (define (wa n) (if (>= 0 n) 0 (+ n (wa (- n 1))))) (define (X a) (wa ((X X) a))))
は"同値"でしょうか。よろしくお願いします。
- 齊藤 (2018/06/13 07:00:10 UTC): (define (<変数> <仮引数部>) <本体>) は (define <変数> (lambda (<仮引数部>) <本体>)) と書いたのと同じ意味であることは保証されています。
あと、今回の質問の意図とは異なるとは思いますが、「同値」という言葉はしばしば比較結果のことを指し、比較手続き (equal? など) が #t を返した場合のことをいいますが、これについてはちょっと曖昧というか未定義とされている部分があって、また、 R5RS と R6RS で挙動が違ったりする部分もあるので、もしも手続き同士を比較した結果という意味での「同値」を扱う必要があるのであれば仕様をよく読む必要があるでしょう。
- hamayama(2018/06/13 13:37:31 UTC): 2個目の make-wa3 についてですが、
(define wa (make-wa3 0)) のようにしても wa ができてしまうので、最初の make-wa3 とは異なっているようです。
多分、define の戻り値が未定義であるため、たまたま wa そのものが返っているのだと思いますが。。。
- hamayama(2018/06/13 15:19:20 UTC): 最初の make-wa3 を define に置き換えると、以下のようになりました。
(無名の lambda に F,G,H という名前を付けて、また、body を記述しました)(define (make-wa3 X) (define (F wa) (define (G n) (if (>= 0 n) 0 (+ n (wa (- n 1))))) G) (define (H a) ((X X) a)) (F H))
syntax-rules の <literals> について
マクロ定義の <literals> に現れる識別子が束縛されている場合で、マクロを呼び出す時には束縛が見えない場合、リテラルのパターンにマッチするものでしょうか?
(let-syntax ((test (syntax-rules () ((_ arg) (let ((yelse '(value))) (let-syntax ((test-aux (syntax-rules (yelse) ((_ yelse) 'ok) ((_ x) 'no)))) (test-aux arg))))))) (test yelse)) ;; ここでは `yelse` は束縛されていない ;;=> ok ;; 正しい?
'no
が返って来るのではないかな?と考えたのですがそうでない理由がわかりませんでした。
以下に質問の経緯を書きます。
R5RSのパターン言語の所の<リテラル>の扱いがよくわからない部分があったので質問します。<リテラル>の扱いについてこんな記述があります。
A subform in the input matches a literal identifier if and only if it is an identifier and either both its occurrence in the macro expression and its occurrence in the macro definition have the same lexical binding, or the two identifiers are equal and both have no lexical binding.
後者(or〜以降)は、二つの識別子が同じで、両方とも字句的束縛がされていない、ということは、例えば、 yelse
の束縛が無い場合に以下のような結果になる、ということだと考えられます。
(let-syntax ((test (syntax-rules (yelse) ((_ yelse) 'ok) ((_ x) 'no)))) (test yelse)) ;;=> ok
前者がどんな例があるかを考えます。素直に考えると以下のものが思い付くのだけれども、上の例との違いがほとんど無いので、いまいちはっきりとしません。これをもってして、“扱いがよくわからない”、と考えています。
(the macro expression って?というのもあるのですが、以下の場合では、多分、 (test yelse)
の式のことだと思うのですが自信無いです…)
(let ((yelse '(value))) (let-syntax ((test (syntax-rules (yelse) ((_ yelse) 'ok) ((_ x) 'no)))) (test yelse))) ;;=> ok
自分なりに考えを整理すると、見た目・字面が同じなので、単に字面で <literals> を比較しているのでは?という疑問が残る、ということです。
この疑問を解消するには、(1)それぞれ異なる場所に束縛されている識別子を比べてみる、(2)どちらか一方のみが、ある場所に束縛されている識別子を比べてみる、その結果、リテラルのパターンにマッチしないということを確認すれば納得できると考えたので、やってみました。
「それぞれ異なる場所に束縛されている識別子を比べてみる」もの: 'no
が返ってくると考えます。
(let ((yelse '(value))) (let-syntax ((test (syntax-rules (yelse) ((_ yelse) 'ok) ((_ x) 'no)))) (let ((yelse #f)) (test yelse)))) ;;=> no ;; 正しい結果だと考えられます
「どちらか一方のみが、ある場所に束縛されている識別子を比べてみる」もの: まず、マクロを呼び出している所では、ある場所に束縛されている場合。やっぱり 'no
が返ってくると考えます。
(let-syntax ((test (syntax-rules (yelse) ((_ yelse) 'ok) ((_ x) 'no)))) (let ((yelse '(value))) (test yelse))) ;;=> no ;; 正しい結果だと考えられます
一方、マクロ定義で <literals> に現れる識別子が、ある場所に束縛されている場合、というのが簡単には書き下ろしずらいです。以下のような、別のモジュールから呼び出すという方法を思い付きました。(他に何か方法があるのだろうか?思い付いたのが質問の冒頭に記述したものです。)
(select-module user) (define-module t.syntax-rules-test (export-all)) (select-module t.syntax-rules-test) (define yelse '(value)) (define-syntax test-literal-bound (syntax-rules (yelse) ((_ yelse) 'ok) ((_ x) 'no))) (test-literal-bound yelse) ;;=> ok ;; 正しい結果 ;; これだけだと字面で比較しているかもしれないという疑問が残ります (select-module user) (import (t.syntax-rules-test :prefix t:)) (t:test-literal-bound t:yelse) ;;=> ok ;; 正しい結果のようにも思いますし、実装依存のような気もします。 ;; 同じ場所に束縛されている識別子なので 'ok が返る(字面が違うのに注目します) ;; ただし、インポートを上記のように使うとこの結果が*たまたま* 'ok が返るよう ;; に識別子がインポートされる、ということの裏返し、とも考えられます。 ;; つまり、これは実装に依存する動作である、とも考えられます。 (let ((yelse t:yelse)) (t:test-literal-bound yelse)) ;;=> no ;; 正しい結果 ;; 異なる場所に束縛されている識別子なので 'no が返る(字面は同じ) ;; XXX: これがわからない (t:test-literal-bound yelse) ;;=> ok ;; 正しい? ;; マクロ呼び出しの `yelse` はどの場所にも束縛されていない。 一方で、 ;; マクロ定義の `yelse` は束縛されている。なので 'no だと考えられるけれども…
最後の、 (t:test-literal-bound yelse)
では 'no
が返って来るのではないかな?と考えたのですがそうでない理由がわかりませんでした。
よろしくお願いします。
- hamayama(2018/06/09 02:57:17 UTC): 最初の例については、GitHub にある Gauche の開発最新版では no になるようです。
多分 https://github.com/shirok/Gauche/pull/330 の付近で修正されたように思います。
ただ、リリースはされていないので、確認するには自分でビルドする必要がありますが。。。
モジュールを使った例については、ちょっと自分には正解が分かりません。
調べてみると、syntax-rules の literal の一致チェックには、Scm__ERCompare が使われています。
https://github.com/shirok/Gauche/blob/b57a85df69fd91e68bee83d3f71c98c0faac5170/src/compile.scm#L1573
ここで、実行時環境でまずチェックを行い、一致しなければ、%free-identifier=? でさらにチェックしています。
https://github.com/shirok/Gauche/blob/b57a85df69fd91e68bee83d3f71c98c0faac5170/src/compile.scm#L1507
これらの関数の実行状況を実際に見てみると、以下のようになっていました。
- (t:test-literal-bound t:yelse)
→ %free-identifier=? で、グローバルな束縛が同一であるため ok となっている。 - (let ((yelse t:yelse)) (t:test-literal-bound yelse))
→ Scm__ERCompare で、ローカル変数の方の yelse が lvar であることを検出して no となっている。
- (t:test-literal-bound yelse)
→ %free-identifier=? で、一方だけグローバルな束縛がある場合には、字面の比較で一致を判定して ok となっている。 (R5RS, R7RSの記述とは違っているような気もしますが。。。あえてそうしているようにも見える。。。)
- Shiro(2018/06/09 08:15:34 UTC): hamayamaさんの解析のとおりですが、最後の例は何かの互換性のためにそうしたんだったような? 古いモジュールだとリテラルキーワードをexportしてないコードがあって問題が出たのかも。ご指摘のとおりR6RS的には変なので、仕様に合うように変えるかもしれません。
ラムダだけで再帰は不可能ですか?
例えば 1から n までの自然数の和(wa)から n を引いた数を返す関数(wa-n)を考えます。 まず1から n までの自然数の和は
(define (wa n) (if (>= 0 n) 0 (+ n (wa (- n 1)))))
なので wa-n は
(define (wa-n n) (if (>= 0 n) 0 (- (wa n) n)))
で、4 を渡すと (4+3+2+1)-4=6 が返ってきます。(そんなんwaに(n-1)を渡したら返ってくる数と同じじゃん、というのはとりあえず) これをラムダを使って一つの関数にしようとすると
(define (wa-n2) (if (>= 0 n) 0 (- (lambda (i) (if (>= 0 i) 0 (+ i (.....(- n 1)))));※ n) n)))
というようになると思いますが、※の行の (.....) が思い付きません。この様な場合、局所関数を定義するか、letrec を使うしか(或いは名前付きletとかdoを使うとか)方法は無いのでしょうか? ラムダの中だけで再帰は不可能なのでしょうか?
- Shiro(2018/05/31 10:22:47 UTC): これは素晴らしい質問です。結論から言うと、lambdaだけで再帰を書くことができます。長くなるので別ページにします→Scheme:lambdaだけで再帰
map についての質問です
(map (lambda (ls) (cons 'a ls)) '(a b c))
だと
((a . a) (a . b) (a . c))
が返ってきましたので、
(map (lambda (ls) (cons 'a ls)) '())
を評価すると
(a)
が返ってくると思っていたのですが
()
が返って来ました。今ひとつ理解できません。map の第2引数にヌルを渡すと、必ずヌルが返ってくるのでしょうか。
(map (lambda (ls) (cons 'a ls)) '(a b ())) ((a . a) (a . b) (a))
になるのですが。 また何となく
(map (cons 'a) '(a b c))
で行ける気がしたのですが 「cons は2つ引数が要るんじゃ!1つしか貰っとらん!」 と怒られました。 map の第一引数に2つ(以上)の引数が必要な関数を使う場合、lambda で定義しないといけないのでしょうか?
(map cons '(1 2 3) '(a b c))
では
((1 . a) (2 . b) (3 . c))
と仕事してくれるのですが、どう理解したものか分かりません。
- Shiro(2018/05/09 17:16:09 UTC): まずひとつめ。
()
と(())
を区別することが肝要かと。()
は空リスト、つまり要素を持たないリスト(())
は要素をひとつ持つリストで、その要素とは()
。- (map f list)はリストlistの各要素にfを適用します。listが
()
なら、要素がないので適用するものがありません。結果も要素のないリスト()
となります。 - listが
(())
の場合は、listは1要素のリストなのでその要素にfが適用されます。その要素とは()
です。mapの結果も1要素のリストになります。gosh> (map (lambda (ls) (cons 'a ls)) '()) () gosh> (map (lambda (ls) (cons 'a ls)) '(())) ((a))
- Shiro: ふたつめ。Haskellなど関数がカリー化される言語では、必須引数より少ない引数を渡すと、残りの引数を受け取る関数になります。
GHCi, version 7.10.3: http://www.haskell.org/ghc/ :? for help Prelude> let kons x y = x:y Prelude> kons 1 [2] [1,2] Prelude> kons 1 <interactive>:4:1: No instance for (Show ([a0] -> [a0])) (maybe you haven't applied enough arguments to a function?) arising from a use of `print' In the first argument of `print', namely `it' In a stmt of an interactive GHCi command: print it Prelude> :t kons 1 kons 1 :: Num a => [a] -> [a] Prelude> :t kons 1 [2] kons 1 [2] :: Num a => [a]
- これはScheme風に書けば、(kons 1)が自動的に(lambda (y) (kons 1 y)になっているということです。
- しかし、Schemeでは関数はカリー化されません。(cons 'a)という式があったらその時点で一個の引数でconsを呼び出して値を求めようとしてエラーになります。従って、一つの引数を固定して残りを受け取りたい場合は (lambda (y) (cons 'a y)) と書かないとなりません。
- ただ、こういうパターンは頻出するので略記できる仕組みが用意されています。ひとつはsrfi-26のcutで、(cut cons 'a <>) と書くと(lambda (y) (cons 'a y)) と同じことになります。SRFI:26。Gaucheでは組み込みになっています。
- もうひとつはGauche独自の組み込み手続きpa$で、(pa$ cons 'a)と書くと(lambda xs (apply cons 'a xs))になります。GaucheRefj:pa$。
#N タグ(?)が何故使われているのかが分かりません
n個の数要素を持つリストを、car部分を消去しながらn個のリストにするコード、nlistとn2listを書いてみました。
(define (nlist ls) (if (null? ls) '() (cons (cons (car ls) (cdr ls)) (nlist (cdr ls)))))
と
(define (n2list ls) (if (null? ls) '() (cons ls (n2list (cdr ls)))))
です。この二つは同じだと思うのですが、
gosh> (nlist '(1 2)) ((1 2) (2))
gosh> (n2list '(1 2)) ((1 . #0=(2)) #0#)
と、後者では繰り返し(?)を表す(のだと思う) "#" タグで 2 が返ってきます。何故でしょうか。
また、マクロで返ってくるコードを実行する場合、このように "#" タグが使われていても、問題なく処理されるのでしょうか。
よろしくお願いします。
- Shiro(2018/04/28 18:55:30 UTC):
#n=
は、リストの部分共有があることを示しています。図で書くとこんな感じで、(2)
のセルが両方からポイントされてるわけですね。+---+---+ +---+---+ | * | *---->| * | ()| +-|-+---+ +-|-+---+ | | | \---\ | | | +---+---+ \-->+---+---+ \--->| 1 | *------->| 2 | ()| +---+---+ +---+---+
部分共有が無い場合はこうなります。+---+---+ +---+---+ | * | *---->| * | ()| +-|-+---+ +-|-+---+ | | +---+---+ | \------>| 2 | ()| | +---+---+ | | +---+---+ +---+---+ \--->| 1 | *------->| 2 | ()| +---+---+ +---+---+
(2)
のリストを破壊的変更しなければ、ふたつの差はほとんどありません(後者の方がメモリを使う、というくらいです。) マクロ展開後のコードを破壊的変更することは普通ありませんから、マクロ出力に#n=
ラベルが含まれていても問題ありません。
Schemeの仕様上、部分共有は必ずしも明示しなくても良いので、前者のリストを((1 2) (2))
と出力しても構わないことになっています。実際、混乱が多いので、開発版(0.9.6になるもの)では既にREPLではデフォルトで部分共有は明示しないようになりました。
0.9.5で#0=
表記が見にくかったら、結果をwrite
に渡せばラベルなしの出力が得られます。
質問者です。お答えありがとうございました。 「nlist は再帰で (nlist '(2)) になった場合 (cons (car '(2)) (cdr '(2))) で新たに (2) というリストが作られるが、n2list の場合 (cons ls (n2list (cdr ls))) で (1 2) の cdr 部分 (2) が共有される、『使い回される』(?)ので同じポインタで指し示される」と理解して合っていますでしょうか?
定義によって導入 (齊藤: 2018/04/21 02:59:58 UTC)
R5RS や R7RS にはこのような文言があります。
定義によって導入されていないグローバルの束縛を変更することは, 本章で定義された手続きの振舞いに対して未規定の効果をもつ。
「定義によって導入されていない」というのはどのような意味でしょうか?
R5RS については、この言葉は「プログラマが define を書いていない (要するに処理系が提供する束縛)」 くらいの意味だと思っていました。
しかし、R7RS だと「import した変数へ set! した効果はエラー」という旨の記述があり、 デフォルト環境は空っぽなので処理系が提供する全ての変数は import を経なければ使えません。 処理系が提供する変数については import した変数についての規定で説明がついてしまい、 「定義によって導入されていない」という文言が有る意味がわからなくなってしまいました。
- Shiro(2018/04/21 20:54:37 UTC): 6節の冒頭のところですね。確かにR7RSライブラリ/プログラムでは全ての束縛はimportもしくはdefineで明示的に導入されるので、defineのない束縛変数はimportされてるはずだからset!できないです。
ただ、直前の5.7でREPLについては処理系が暗黙のトップレベル束縛を導入してもよいとあるので、その場合をカバーするために残されたのかもしれません。 - 齊藤(2018/04/21 21:12:20 UTC): なるほど。 私は REPL の場合に導入される束縛を「暗黙の import」と勝手に思い込んでいたので、そこに区別が有りうるということに思い至りませんでした。
エラーではなく未規定という扱いになっているのも、 R5RS (の処理系やその上で動いていたプログラム) との互換性に配慮したと考えれば納得できます。
(エラーの挙動についての規定が雑なので、区別する意味はそんなになさそうな気もしますが。)
木構造に対するmap
S式をトラバースして特定のシンボルだけを置換したいと思い、以下のようなコードを書いたのですが、これと似た動作をする関数がGauche内にあれば教えて頂きたいです。上手く探しきれずにすみません。
(define (map-tree proc sexp) (if (pair? sexp) (cons (map-tree proc (car sexp)) (map-tree proc (cdr sexp))) (proc sexp)))
- Shiro(2018/03/07 17:57:48 UTC): ツリーのマップは様々なバリエーションがあり得るので上のmap-treeそのものずばりは無いです。やや汎用的なものが、undocumentedですがtext.treeというモジュールにあります。
tree-walk TREE PROC LEAF? WALKER
これはTREEの構造を特定せず、葉の判別述語LEAF?とノードの子供を訪れるWALKERを外から与えるものです。これを使うとmap-treeは次のとおり書けます。(define (map-tree proc sexp) (tree-walk sexp proc (complement pair?) (^[f p] (cons (f (car p)) (f (cdr p))))))
ただ、このくらいならmap-treeを直接書いてしまう方が分かりやすいですね。 map-treeもtext.treeに入れようかな。 - Shiro(2018/03/07 18:34:34 UTC):改めて考えたら「葉であること」を述語にするより「葉でないこと」を述語にする方が使い勝手がいいかなあ。変えるかも。
- kuu?(2018/03/08 14:49:03 UTC):ありがとうございます、私のだとAtomしか触れないからこちらのほうが強力ですね
gauche.configureの動作について
Gaucheの拡張を書いている時にgauche.configureを使用してconfigureスクリプトを書いたのですが、その時に気になる事があったので質問します。
configureスクリプトを書いている時に以下の問題がおきました。
・問題
(cf-check-headers)でヘッダをチェックしてconfig.h.inからconfig.hを生成しようとしたが、ヘッダが見つかったにも関わらず#define HAVE_XXX_Hが出力されませんでした。
・調査
gauche/configure.scmを調べた所、#define HAVE_XXX_Hを出力する箇所(748行目)で比較が常に失敗しているために#define ...が絶対に出力されない事が分かりました。
・修正
ヘッダが見つかった際には比較が成功するように修正した所、正しくconfig.h.inから#define HAVE_XXX_Hが出力されたconfig.hが生成されました。
私の環境では上記の修正で望む動作が実現してその他のconfigureの動作も問題ないのですが、設計思想の面などからこの修正は良かったと言えるのでしょうか。
以下は環境と修正のパッチです。
OS: Ubuntu 14.04 LTS
Gauche: 0.9.5
configure: ./configure '--prefix=/usr/local/gauche-0.9.5' '--enable-threads=default' '--with-tls=axTLS' '--with-dbm=dbm,odbm' '--enable-multibyte=utf-8' 'PKG_CONFIG_PATH=/usr/local/pkgconfig'
patch:
--- ../Gauche-0.9.5/lib/gauche/configure.scm 2016-08-06 19:47:50.000000000 +0900 +++ /usr/local/gauche/share/gauche-0.9/0.9.5/lib/gauche/configure.scm 2017-07-14 08:49:38.699387698 +0900 @@ -111,7 +111,7 @@ (tarname :init-keyword :tarname) (tool-prefix :init-form #f) ; cross compilation tool prefix (config.h :init-form '()) ; list of (config-header . source) - (defs :init-form (make-hash-table 'eq?)) ;cf-define'd thingy + (defs :init-form (make-hash-table 'string=?)) ;cf-define'd thingy (substs :init-form (make-hash-table 'eq?)) ;cf-subst'ed thingy (precious :init-form (set eq-comparator)) ;vars overridable by env (features :init-form (make-hash-table 'eq?)) ;enabled features by cmdarg @@ -745,7 +745,7 @@ (^[line outp] (rxmatch-case line [#/^#undef\s+([A-Za-z_]+)/ (_ name) - (if-let1 defval (dict-get (~ pa'defs) (string->symbol name) #f) + (if-let1 defval (dict-get (~ pa'defs) name #f) (display #"#define ~name ~defval" outp) (display #"/* #undef ~name */" outp))] [else (display line outp)])
- Shiro(2017/07/18 11:35:46 UTC): ありがとうございます。これはcf-check-headersのバグですね。 cf-defineは第一引数にシンボルを取る想定になっています。直しておきます。
- Shiro:コミットしました。HEADからの再ビルドが面倒であれば、cf-check-headersの中でcf-defineを呼んでいる箇所の引数にstring->symbolを入れるだけです。
- 投稿者(2017/07/18 21:55:18 UTC):修正されているのを確認しました。お忙しい中、修正して下さりありがとうございました。
subr, closure, procedureの違い
REPLで組み込みの手続きはsubrと表示され、自分で定義したものはclosureと表示されます。そもそもSchemeでは手続き(procedure)という用語を使うようですが、どうしてこうなっているのでしょうか?
gosh> cons #<subr cons> gosh> (define x (lambda () 123)) x gosh> x #<closure x>
なお http://chaton.practical-scheme.net/gauche/a/2010/09/10 では
Gaucheのクラス名だと,最初から入ってる関数がsubr,作った関数がprocedure,最初から入ってる文法がsyntax,作った文法がmacroになるっぽい.
とありますが、現在の0.9.4ではprocedureでなくclosureと表示されます。仕様が変わったのでしょうか?
- hamayama(2016/11/14 12:41:50 UTC): class-of や d で確認すると、cons と x は、どちらも <procedure> クラスのインスタンスのようです。
#<subr cons> や #<closure x> と表示されるのは、実装が C なのか Scheme なのか、デバッグ用に区別がつくようにしているのではないでしょうか。 - Shiro(2016/11/15 20:29:29 UTC): 整理するとこんな感じです:
- 言語としては、手続きは一律<procedure>型
- 実体は、Cで実装されたものとSchemeで実装されたものがあり、実行方法が違うので内部的には区別している。表示にもそれが反映される。Cで実装されたものをsubr、Schemeで実装されたものをclosureと表示している。
- subrという呼び方は昔のLispからの伝統で、Lisp自身ではなく実装言語(Gaucheの場合はC)で書かれた、本来の意味での「組み込み手続き」。Gaucheの場合はコアに最初から入っている手続きを組み込みと呼んでいるけれどそれにはCで書かれたものもSchemeで書かれてコンパイルされたものもある。
- ユーザから見た違いとしては、Schemeで書かれたclosureの場合
source-code
でソース情報などが参照できる可能性があるが、subrの場合は参照できない (組み込みでプリコンパイルされてる手続きはclosureであってもソース情報が落とされてるのでソースが見られないけど)。closureはdisassembleできるけどsubrはできない。等。
- 質問者: 分かりました。ありがとうございます。subrが一番気になっていたのですが、昔からの伝統だったのですね。
自動整形の設定
lisp全般に言えることですが、ライブラリ固有のマクロや独自に定義したマクロをうまく自動整形する仕組みはないのでしょうか。ちまちま手作業でインデントしたり、lisp-indent-functionを設定したりするのがわずらわしいのですが、皆さまはどのように対応していますか。(put 'keyword 'lisp-indent-function num)は特定のファイル、ライブラリでしか使われない構文がそのlisp方言全体の設定として保存されるのが好ましくありません。理想的にはエディタがファイル内のimport文を読み取ってそのライブラリ特有のインデントのルールをそのファイルに適用してほしいです。
- Shiro(2016/10/01 02:12:26 UTC): なるほどー。私は自分の設定ファイルにscheme-indent-functionを追加してますが、他人と共有できません。EmacsのLocal variablesを使えばソースファイル中に書いておけますが、影響はグローバルですし、ライブラリからimportしたものはそのライブラリのソースも読みにいかないと有効にならない、と。
仰るような機能を持つ開発環境は私は知らないです。かつては、現代ほどサードパーティライブラリを多用するという時代ではなかったので何とかなってたのでしょうね。
作ろうと思えばできなくはないと思います。Emacsではバッファローカルのシンボルプロパティって持てなさそうなんで、独自のバッファローカル変数でインデント情報を管理して、インデント計算でそっちを見にゆくようなモードを作れば…
Windowsでの日本語名ファイルの取扱い
Gauche-mingw-0.9.4.msiで、日本語名のファイルを含むディレクトリに対して (diectory-list2 directory-path)をすると
- cygwinだとNo such file or directoryのエラー(文字化けはしない)
- DOS promptだと文字化けする上にNo such file or directoryのエラー
- chcp 65001しても同じ となってしまいます。
Windowsで日本語名のファイルを取り扱うスクリプトを書くにはどうすると良い のでしょうか?
以前の環境では、cygwinでビルドしたgaucheでは問題なかったと記憶しているので、 それが無難でしょうか? (2016/05/22 13:11:03 UTC)
- Shiro(2016/05/23 00:16:04 UTC): 開発版では直っているはずです。今すぐ使うなら 開発版をコンパイルしていただくしかないです。 ちょうどdoc/HOWTO-mingw.txtを書き直していたところなんですが、現在の開発版では mingw-w64 (mingw32, mingw64) & msys2 がWindowsでの推奨開発環境になります。 普通にビルドするだけなら、msys2を入れて ./DIST gen + configure + make で いけると思います。 0.9.5リリースは近い、はずなんですが多忙につき進捗が亀になっています。
- hamayama(2016/05/23 09:24:42 UTC):自分の環境(Windows 8.1 (64bit))で実行してみたところ、
開発版の Gauche v0.9.5_pre1 でも以下のエラーになりました。
*** SYSTEM-ERROR: stat failed for C:\xxx\日本語の文字化け No such file or directory
ソースの win-compat.h で、stat を、ワイド文字対応の _wstat の define にすればよいように思いますが、
コメントに以下の記述があるのが少し気になります。
NB: stat() needs special treatment; MinGW defines its own macro.
エラーメッセージの日本語の文字化けについては、Gauche:Windowsコンソール関連 の msjis の項目に説明があります。
- Shiro:ああ、statでもひっかかっていたんですね。思い出しました。 statが厄介なのは、関数名を切り替えるだけじゃなくてstructの名前も対応するものに 切り替えないとならないからです。例えば stat() -> _wstat32(Scm_MBS2WCS...) したら、 引数に渡すstruct statで書いてあるところを全部struct _stat32 にしないとならない。 まあ、このさいですからやってしまうことにします。
- Shiro(2016/05/24 12:42:31 UTC): これでどうかな。手元のWindows7(言語設定は英語)では 日本語ファイル名でも動くようになりました。 https://github.com/shirok/Gauche/commit/db4fb29fc36968bd8a4e62feecedeeb78db499e6
- hamayama(2016/05/24 14:30:10 UTC): Windows 8.1 (64bit) 環境で動作することを確認できました。
また、MSYS2/MinGW-w64 開発環境についてのメモが Gauche:Windows/MinGW-w64 にあります。
- 質問者 (2016/05/25 12:02:20 UTC): 回答ありがとうございます。日和って0.9.5待とうかな…
syntax-rulesでcondlet
ポール・グラハム著のOn Lispに載っているcondletをsyntax-rulesで再現しようしたのですが,どうにもうまく行かなくなったので質問致します.まずひとつ目は私が書いたマクロの誤りを教えていただきたいです.
;; 定義 ;; ローカル変数を抽出する (define-syntax cond-let (syntax-rules () ((_ ((condition (var val) ...) ...) body ...) (cond-let% () ((var ...) ...) ((condition (var val) ...) ...) (body ...))))) ;; 抽出した変数のリストを平坦化する (define-syntax cond-let% (syntax-rules () ((_ (var% ...) () clauses bodies) (cond-let%% () (var% ...) clauses bodies)) ((_ (var% ...) ((var ...) rest ...) clauses bodies) (cond-let% (var% ... var ...) (rest ...) clauses bodies)))) (define-syntax cond-let%% (syntax-rules () ((_ vars%% () ((condition (var val) ...) ...) bodies) ;; 外側の骨格を作る (cond (condition (let ((var val) ...) (cond-let%V () vars%% (var ...) bodies))) ...)) ((_ (var%% ...) (var0% var1% ...) clauses bodies) (letrec-syntax ((cond-let%%% (syntax-rules (var%% ...) ;; 抽出した変数の中の重複するものを取り除く ((_ var%% vars%% vars%) (cond-let%% vars%% vars% clauses bodies)) ... ((_ var%%% vars%% vars%) (cond-let%% (var%%% . vars%%) vars% clauses bodies))))) (cond-let%%% var0% (var%% ...) (var1% ...)))))) (define-syntax cond-let%V (syntax-rules () ((_ (var%V ...) () vars (body ...)) ;; 未束縛の変数に'()を束縛する (let ((var%V '()) ...) body ...)) ((_ (var%V ...) (var0%% var1%% ...) (var ...) bodies) (letrec-syntax ((cond-letV (syntax-rules (var ...) ;; 未束縛の変数を抽出する ((_ var vars%V vars%% vars) (cond-let%V vars%V vars%% vars bodies)) ... ((_ varV vars%V vars%% vars) (cond-let%V (varV . vars%V) vars%% vars bodies))))) (cond-letV var0%% (var%V ...) (var1%% ...) (var ...)))))) ;; 想定している動作 (cond-let (((= 1 2) (x 'a) (y 'b)) ((= 1 1) (y 'c) (x 'd)) (else (x 'e) (z 'f))) (list x y z)) => (cond ((= 1 2) (let ((x 'a) (y 'b)) (let ((z '())) (list x y z)))) ((= 1 1) (let ((y 'c) (x 'd)) (let ((z '())) (list x y z)))) (else (let ((x 'e) (z 'f)) (let ((y '())) (list x y z))))) => (d c ())
実際は`unbound variable: x'とエラーが出ます.bodyにローカル変数を使わなければ正しくリストが返ります.オリジナルの版と比べてもやたらと長く,おそらく筋の悪い書き方なので申し訳ないのですがよろしくお願いします.
もうひとつはGaucheではどのようにマクロをデバックすれば良いかということです.macroexpandを用いてもlet(rec)-syntaxより先まで式が展開されず,想定通りの式が出来上がっているのか分からないのでそこで手詰まりになります.何か良い方法があったらアドバイスください.
よろしくお願いします
- Shiro(2016/04/08 09:21:48 UTC): とりあえずすぐ回答できる点を。
- 開発版Gaucheではフォーム中のマクロを全て再帰的に展開する
macroexpand-all
が使えます。gosh> (macroexpand-all '(cond-let (((= 1 2) (x 'a) (y 'b)) ((= 1 1) (y 'c) (x 'd)) (else (x 'e) (z 'f))) (list x y z))) (if (= '1 '2) (letrec ((x.0 'a) (y.1 'b)) (letrec ((z.2 '())) (list x.0 y.1 z))) (if (= '1 '1) (letrec ((y.3 'c) (x.4 'd)) (letrec ((z.5 '())) (list x.4 y.3 z))) (letrec ((x.6 'e) (z.7 'f)) (letrec ((y.8 '())) (list x.6 y z.7)))))
- 出力はわかりやすくインデントしました。ローカル変数が
x.0
等となっているのは、 衛生マクロの変数衝突回避のリネームがかかっているからです(内部的にはidentifierになってるんですが、macroexpand-all
がわかりやすいようにシンボルに直しています。また、変数リネームにより束縛が解決された後ではlet
は全てletrec
として扱って構わないので、macroexpand-all
は全てletrec
にして返します) - で、これを見ると未束縛変数をバインドするために導入された
z.2
などと元のフォームのz
が同一視されていないことがわかります。 - これが想定される動作なのかGaucheの問題なのかはもうちょい深く見てみないとわからないです。マクロ展開によって挿入されるマクロのリテラルの扱いが怪しかったような覚えがあるので。
- 開発版Gaucheではフォーム中のマクロを全て再帰的に展開する
- ご返答してくださってありがとうございます. ところがせっかく展開結果を載せて下さったにもかかわらず,それと元のコードとを読み比べても解決法が見いだせませんでした.試しに別の処理系でと思ってchibi schemeを導入してみたところうまく動いたので,Gaucheでsyntax-rulesのみ使うのは諦めてdefine-macroを併用して書き直してみることにしました.コード全体もすっきりしましたし,これでよしとしておきます.
- Shiro(2016/04/10 03:59:59 UTC): Chibiで動いたのならGaucheのバグっぽいですね。 なおGaucheのHEADにはExplicit renaming macro (er-macro-transformer) も入っているので、define-macroの柔軟性と衛生的マクロの両方が欲しければ今後は そちらがおすすめです。
Gauche(Scheme)初心者が約束通り再帰で躓いたので質問したく存じます。
最近のラノベのタイトルのような質問で申し訳ありませんが、分かりやいようにと思いこの様な質問に致しました。ご不快に思われたら申し訳ありません。
例えばリストの個数を数える関数、kosuを再帰で作って、traceしてみます。
(use slib) (require 'trace) (define (kosu x) (if (null? x) 0 (+ 1 (kosu (cdr x))) ) ) (trace kosu) (kosu '(a b c)) gosh> CALL kosu (a b c) CALL kosu (b c) CALL kosu (c) CALL kosu () RETN kosu 0 RETN kosu 1 RETN kosu 2 RETN kosu 3 3
はい、リストの数を数えてくれました。だがしかし、ここで私には上手く分からない疑問が2つあります。
其の1
(if (null? x) 0[若しくは '()] 再帰[大抵は元のリストをcdrする])
は、初心者向けのScheme本によく出てくる例ですが、ここで最初に疑問に当たりました。
何故ならばtraceして評価してみると
CALL kosu ()
とありますように、リストをcdrしていくと最後は () つまりnullになるわけですから
(if (null? x) 0
のところで引っ掛かり((null? x)が#tになり)、0が返って来て、そこで終了になるように思えるのです。 実際、
(if #t 0 1)
を評価しますと、必ず0が返ってきます。同じことが起きないのは何故なのでしょうか。
- では、if の働きを理解するために、仮に if を取り外して考えてみましょう。
(kosu x) = (+ 1 (kosu (cdr x))))
日本語に訳すと「x の kosu は (cdr x) の kosu に 1 を足したもの」となります。 ただし、ヌルリストの cdr は取れないので、ヌルリストの kosu は 0 と定義してしまいます。 ヌルリストには要素がありませんから妥当な定義と言えるでしょう。(kosu '()) = 0
最初の定義と合わせると(kosu x) = (+ 1 (kosu (cdr x)))) ただし (kosu '()) = 0
これを Scheme に翻訳すると(define (kosu x) (if (null? x) 0 ; ヌルリストの kosu は 0 (+ 1 (kosu (cdr x))))) ; それ以外は cdr の kosu に 1 を加える
其の2
この関数、「kosu」は
(kosu '(a b c))→(+ 1 (kosu '(b c)))→(+ 1 (+ 1 (kosu '(c))))→(+ 1 (+ 1 (+ 1 (kosu '()))))→(+ 1 (+ 1 (+ 1 0)))
を計算して3を返しているのだと思いますが(違っていたらすみません)、私には
- これが正解です。 もうちょっと進めて (+ 1 (+ 1 (+ 1 0))) → (+ 1 (+ 1 1)) → (+ 1 2) → 3
(+ 1 (kosu (cdr '(a b c))))→(+ 1 (kosu '(b c)))
↓
(+ 1 (kosu (cdr '(b c))))→(+ 1 (kosu '(c))
↓
(+ 1 (kosu (cdr '(c))))→(+ 1 (kosu '())
そして if の条件が真になり 0 が返ってきて(何故ここで0が返ってこないか疑問であるのは、其の1で述べた通りですが、ここでは無視します)
(+ 1 0)
となり、1が返ってくるように思えるのです。つまり、(+ 1 (+ 1 (+ 1 0))) の左側二つの"1"たちは、足されようとしたら足すはずの相手が再帰で振り出しに戻るので、「そうか、またあそこへ戻るのかい……じゃあ新しい1と上手くやんな。あばよ」と去っていくのではないかと思えます。
- 「あばよ」と去っていくのは goto で関数の呼び出しは「ちょっと行ってくるら待ってててね」という意味なんですよ。
左側二つの"1"たちは、どこで(+ 1 0)の答えを待っているのでしょうか。関数自体が記憶しているわけではないと思いますが……優しさでしょうか。しかし"0"に二股どころか三股掛けられても平気なわけではないと思います。 これは一体どう解釈すれば良いのでしょうか。よろしければ詳しい方、ご教授お願いいたします。(杜仲茶)
- 実は「ちょっと行ってくるから待っててね」と浮気に出かける kosu は 戻ってこれるように、待ってる人がいる場所をちゃんと覚えとくんですね。 その場所が継続というものです。 少し高度に話になりますが、 その場所は必ずしも浮気に出かける直前の場所でない事に注意。あくまで待っている人がいる場所なんです。 勉強を続けていくと、そのうち末尾呼び出しという言葉が 出てくると思いますが、その辺りに関連します。
かしこ
追記:試しに
(define (kosu x) (if (null? x) 0 (+ (kosu (cdr x)) 1) ) )
と、最後の式の順序を入れ替えましたところ、同じように '(a b c) に対して3が返って来ましたし、traceしても同じ結果でした。「そりゃ足し算は順序関係ないけどさー」と思いつつも、1を足す前に「振り出しに戻る」を繰り返すのですから、ますます
(+ 0 1)
だけが評価されて終わる気がするのです。
追記の追記:この順序だと
(kosu '(a b c)) (+ (kosu '(b c)) 1) (+ (+ (kosu '(c)) 1) 1) (+ (+ (+ (kosu '()) 1) 1) 1)
となり
(+ (+ (+ (+ 0) 1) 1) 1)
となると思うのですが、今回は右の "1" たちが残っている理由が分かりません。
- 残っているのでなく、まだ続きがあるんですね。 一番内側は (+ 0) じゃなくて (+ 0 1) で、続きを書くと (+ (+ (+ 0 1) 1) 1) → (+ (+ 1 1) 1) → (+ 2 1) → 3。trace した時の後半部分です。
上手く分からない理由を上手く説明できなくてすみません。
- hamayama(2016/02/09 21:10:46 UTC):基本的には、関数を呼び出して処理が完了すると、呼び出し元の次の命令から実行が継続されるということだと思います。
このため、関数①の中からさらに関数②を呼び出した場合には、呼び出し元の関数①の状態は記憶されていて、 関数②の処理が完了した後に、関数①の残りの命令が実行されます。
したがって、「0が返って来て、そこで終了」とはならず、そこから3個の呼び出し元に帰っていき、それぞれで+1されるため、最終結果が3になります。
traceの結果がまさにそれを表しています。
(末尾呼出し最適化等により、関数の状態が記憶されないケースもありえますが、それは上記を理解できた後に、さらに勉強するとよいと思います。。。)
質問の仕方
すみません。このページでの質問の仕方が分からないのですが、こうやって「編集」をして質問するのでしょうか?
- はい、その通りです。他のエントリーを参考にヘッダをつけたりしてくださいね。
型によるプログラミングをする際の関数の宣言方法について
hateman? 任意の引数が存在する場合の関数の宣言方法はどうすればいいのでしょうか。 例えば 文字列の足し算や
(+ "hoge" "fuga" "aaa") # -> "hogefugaaaa"
A, B, C は(独自の)行列クラスの場合の掛け算
(* A B C) # -> ABC
などは generics 型によって動的にどの手続きを呼ぶか判断されると思いますが、 これが任意の引数の場合に型で判断できるように +, * の手続きを実装する上手い書き方がありますでしょうか。 よろしくお願いします。
- 齊藤 (2015/02/19 15:48:15 UTC): define-method で + にメソッドを追加すればよいです。
(define-method + ((x <string>) (y <string>) . z) (apply + (string-append x y) z))
しかし、性能上の問題などから一部の演算は特別なフックが仕掛けられている場合があり、 + はそのひとつです。 object-+ にメソッドを追加するとただ + に追加するよりは速いかもしれません。(define-method object-+ ((a <string>)(b <string>)) (string-append a b))
残念ながらこの方法では、 + は可換であることを利用した最適化がかかる場合があるとのことで、順序が意味を持つ文字列の連結に使うと意図しない挙動になるかもしれません。 また、 object-+ にメソッドを追加するのは現状ではドキュメントに載っていない隠し機能です。
hateman? ありがとうございます。 確かに define-method に apply すればいいだけでした。。初歩的ですいません。 また object-+ の機能についてもご説明ありがとうございます。
macroexpand の再帰的な評価について
takker? macroexpand を再帰的な評価をすることは可能なのでしょうか。 例えば以下のような、 my-or を macroexpand を行うと最後まで評価されずに終わってしまいます。
(define-macro (my-or . args) (if (null? args) #f (let ((sym (gensym))) `(let ((,sym ,(car args))) (if ,sym #t (my-or ,@(cdr args)))))))
クロージャーなどでは、macroexpand-all などが存在するようなのですが これを自分で定義することは可能なのでしょうか。 いろいろ調べてもよく分からなかったので質問しました。
- Shiro(2014/07/09 22:38:00 UTC): 今のところ組み込みで全部展開してくれるものはありません。
自前で定義することはもちろんできます。が、特殊形式の扱いは個別に書かないとならないので
(例えば
(let ((var init) ...) body ...)
を見たら、再帰するのはinitとbody部分、 というようなルールを自分で書いてやる必要がある)、二度手間っぽいことにはなります。 Gaucheのコンパイラは特殊形式の扱い方を知ってるわけですから。
現状、Gaucheはマクロ展開と並行して内部形式への変換を行ってしまうので、マクロ展開だけを うまい具合に取り出せないんですが、自前で書いてると新たな特殊形式をGauche本体に足した時に 齟齬が生じるので、いずれはオフィシャルなコードウォーカーを提供したいですね。- Shiro(2016/02/08 00:28:40 UTC) 現時点のHEADで
macroexpand-all
が使えるようになっています。返り値の仕様はいじるかもしれません。
- Shiro(2016/02/08 00:28:40 UTC) 現時点のHEADで
- takker? 早速ご返信ありがとうございます。再帰を使ったマクロのデバッグに苦労 していました。自前で定義することも可能とのことなので、ひとまず自分で試行錯誤してみようと思います。
- takker?(2014/07/10 12:39:19 JST): 追記です。以下で一応上手くいきました。展開されるマクロは一つのみだけですが。。一応コードについても載せておきます。
(define (macroexpand-rec macro form) (cond [(null? form) '()] [(not (pair? form)) form] [(eq? macro (car form)) (macroexpand-rec macro (macroexpand form))] [else (cons (macroexpand-rec macro (car form)) (macroexpand-rec macro (cdr form)))])) (define-macro (%macroexpand-rec macro form) `(macroexpand-rec (quote ,macro) (quote ,form))) ;; マクロ展開 (%macroexpand-rec my-or (my-or (eq? 5 5) (eq? 3 4)))
- Shiro(2014/07/10 05:53:52 UTC): ちょっと確認するくらいならそれで良いと思います。
一般的な話でいうと、else節のところでcarとcdrで再帰すると、formがたまたま
(foo my-or x)
という式だった場合、(cdr form)が(my-or x)になって展開されちゃうんで、(map (lambda (x) (macroexpand-rec macro x)) form)
とするのが定石ですね。ただこの場合、formが完全なリストであることを確認しておく必要があります。
上で特殊形式云々、と言ってるのは、(lambda (my-or x) body ...)
とか(let ((my-or x)) body ...)
なんてのが出てきた場合の(my-or x)の部分は展開しちゃいけないし、bodyの中でもmy-orは (マクロをシャドウする)単なる変数になってるので展開できない、といった問題が出てくるということです。 完全にやろうとするとそこそこ面倒です。
Gauche(0.9.4_pre3)のr7rs環境について
asada? 2014/03/21 07:51:48 UTC
gosh -r7で起動した時,Gaucheの既存のprocedureをimportするには、どうすれば良いですか?regexp?等の正規表現が使いたいのです。
(import (gauche regexp))としたのですが、意図通りにimportできてない様です。 お手数ですが、アドバイスを頂けると助かります。
- Shiro(2014/03/21 08:48:46 UTC):
(import (gauche base))
とすれば、 Gaucheの組み込み関数が使えるようになります。 autoloadされる関数も使えるようになるので、Gaucheモードでuse無しで使えてる機能は 基本的に(import (gauche base))
だけで使えるようになるはずです。
- asada? ご対応ありがとうございます。問題無く使用できました。
- asada? https://github.com/shirok/Gauche/blob/master/doc/modr7rs.texi こちらにヒントが記載されてましたね。不勉強でした。お手数お掛けしました。
Window7(Gauche-mingw-0.9.3.3.msi)の gauche-package でエラー
anon(2014/01/05 02:27:58 UTC): 自分のPC上でWiLiKiを使いたいと思って、Apacheをインストールして
#!gosh.exe (display "Content-type: text/plain\n\n") (display (gauche-version)) (display "\n") (display (gauche-architecture)) (display "\nHello World")
というCGIスクリプト(localhost/cgi-bin/test.scm.cgi)が
0.9.3.3 i686-pc-mingw32 Hello World
を返すのを確認した後、DOSプロンプト上でWiLiKiをインストールしようとしたところ、
D:\>gauche-package install WiLiKi-0.6.2.tgz *** SYSTEM-ERROR: cannot find program '#f': No error Stack Trace: _______________________________________ 0 (sys-fork-and-exec (car argv) argv :iomap iomap :directory dir :si ... At line 183 of "D:\\Program Files\\Gauche\\share\\gauche-0.9\\0.9.3.3\\lib/gauche/process.scm" 1 (run-process (cond-expand (gauche.os.windows (win-break-cmdargs cm ... At line 51 of "D:\\Program Files\\Gauche\\share\\gauche-0.9\\0.9.3.3\\lib/gauche/package/util.scm" 2 (clean config dir) At line 148 of "D:\\Program Files\\Gauche\\share\\gauche-0.9\\0.9.3.3\\lib/gauche/package/build.scm" 3 (usage) At line 93 of "(input string port)"
となります。 (clean config dir)するためのコマンドが見つからないということなのでしょうか?
- Shiro(2014/01/05 11:12:55 UTC): ふむぅ。gauche-packageはMinGW+MSYSのUnix風ツール類 があることを当てにしているので、そのせいですね。cleanはrm -rfを呼んじゃってます。 他にも、cat, tar, gzip, makeあたりが必要です。 rmとかはもう外部コマンド呼びだす必要はないので、vanilla windowsでも動くように 徐々に改良してゆくことはできますが、今のところはMSYS環境でMSYS bashから 実行してもらうのが確実かと思います。
- anon (2014/01/05 22:39:26 UTC): MSYS、やってみます。アドバイスありがとうございました。
ファイルのアクセス日時のみ現在の日時にしたい
2011/10/26 19:49:51 PDT: (touch-file "filename")だと更新日時も変わってしまいます… アクセス日時を変えたいだけなので、実際にファイルを読み込む手間を省く方法はないでしょうか?
- Shiro(2011/10/26 20:22:50 PDT): 今やるなら
(let1 s (sys-stat "filename") (sys-utime "filename" (sys-time) (~ s'mtime))
でしょうか。将来的にはtouch(1)コマンドのようなオプションをtouch-fileにつけるのがいいですかね。 - ありがとうございました。オプションはあるとうれしいですね。(2011/10/26 23:02:40 PDT)
refの仕様
2011/07/05 11:41:25 PDT: これ,デフォルトではエラーなんですね.少し引っかかりました.
(ref xs 1.0)
- Shiro(2011/07/05 14:41:52 PDT): もともとSchemeの規格にある list-ref, vector-ref, string-ref は第2引数に「正確な非負整数」を取ることになっていて、非正確数を 渡せばエラーになります。それに合わせてあるだけなんですが、どういう動作を期待しましたか?
2011/07/05 19:16:14 PDT: Cプログラムを何も考えずにSchemeに移植していたのですが、こう定義すると
gosh> (define-method ref ((l <list>) (r <real>)) (ref l (inexact->exact r)) ) #<generic ref (10)>
これらは動きます
gosh> (ref '(a b) 1.0) b gosh> (~ '(a b) 1.0) b
これはエラーになってしまいます
gosh> (set! (~ '(a b) 1.0) 1) *** ERROR: no applicable method for #<generic |setter of ref| (6)> with argumen s ((a b) 1.0 1) Stack Trace: _______________________________________
さらにこう定義して期待通りの動作となりましたが、正しいのかわかりません
(define-method (setter ref) ((l <list>) (r <real>) obj) (let1 i (inexact->exact r) (set! (ref l i) obj)))
- Shiro(2011/07/05 19:21:36 PDT): その定義で正しいです。通常のメソッドを定義したら
setterメソッドが勝手についてくる、なんてことはないので、setterメソッドは
別に定義してやらないとなりません。
まあ、inexact numberでrefするのって気持ち悪いんで、心情的には 呼出側で (ref xs (exact r)) とか (set! (ref xs (exact r)) n) とか する方が綺麗だと思いますが、手っ取り早く移植したいならメソッド定義しちゃうのは ありです。なお手続き exact はR6RSですがGaucheでも0.9.1から使えるようになってます。
syntax-rules の ... の動作
齊藤 (2010/09/29 19:46:49 PDT): syntax-rules のテンプレートについてです。 以下のように書くとエラーになります。
(define-syntax hoge (syntax-rules () ((_ r ...) (list '(r (r ...)) ...)))) (hoge 1 2 3)
期待する展開形は
(list '(1 (1 2 3)) '(2 (1 2 3)) '(3 (1 2 3)))
です。
... が (r ...) にも掛ってしまうことが原因とは思いますが、 R5RS 的には未定義なんでしょうか? 手元で試してみたところ、 Gauche を含む R5RS 処理系のいくつかではエラーになり、主要な R6RS 処理系では期待通りに展開されるようです。
Shiro(2010/10/02 17:33:18 PDT): はい、これはR5RSでは曖昧で、R6RSで明確化されました。
Pattern variables that occur in subpatterns followed by one or more instances of the identifier ... are allowed only in subtemplates that are followed by as many instances of ....
このas manyは「同数の」ですが、文章全体が必要条件を述べているともとれ、その場合 as manyは下限を示していると解釈可能です。テンプレートの方で...が多い場合については 仕様は何も言っていないので、以下のどちらの解釈もOKです。
- 厳密に解釈して、同数の...でないとエラーにする
- ...が多い場合は処理系拡張の動作とする
R6RSでは...が多い場合について明示的に許され、振る舞いも定義されました。
If a pattern variable is followed by more ellipses in the subtemplate than in the associated subpattern, the input form is replicated as necessary.
齊藤 (2010/10/08 21:35:41 PDT) : なるほど。 納得しました。 R5RS 的には使わない方がよさそうですね。
write の出力のカスタマイズ
osn(2010/05/27 22:16:11 PDT): 実数の write での出力では、桁数に応じて固定小数点表記あるいは浮動小数点表記とが切り替わります。
gosh> (write 1.0) 1.0 gosh> (write 10000000.0) 10000000.0 gosh> (write 100000000000.0) 1.0e11
この表記の切り替わりの桁数や浮動小数点表記内容をカスタマイズできないでしょうか。write-object でできないかと思い、以下のようなことをやってみたのですが、効果ありませんでした。
gosh> (define-method write-object ((obj <real>) out) (format out "~a*1000" (/ obj 1000))) #<generic write-object (3)> gosh> (write 3e3) 3000.0 (3*1000 と表示されることを期待)
他処理系で生成されたS式のファイルを gauche で読み込んで write とすると数の表記が変わってしまい、できれば元の処理系と同じ規則を定義して出力したいと思っています。 よろしくお願いします。
- 齊藤(2010/05/28 00:24:07 PDT): write に直接メソッドをかぶせれば一応そのような動作をさせることは出来るようです。
(define-method write ((obj <real>) . rest) (let-optionals* rest ((out (standard-output-port))) (format out "~d*1000" (inexact->exact (/ obj 1000)))))
gosh> (write 3e3) 3*1000
でも、 Scheme では write して read したら同じものになるのが基本なので、こういうカスタマイズはスジが悪い方法かも。 別の名前で専用の関数 (メソッド) を用意した方が無難に思えます。
- Shiro(2010/05/28 00:53:18 PDT): write-objectはプリミティブ型には効果を及ぼしません。
原則としてユーザレベルで追加したクラスに対するもの、という位置づけです。
write-objectによるプリミティブ型の表示の変更を許すと、
性能上の問題と、メソッドオーバロードによる変更は効果がグローバルに
出るために制御しにくいという問題が出ます。
また、齊藤さんの方式は他の構造体中にある浮動小数点数には影響を及ぼしません。 writeは内部的に組み込みのScm_Writeを直接呼んでいるので。
したがって、現在のGaucheでは、(1)write相当の手続きを再実装するか (2)一度構造をwalkして浮動小数点数を<my-flonum>などのオブジェクトに置き換えて、 それに対するwrite-objectメソッドを定義しておくか、になると思います。
将来的には、何らかのパラメータで制御できるようにするアイディアはあります。 既に現場での需要として、IEEE 754形式のバイナリを16進表記で 読み書きする、というものがあるので (そうすると高価な2進-10進変換が 避けられる。大量のflonumデータを読み書きする際に有利)。
なお、<real>は正確な整数や有理数も含むクラスなので、<real>に スペシャライズするのは多分やりたいことと違うと思います。 本来は、抽象型<real>を継承した<flonum>のような型であるべきなんですね (cf. Gauche:NumericTower)。
- osn(2010/05/28 01:26:48 PDT):齊藤さん、Shiro さん、ありがとうございました。私の不正確な表記から汲み取っていただいて Shiro さんが解説いただいた内容で納得しました。(実数、と安易に書いたりしてましたが、整数も実数なわけで、小数点部を持つ実数、と書くべきでした。)50MbytesのS式のファイルを処理しようとして、処理結果の確認をする際にちょっとひっかかってご相談させていただきました。幸い、gauche の write の出力を問題の他処理系でも問題なく読み込め、実用上は問題とはならなそうです。ありがとうございました。
gauche/gdbm on openSUSE 11.x
osn(2010/04/14 20:15:08 PDT):
(便乗したようなタイトルですいません。;)
openSUSE 11.1,11.2(gcc-4.4.1) に Gauche-0.8.14,0.9 をインストールしようとしたところ、./configure の結果では、
optional modules: odbm ndbm gdbm zlib
となっているのに、コンパイル終了時点で、lib/dbm には fsdbm.scm のみしかなく、gdbm インターフェースが生成されないようです。
config.log をみると
configure:11795: checking for dbm_open configure:11851: gcc -std=gnu99 -o conftest -g -O2 conftest.c -ldl -lcrypt - lutil -lm -lpthread >&5 /tmp/ccEbKcoc.o: In function `main': /root/Gauche-0.9/conftest.c:135: undefined reference to `dbm_open' ... configure:12871: checking for dbminit in -ldbm configure:12906: gcc -std=gnu99 -o conftest -g -O2 conftest.c -ldbm -ldl -l crypt -lutil -lm -lpthread >&5 /usr/lib64/gcc/x86_64-suse-linux/4.4/../../../../x86_64-suse-linux/bin/ld: canno t find -ldbm collect2: ld returned 1 exit status ...
等が見られるので、dbm インターフェースに関連して、gauche のコンパイルに失敗しているのかも、と想像しているのですが、対策が分からず困ってます。openSUSE 10.x (gcc-4.1.0)等では問題なくインストールできているのですが、、、対応方法をご教示いただけるとありがたくよろしくお願いします。(gdbm は 1.8.3 で、gauche のインストール可否にかかわらず同バージョンです。)
- Shiro(2010/04/14 20:25:49 PDT): ndbm, odbmについてはconfigure内で本来のライブラリと gdbmエミュレーションを順次チェックするので、一方がfailしていること自体は 問題ではないです。ext/dbm以下のmake時のメッセージはどのようになっていますか。
- osn(2010/04/14 20:46:28 PDT):エラーメッセージが出ていました。確認不足ですいません。といっても対応方法はわからないのですけれど。^^;;
make[2]: Entering directory `/root/Gauche-0.9/ext/dbm' ../../src/gosh -ftest ../../src/precomp -e -o dbm--odbm odbm.scm gcc -std=gnu99 -DHAVE_CONFIG_H -I. -I../../src -I../../gc/include -g -O2 -fPI C -fomit-frame-pointer -c dbm--odbm.c odbm.scm: In function 'dbm__odbmodbm_close': odbm.scm:212: error: too few arguments to function 'dbmclose' make[2]: *** [dbm--odbm.o] Error 1 make[2]: Leaving directory `/root/Gauche-0.9/ext/dbm'
- Shiro(2010/04/14 23:07:30 PDT): あれ? dbm互換のヘッダじゃない何かが見えているのかな?
ext/dbm/dbmconfig.hを見て、
- HAVE_DBM_H が #defineされてたら dbm.h
- HAVE_GDBM_SLASH_DBM_H が #define されてたら gdbm/dbm.h
- HAVE_GDBM_MINUS_DBM_H が #define されてたら gdbm-dbm.h
- をシステムのincludeディレクトリから探してください。
- 見つかったら、その中のdbmcloseの定義を見てみてください。
- Ubuntuの場合、HAVE_DBM_Hが#defineされ、システムのヘッダは /usr/include/dbm.hにあり、dbmcloseの定義は extern int dbmclose() となっています。
- もしdbmcloseの定義が違っていたら、legacy dbm互換でない別のライブラリが 入っててそれと衝突していることになります。
- osn(2010/04/15 00:18:06 PDT): コメントありがとうございます。
- #define HAVE_DBM_H 1 でした。
- /usr/include/dbm.h を見たところ、
/* Determine if the C(++) compiler requires complete function prototype */ #ifndef __P #if defined(__STDC__) || defined(__cplusplus) || defined(c_plusplus) #define __P(x) x #else #define __P(x) () #endif #endif ... extern int dbmclose __P((DBM *));
となってました。#define __P(x) x が有効になって、dbmclose の引数の数があわない、ということになってるということですね。 - プリプロセッサの実際の動作等、分かってないんですが、#define __P(x) () をこの前にどこかで定義してやればいいんでしょうか。
- なお、openSUSE 10.x(問題なかったもの)では、単に extern int dbmclose (); となってました。
- osn(2010/04/15 01:07:50 PDT): 追加情報です。
- /usr/include/dbm.h に#define __P(x) () を挿入して Gauche のコンパイルをしてみましたら、一応、OK となったっぽいです。
- このマクロ(__P)は、suse のパッケージ化の際に加えられたもののようです。 http://lists.opensuse.org/opensuse-commit/2008-02/msg01086.html
- どう対処するのが妥当なのか、ご意見お願いします。
- Shiro(2010/04/15 02:04:12 PDT): オリジナルのdbm APIでもgdbm 1.8.3の互換APIでも
dbmclose()は引数を取らないので、そもそも__P((DBM *))というのがおかしいです。
プロトタイプが必要なら__P((void))となっていなければならないはず。
なので、OpenSUSEがパッケージ化の際にエンバグしたか、あるいはlegacy dbm互換を捨てた
(そもそもdbm互換レイヤが古いプログラム対応のためなので、いまさらそこに非互換性を
持ち込む意味はないと思うのですが)ってことだと思います。
で、"opensuse dbmclose"でぐぐるとPerlのdbmパッケージでやっぱりビルドエラーが
出てるんですが、なんとopensuseのPerlパッケージは
非互換なdbmcloseに合わせてperlの方にパッチをあててしまっているようです。
http://lists.opensuse.org/opensuse-commit/2008-03/msg00374.html
(他には、「ODBMのサポートを外してコンパイルしたら?」というようなアドバイスが 目立ちます)。
コミットから2年も経ってて誰も指摘してないってのが不思議なんですが、 今時legacy dbmを使ってるアプリってまず無いので問題が出てないのかなあ。 でもこれはOpenSUSEのdbmパッケージがfixすべき問題です。
Gaucheとしての対応ですが、現代においてlegacy dbmだけしかない環境って おそらく皆無なので、いっそlegacy dbmのサポートを落としてしまうっていう のもありかも。(Schemeのdbm.odbmはそのままで、Gaucheのルーチンの方で ndbmかgdbmを呼ぶってのでもいい。)
osnさんの手元でさえ動けば良いのなら、こうしてみてください。--- odbm.scm (revision 7085) +++ odbm.scm (working copy) @@ -207,7 +207,7 @@ (result r))) (define-cproc odbm-close () ::<void> - (when odbm_opened (dbmclose) (set! odbm_opened FALSE))) + (when odbm_opened (dbmclose NULL) (set! odbm_opened FALSE))) (define-cproc odbm-closed? () ::<boolean> (result (not odbm_opened)))
- osn(2010/04/15 05:28:04 PDT): 上記修正でのコンパイル&動作OKを確認しました。気をつけて利用していきたいと思います。ありがとうございました。
- 私も、2年前からあった問題のはずなのに、、と、ちょっと悩ましく思っておりました。openSUSE はそれなりにユーザーも多いつもりで使ってきていたので、ちょっとショック。
- 自分では、wiliki等、たまたま、FreeBSD や他ディストリでしか運用していなかったので、openSUSE 11.x の gauche インストールの際、 dbm インターフェースの構築に失敗していたことに気づいてませんでした。それが、最近、近くの者がopenSUSE 11.1 で wiliki を動かそうとしてうまくいかない、と相談してきたのでした。
gauche-gdbm on FreeBSD 8.0
- suzuki@cis.iwate-u.ac.jp (2010/03/21)
WiLiKi のデータベースに gdbm を使っています。 FreeBSD 8.0 で,portupgrade databases/gauche-gdbm に失敗します。 0.9 がBROKENになっているためです。 試しに,Makefile 中の BROKEN をコメントにして make してみると, gdbm.stub がないと怒られます。 0.8.13 のgdbm.stub をコピーしてmakeすると, 実行時エラーになります。
gdbm を使うのはおすすめではないのでしょうか?
対処方法とおすすめのデータベース形式を教えていただけませんか?
- Shiro(2010/03/20 17:12:51 PDT): FreeBSDで何故BROKENになっているのかが分からないので
何とも。Gauche本家としてはgdbmは今までどおりサポートしており、非推奨ということはありません。
0.8.xと0.9の間にext/dbmのソース構成が変わっているので、古いバージョンの ソースは使えません。Gauche本体は0.9なんですよね?
とりあえず使うならGauche-0.9のソースからコンパイルしてみては。
ちゃんと直すならBROKENの理由を調べることから、ですかね。
- osn(2010/03/20 19:05:52 PDT) FreeBSD 7-stable ですが、やはり BROKEN で往生したので、ソースから素朴にインストールして問題なく使えています。WiLiKi が半日でも使えないと苦情が来ますもので。御参考まで。
- suzuki (2010/03/21 16:30 jst) Gauche-0.9 のソースからコンパイルしてみます。
BROKENの理由を調べようとしていますが,行き詰まって,相談しました。
ありがとうございました。
suzuki (2010/03/22 14:40 jst) Gauche-0.9 のソースからインストールしました。FreeBSD 8.0-Release です。下記二ヶ所で悩みました。
- configure のオプションで gdbm を有効にする方法が分からなかったので
ext/dbm/dbmconfig.h.in を修正して HAVE_GDBM_H を define しました。 - '--with-local=/usr/local' を指定。しないと libgdbm.so がリンクされなくなり,実行時にgdbm_errno が undef になりました。
suzuki (2010/03/22 14:40 jst) FreeBSD の gauche-gdbm-0.9が BROKEN状態になっていて、Mark BROKEN with 0.9 updateと記載されています。Makefile は 0.8.13 のものと同じで,ソースディレクトリは,Gauche-0.9/ext/dbm をさしています。
0.9 にまだ対応していないものと思いましたが。。。
- Shiro(2010/03/21 23:00:35 PDT): ちょっと謎です。
- gdbmは明示的に有効にしなくても、configure時にライブラリが見つかれば自動的に 有効になります。従って/usr/local/{include,lib} にgdbmがインストールされているなら、 ./configure --with-local=/usr/local とするだけで良いはず。 手動でconfig.hなどをいじる必要はありません。
- suzuki (2010/03/22 17:10 jst): ご指摘のとおりでした。gauche-gdbm が別ポートになっているので,何か指定をしないといけないと思いんでました。すっきりしました。
- portsの方ですが、元々のext/dbm/Makefileなどはコアに含まれてリリース毎に アップデートされるので、同じMakefileになっているというのはおかしいですね。 もしかするとBSD部分とGPL部分とを分けるために、手動でgdbmに依存するコードだけ 分離しているのかな? だとしたらリリース毎にその作業が必要で、0.9については それをやっていないってだけかもしれません。
文字列上の繰り返しと部分文字列
ziro (2010/03/19 00:53:38 PDT) 勉強のためにいろんな文字列のアルゴリズムを Gauche で書いています.文字列の各文字を順番に見るときには,マルチバイト文字列上でのインデックスによるアクセスは遅いので,string port を使ったほうがいいんですよね.そうして文字列を舐めている途中の二つの状態の間に対応する部分文字列を取得する方法はありますか? 今は port から読んだ文字を別の string port に書いているのですが,どうにも格好がつきません.
Shiro(2010/03/19 04:43:06 PDT): 「遅い」といっても程度の問題で、データのサイズや必要な 性能要件によって変わってきます。インデックスアクセスは入力文字列長に比例するので、 特に問題になるのは「長い文字列に対して頻繁にインデックスアクセスする」という場合です。
従って、(dotimes (n len) (do-something (string-ref str n))) のようなコードは O(N^2)に なってしまいますが、もし取得する部分文字列の総数が定数ならば、
- 文字列を舐めるのにはstring portを使い、マッチした文字インデックスを覚えておく
- 部分文字列を取り出すのはsubstringで行う
でもいけるかもしれません。
入力文字列が巨大で、substringで深くインデックスするのを避けたいという場合は、 部分文字列の先頭になり得る箇所で、get-remaining-input-stringで半部分文字列 (その箇所以降の文字列)を取得しておく、という手もあります。Gaucheでは 文字列本体は共有されるので、get-remaining-input-stringが返す文字列は 事実上入力文字列の途中を指すポインタみたいなもので、たくさん取得しておいても さほど性能に影響は出ません。さらにマッチングを進めて、切り取るべき文字列の 終端が分かったら、get-remaining-input-stringで取った文字列partial-strに対して (substring partial-str 0 (- end-index start-index)) のようにして切り取ります。 string-takeでもいいです。この場合、文字列のインデックスアクセスのオーバヘッドは 部分文字列の長さに制限され、入力文字列の大きさには影響を受けません。
気になるのがどちらかというと性能よりもコードの簡潔さ、読みやすさだというのなら、 切り取る部分は文字のリストにしといて後でlist->stringするのがたぶんエレガントになると 思います。オーバヘッドはありますがO(N)なのでこれでもstring-refで舐めるよりは ずっと良いと思います。
ziro (2010/03/19 07:26:51 PDT) ありがとうございます.安直には,バイト数ベースの添字で substring ができると簡単だと思いますが,そんなことはできないのでしょう か (incomplete string を使えばできる?).それから,文字列の途中から逆向 きに辿ってみたいと思うのですが,添字を使わないですます方法はありますか?
Shiro(2010/03/19 12:47:08 PDT): これ以上は目的がわからないと何とも言えないのですが、 かなり性能的にシビアな状況なのでしょうか。
substringをincomplete stringに適用すれば(今のところ)バイトインデックスとして 扱われますが、文字インデックスからバイトインデックスを簡単に求める方法が無いので 使いどころが難しいですね。string-portで読み出しつつport-tellでオフセットを記録するって 手はあります (ただし、peek-charするとオフセットがずれるというバグがあるので注意)。
とにかく今、性能が欲しいということでしたら、unofficialなstring-pointerという のがあるにはあります。途中から逆向きということができます。ただし将来無くなるかも しれません。
あとは、アルゴリズム的にインデックスアクセスが綺麗に書ける、ということなら いっそベクタに変換してしまうとか。 綺麗さにこだわるなら読んだ文字を全部リストにしとくとか、 文字列から離れる手もあります。
ziro (2010/03/20 05:24:03 PDT) 私の状況は要件が厳しいというものではなく,毎回再計算が起こるから string-ref は軽い気持ちでは使い難いという程度のものです.紹介して下さった方法の一で書いて実際の文字列に適用してみて,様子を見たいと思います.まともに可変バイト長の文字エンコーディングを扱えるのはとても有り難いことなので,うまく Gauche を使えたらと思います.ありがとうございました.
Shiro(2010/03/20 07:15:28 PDT): それなら、とりあえず
- 読み出しはstring-portから。文字数を数えておく。
- 部分文字列の取得は数えておいた文字数を使ってsubstringで。
- 途中から逆向きに探す可能性があるのなら、既に読んだ文字列をリストにconsしてゆき、 そのリストから探すようにする。
というのが、多分シンプルさと性能のバランスのいいところじゃないかと思います。 それで問題が出てきたら上に挙げた他の方法を試すということで。