Scheme:初心者の質問箱

Scheme:初心者の質問箱

メーリングリストで質問したり、WiLiKiに自分のページを作ったりするのはちょっと… というシャイなあなたのためのスペースです。

あたらしい質問は、項目の先頭に追加していって下さい。

書き方を間違えても小人さんが直してくれるので、 こわがらなくてもだいじょうぶ。

長くなってきたので過去ログ:



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))

こういう際に推奨されるスタイルや、上記のようなスタイルのデメリットなどはあるのでしょうか? よろしくお願いいたします。

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

(define (f a) ( 関数 )) と (define f (lambda (a) (関数))) は同値ですか

たとえば

(define add+1
  (lambda (a)
    (+ a 1)))

(define (add+1 a)
  (+ a 1))

は"同値"でしょうか?(同じ引数に対して返ってくる値が同じという意味ではなくて、関数の意味・定義そのものが同じという意味において)

Scheme:lambdaだけで再帰 にある

(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))))

は"同値"でしょうか。よろしくお願いします。

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 が返って来るのではないかな?と考えたのですがそうでない理由がわかりませんでした。

よろしくお願いします。

ラムダだけで再帰は不可能ですか?

例えば 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を使うとか)方法は無いのでしょうか? ラムダの中だけで再帰は不可能なのでしょうか?

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))

と仕事してくれるのですが、どう理解したものか分かりません。

#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 が返ってきます。何故でしょうか。

また、マクロで返ってくるコードを実行する場合、このように "#" タグが使われていても、問題なく処理されるのでしょうか。

よろしくお願いします。

質問者です。お答えありがとうございました。 「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 した変数についての規定で説明がついてしまい、 「定義によって導入されていない」という文言が有る意味がわからなくなってしまいました。

木構造に対するmap

S式をトラバースして特定のシンボルだけを置換したいと思い、以下のようなコードを書いたのですが、これと似た動作をする関数がGauche内にあれば教えて頂きたいです。上手く探しきれずにすみません。

(define (map-tree proc sexp)
  (if (pair? sexp)
      (cons (map-tree proc (car sexp)) (map-tree proc (cdr sexp)))
      (proc sexp)))

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)])

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と表示されます。仕様が変わったのでしょうか?

自動整形の設定

lisp全般に言えることですが、ライブラリ固有のマクロや独自に定義したマクロをうまく自動整形する仕組みはないのでしょうか。ちまちま手作業でインデントしたり、lisp-indent-functionを設定したりするのがわずらわしいのですが、皆さまはどのように対応していますか。(put 'keyword 'lisp-indent-function num)は特定のファイル、ライブラリでしか使われない構文がそのlisp方言全体の設定として保存されるのが好ましくありません。理想的にはエディタがファイル内のimport文を読み取ってそのライブラリ特有のインデントのルールをそのファイルに適用してほしいです。

Windowsでの日本語名ファイルの取扱い

Gauche-mingw-0.9.4.msiで、日本語名のファイルを含むディレクトリに対して (diectory-list2 directory-path)をすると

Windowsで日本語名のファイルを取り扱うスクリプトを書くにはどうすると良い のでしょうか?

以前の環境では、cygwinでビルドしたgaucheでは問題なかったと記憶しているので、 それが無難でしょうか? (2016/05/22 13:11:03 UTC)

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より先まで式が展開されず,想定通りの式が出来上がっているのか分からないのでそこで手詰まりになります.何か良い方法があったらアドバイスください.
よろしくお願いします

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が返ってきます。同じことが起きないのは何故なのでしょうか。

其の2

この関数、「kosu」は

(kosu '(a b c))→(+ 1 (kosu '(b c)))→(+ 1 (+ 1 (kosu '(c))))→(+ 1 (+ 1 (+ 1 (kosu '()))))→(+ 1 (+ 1 (+ 1 0)))

を計算して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と上手くやんな。あばよ」と去っていくのではないかと思えます。

左側二つの"1"たちは、どこで(+ 1 0)の答えを待っているのでしょうか。関数自体が記憶しているわけではないと思いますが……優しさでしょうか。しかし"0"に二股どころか三股掛けられても平気なわけではないと思います。 これは一体どう解釈すれば良いのでしょうか。よろしければ詳しい方、ご教授お願いいたします。(杜仲茶)

かしこ

追記:試しに

(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" たちが残っている理由が分かりません。

上手く分からない理由を上手く説明できなくてすみません。

質問の仕方

すみません。このページでの質問の仕方が分からないのですが、こうやって「編集」をして質問するのでしょうか?

型によるプログラミングをする際の関数の宣言方法について

hateman? 任意の引数が存在する場合の関数の宣言方法はどうすればいいのでしょうか。 例えば 文字列の足し算や

(+ "hoge" "fuga" "aaa") # -> "hogefugaaaa"

A, B, C は(独自の)行列クラスの場合の掛け算

(* A B C) # -> ABC

などは generics 型によって動的にどの手続きを呼ぶか判断されると思いますが、 これが任意の引数の場合に型で判断できるように +, * の手続きを実装する上手い書き方がありますでしょうか。 よろしくお願いします。

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 などが存在するようなのですが これを自分で定義することは可能なのでしょうか。 いろいろ調べてもよく分からなかったので質問しました。

(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)))

Gauche(0.9.4_pre3)のr7rs環境について

asada? 2014/03/21 07:51:48 UTC

gosh -r7で起動した時,Gaucheの既存のprocedureをimportするには、どうすれば良いですか?regexp?等の正規表現が使いたいのです。

(import (gauche regexp))としたのですが、意図通りにimportできてない様です。 お手数ですが、アドバイスを頂けると助かります。

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)するためのコマンドが見つからないということなのでしょうか?

ファイルのアクセス日時のみ現在の日時にしたい

2011/10/26 19:49:51 PDT: (touch-file "filename")だと更新日時も変わってしまいます… アクセス日時を変えたいだけなので、実際にファイルを読み込む手間を省く方法はないでしょうか?

refの仕様

2011/07/05 11:41:25 PDT: これ,デフォルトではエラーなんですね.少し引っかかりました.

(ref xs 1.0)

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)))

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で明確化されました。

R5RS section 4.3.2

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では...が多い場合について明示的に許され、振る舞いも定義されました。

R6RS section 11.19:

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 とすると数の表記が変わってしまい、できれば元の処理系と同じ規則を定義して出力したいと思っています。 よろしくお願いします。

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 のインストール可否にかかわらず同バージョンです。)

gauche-gdbm on FreeBSD 8.0

WiLiKi のデータベースに gdbm を使っています。 FreeBSD 8.0 で,portupgrade databases/gauche-gdbm に失敗します。 0.9 がBROKENになっているためです。 試しに,Makefile 中の BROKEN をコメントにして make してみると, gdbm.stub がないと怒られます。 0.8.13 のgdbm.stub をコピーしてmakeすると, 実行時エラーになります。

gdbm を使うのはおすすめではないのでしょうか?

対処方法とおすすめのデータベース形式を教えていただけませんか?

suzuki (2010/03/22 14:40 jst) Gauche-0.9 のソースからインストールしました。FreeBSD 8.0-Release です。下記二ヶ所で悩みました。

suzuki (2010/03/22 14:40 jst) FreeBSD の gauche-gdbm-0.9BROKEN状態になっていて、Mark BROKEN with 0.9 updateと記載されています。Makefile は 0.8.13 のものと同じで,ソースディレクトリは,Gauche-0.9/ext/dbm をさしています。
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)に なってしまいますが、もし取得する部分文字列の総数が定数ならば、

でもいけるかもしれません。

入力文字列が巨大で、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): それなら、とりあえず

というのが、多分シンプルさと性能のバランスのいいところじゃないかと思います。 それで問題が出てきたら上に挙げた他の方法を試すということで。

More ...