Guile基礎/モジュール雑話

変更履歴

概 要

目 次

参考資料

現象記録:2行目の掟,再び

▹ 以下は現象記録です.現象の要因は(一応)判明しています.

現象

自作モジュールのテストのところで作成したスクリプトを,次のように変更したとします. 変更したのは先頭の3行(紫色の部分)です. シェルを経由して実行する代わりに,guileに直接実行させています.
#!/usr/bin/guile \
-e '(@@ (hello) main)' -s 
!#

;; hello-ms.scm

(define-module (hello)
  #:export (hello-everybody hello-somebody))

(define (hello-everybody names)
  (for-each hello-somebody names))

(define (hello-somebody name)
  (display (make-hello name))
  (newline))

(define (make-hello name)
  (string-append "*** Hello," name "!!! ***"))

;; テスト用手続き
(define (main args) 
  (format (current-output-port) 
          "test for make-hello:~A\n"
          (make-hello "maker"))
  (display "test for hello-somebody:\n") 
  (hello-somebody "somebody")
  (display "test for hello-everybody:\n") 
  (hello-everybody '("Alice" "Bob" "Carol" "David")))
残念ながら,このスクリプトはうまく動きません.
$ ./hello-ms.scm 
ERROR: In procedure read:
In procedure scm_i_lreadparen: #<unknown port>:1:5: end of file
ちなみに,ターミナル上で上記の2行分と同じコマンドを実行するとうまく動きます.
$ guile -e '(@@ (hello) main)' -s hello-ms.scm 
test for make-hello:*** Hello,maker!!! ***
test for hello-somebody:
*** Hello,somebody!!! ***
test for hello-everybody:
*** Hello,Alice!!! ***
*** Hello,Bob!!! ***
*** Hello,Carol!!! ***
*** Hello,David!!! ***

原因

▹ うまく動かない原因は,メタスイッチの2行目の掟のためです. 2行目の掟によれば,2行目の中の空白は,たとえクォートの中だったとしても, 引数の区切りとして機能します.従って,上記の2行目にある
'(@@ (hello) main)'
は,1つの引数としてではなく,
'(@@   と   (hello)   と   main)'
という3つの引数として処理されてしまいます. 結局,上記のシェバン行と2行目は, 以下の実行例に示すようなコマンドを実行していることになります.
$ guile -e "'(@@" '(hello)' "main)'" -s hello-ms.scm
ERROR: In procedure read:
In procedure scm_i_lreadparen: #<unknown port>:1:5: end of file
全く同じエラーメッセージが表示されていることが分かるでしょう.

では,どうしたらよいか. もっとも真面目な解決策はシェルを経由して実行することです(つまり,元に戻すことです).シェルを経由すれば2行目の掟なんか無視できます. もう一つの方法は,2行目の中の空白を以下のようにエスケープすることです.
#!/usr/bin/guile \
-e (@@\ (hello)\ main) -s 
!#

;; hello-ms.scm
   
      ...... (以下同様) ......

ただし,クォートは外します.2行目はguileによって処理されるのですが, クォートを付けているとエラーが発生します(guileが2行目をどう処理しているのかについて,厳密な詳細は分かりません). 以下は変更後の実行結果です.
$ ./hello-ms.scm
test for make-hello:*** Hello,maker!!! ***
test for hello-somebody:
*** Hello,somebody!!! ***
test for hello-everybody:
*** Hello,Alice!!! ***
*** Hello,Bob!!! ***
*** Hello,Carol!!! ***
*** Hello,David!!! ***

現象記録:外部ファイルのロードとdefine-moduleの関係性

▹ 以下は現象記録です.詳しい仕組みや理由は分かっていません.

▹ あるモジュールの手続きを引き抜き方式で実行しようとするとき, 外部ファイルで定義した束縛はロードするタイミングによって, 使えなかったり使えたりします.

現象 その1

▹ 以下では,次の2つのプログラムを使って実験します,
#!/usr/bin/env sh
exec guile -l sub.scm -e '(@@ (mymod) main)' -s "$0" "$@"
!#

;; mymod.scm

(define-module (mymod)
  #:export (hello-somebody))

(define (hello-somebody name)
  (display (string-append "Hello," name "!"))
  (newline))

;; テスト用手続き
(define (main args) 
 (display "test for hello-somebody:\n") 
 (hello-somebody "somebody")
 (display "test for hello-somebody with add-prefix:\n") 
 (hello-somebody (add-prefix "somebody"))
)
;; sub.scm

(define (add-prefix str)
  (string-append "prefix:" str))
mymod.scm は,mymod モジュールの main 手続きを実行しようとしていて, その main 手続きの中で add-prefix 手続き(紫色)を使っています. ただし,add-prefix は sub.scm の中で定義されています. そこで,-l スイッチを使って sub.scm をロードしています.

▹ 残念ながら,これはうまく動きません. 論理的な観点から言えば,動いて欲しいところです. なぜなら,-l スイッチで sub.scm をロードした時点で add-prefix の束縛が確立していると思えるからです.でも,動きません.以下は実行結果です.
$ ./mymod.scm
test for hello-somebody:
Hello,somebody!
test for hello-somebody with add-prefix:
Backtrace:
      ...... バックトレースのメッセージ ......
Unbound variable: add-prefix
main 手続きの途中までは実行できていて,add-preix を呼び出す時点で「add-prefixは未束縛」といったエラーが発生しています.

現象 その2

▹ そこで,-l スイッチでロードするのは諦めて,load 手続きを使ってロードしてみます.ただし,sub.scm をguileに発見させるために %load-path にカレントディレクトリを追加します.変更後の mymod.scm は次のようになります. 紫色は変更箇所を示しています.
#!/usr/bin/env sh
exec guile -e '(@@ (mymod) main)' -s "$0" "$@"
!#

;; mymod.scm

(add-to-load-path (getcwd))
(load "sub.scm")

(define-module (mymod)
  #:export (hello-somebody))

      ...... (以下同じ) ......

残念ながら,これも動きません.先ほどとまったく同じ結果になります. 実行結果は省略します.

現象 その3

▹ 次に,laod 手続きを eval-when してみます. 変更後の mymod.scm は次のようになります. 紫色は変更箇所を示しています. ちなみに,add-to-load-path は,それ自身の中で eval-when しています.
#!/usr/bin/env sh
exec guile -e '(@@ (mymod) main)' -s "$0" "$@"
!#

;; mymod.scm
(add-to-load-path (getcwd))
(eval-when (expand load eval)  
           (load "sub.scm"))

(define-module (mymod)
  #:export (hello-somebody))

      ...... (以下同じ) ......

残念ながら,これも動きません.先ほどとまったく同じ結果になります. 実行結果は省略します.

現象 その4

▹ そこで,load 手続きを define-module のあとに実行してみます. 変更後の mymod.scm は次の通りです.
#!/usr/bin/env sh
exec guile -e '(@@ (mymod) main)' -s "$0" "$@"
!#

;; mymod.scm

(define-module (mymod)
  #:export (hello-somebody)
  )

(add-to-load-path (getcwd))
(load "sub.scm")

      ...... (以下同じ) ......

これはうまく動きます.
$ ./mymod.scm
test for hello-somebody:
Hello,somebody!
test for hello-somebody with add-prefix:
Hello,prefix:somebody!

▹ ロードのタイミングによって外部の束縛が使えたり使えなかったりするのは, ちょっと不思議な感じがします.コンパイルの仕組みを詳しく理解しない限り, 以上の状況を理解することはできないように思います.

現象記録:use-modulesとdefine-moduleの実行順序

▹ 以下は現象記録です.詳しい仕組みや理由は分かっていません.

▹ 前節の load 手続きと define-module の同じ関係性が,use-modules と define-moduleの間にも成り立ちます. つまり,use-modules を define-module の前に実行してもモジュール内の束縛はうまくロードされないようです.use-modules は define-module のあとに実行しなければなりません

現象 その1

▹ 前に示した次のプログラムを使って実験します. ただし,load 手続きの変わりに use-modules を使用します. さらに,submod.scm のほうもモジュール化しています(ファイル名も変更しています).以下の紫色は,前に示したプログラムに対する主な変更点を示しています.
#!/usr/bin/env sh
exec guile -e '(@@ (mymod) main)' -s "$0" "$@"
!#

;; mymod.scm

(add-to-load-path (getcwd))
(use-modules (submod))

(define-module (mymod)
  #:export (hello-somebody)
  )

(define (hello-somebody name)
  (display (string-append "Hello," name "!"))
  (newline))

;; テスト用手続き
(define (main args) 
 (display "test for hello-somebody:\n") 
 (hello-somebody "somebody")
 (display "test for hello-somebody with add-prefix:\n") 
 (hello-somebody (add-prefix "somebody"))
)
;; submod.scm

(define-module (submod)
  #:export (add-prefix))

(define (add-prefix str)
  (string-append "prefix:" str))
残念ながら,これは動きません.
$ ./mymod.scm 
test for hello-somebody:
Hello,somebody!
test for hello-somebody with add-prefix:
Backtrace:
      ...... バックトレースのメッセージ ......
Unbound variable: add-prefix

現象 その2

▹ 次に,use-modules を define-module のうしろに実行します. 変更後のプログラムは次の通りです.紫色は変更箇所を示しています.
#!/usr/bin/env sh
exec guile -e '(@@ (mymod) main)' -s "$0" "$@"
!#

;; mymod.scm

(define-module (mymod)
  #:export (hello-somebody)
  )

(add-to-load-path (getcwd))
(use-modules (submod))

      ...... (以下同じ) ......

これはうまく動きます.
$ ./mymod.scm 
test for hello-somebody:
Hello,somebody!
test for hello-somebody with add-prefix:
Hello,prefix:somebody!
(おしまい)