Syntax highlighter

2014-10-24

SRFI-35/36の紹介

(LISP Library 365参加エントリ)

SRFI-35は例外を、SRFI-36はI/O例外を規定するSRFIです。ここで言う例外とは例外オブジェクトのことです。例外はR6RSに取り入れられ、R7RSで取り除かれたという悲しい歴史を持ちます。
R6RSで定めている例外とほぼ同じなのですが、conditionがマクロだったり検査用の手続きがあったりと多少趣が違います。以下はSRFI-35で定められているものの一部です。
(define c (condition (&serious)
                     (&message (message "message of this condition"))))

(condition? c)
;; -> #t

(condition-ref c 'message)
;; -> "message of this condition"

(extract-condition c &serious)
;; -> instance of serious condition

(condition-has-type? c &message)
;; -> #t
SRFI-36はSRFI-35を元にしてI/O例外を定めています。R6RSに慣れ親しんでいる方であればいくつかの例外には見覚えがあるでしょう。&i/o-malformed-filename-errorなどR6RSには採用されなかった例外もあります。

また、SRFI-36では標準の手続きがどの例外を投げるべきかということも定めています。例えば、call-with-input-file&i/o-filename-errorもしくはそのサブタイプの例外を投げなければならないとしています。

ちなみに、これらのSRFIはSRFI-34と同時に例外に関するSRFIとして出されたみたいです(参考)。さらにSRFI-35の議論で慣例的に例外型の名前に付けられる&説明もあったりと、歴史的経緯を眺めるのも面白いです。

個人的にはこれらのSRFIはR7RSに入るべきだったと思うのですが、まぁ、世の中そう上手く行かないものです。(R7RSをR5RS処理系がR8RSに移行する際の緩衝材的な位置づけとみれば*1納得できなくもないのですが、それはそれでどうかなぁとも思ったり…)

今回はSRFI-35/36を紹介しました。


*1: R7RSにそれとなくそんな感じの文言があったりします(深読みしすぎ)
However, most existing R5RS implementations (even excluding those which are essentially unmaintained) did not adopt R6RS, or adopted only selected parts of it.

2014-10-18

Weak hashtable



こういったいきさつがあって、シンボル(とその他2つ)をGC対象にしたのだが、どうもweak hashtableの実装がまずいらしく、多少改善された程度のメモリー消費量にしかなっていない。とりあえず実装を見直してみると、weah hashtableとhashtableの実装上にweak boxを載せてそれっぽく見せているのだが、どうもこのweak boxが消えないのでエントリー数が増え続けるという感じみたいである。一応キーが回収された際にエントリーを消すような小細工がされてはいるのだが、なぜか上手く動いていない感じである。

どうするか?というのがあるのだが、解決方法は2つくらい案があって、
  1. Weak hashtableを別実装にしてしまう。
    hashtable-ref等の手続きを共有して使えないのだからコードを共有する必要があまりなくね?という発想。
  2. Hashtable側のAPIをリッチにする
    バケツの割り当て等を外部から操作できるようにしてしまえば何とかなりそうじゃね?という発想。
1は多分楽なんだけど、後々のことを考えると(主にメンテ)ある程度のコードは共有しておきたい気もする。2は茨の道なんだけど(パフォーマンスとか)、上手く作ればメンテが楽になりそう。

どちらの道をとったとしても、weak boxの扱いをどうするかという問題は残るのでこれはちと考える必要がある。

追記(2014年10月18日)
よく考えてみればエントリー数が減らないのはweak hashtableの値がGCされた際に対象のエントリーを削除しないのが問題なので、値がGCされた際に対象のエントリーを削除するように変更した(後方互換を保つためにフラグを一個追加した)。なんとなく、動いているっぽいので当面はこれでよしとしよう。

2014-10-16

R5RS auxiliary syntaxes

Recently, there was the post which introduced SCLINT on reddit/lisp_ja: #:g1: SCLINTの紹介. SCLINT is a lint-like program for Scheme written in R4RS Scheme. (Interestingly, Sagittarius could run this without any modification :) but it's not the topic for now.). So for some reason, I've tried to run on R7RS implementations using (scheme r5rs) library. I don't know how this idea came up, but the result was rather interesting.

So I've prepared the following script;
(import (only (scheme base) error cond-expand include)
        (scheme process-context) 
        (scheme r5rs))

(cond-expand
 (foment
  (include "\\cygwin\\tmp\\sclint09\\pexpr.scm"
           "\\cygwin\\tmp\\sclint09\\read.scm"
           "\\cygwin\\tmp\\sclint09\\environ.scm"
           "\\cygwin\\tmp\\sclint09\\special.scm"
           "\\cygwin\\tmp\\sclint09\\procs.scm"
           "\\cygwin\\tmp\\sclint09\\top-level.scm"
           "\\cygwin\\tmp\\sclint09\\checkarg.scm"
           "\\cygwin\\tmp\\sclint09\\sclint.scm"
           "\\cygwin\\tmp\\sclint09\\match.scm"
           "\\cygwin\\tmp\\sclint09\\indent.scm"))
 (else
  (include "/tmp/sclint09/pexpr.scm"
           "/tmp/sclint09/read.scm"
           "/tmp/sclint09/environ.scm"
           "/tmp/sclint09/special.scm"
           "/tmp/sclint09/procs.scm"
           "/tmp/sclint09/top-level.scm"
           "/tmp/sclint09/checkarg.scm"
           "/tmp/sclint09/sclint.scm"
           "/tmp/sclint09/match.scm"
           "/tmp/sclint09/indent.scm")))

(sclint (cdr (command-line)))
The original article is using load but foment complained that scling is not defined. So above is using include instead (even though I've used include yet Foment raised errors...). And execute it with 4 implementations, Chibi, Foment, Gauche and Sagittarius (both 0.5.8 and HEAD). The result was only Gauche could execute as I expected. Foment raised 2 errors (I don't know why), Chibi and Sagittarius raised an error with unbound variable else.

Apparently, the (scheme r5rs) library does't export 4 auxiliary syntaxes; =>, else, unquote and unquote-splicing; and one syntax (or macro transfomer) syntax-rules. I believe the last one is just missing but the others are bit more complicated.

The only purpose of (scheme r5rs) is to provide an easy way to import the identifiers defined by R5RS; it does not give you an R5RS emulator.
http://lists.scheme-reports.org/pipermail/scheme-reports/2014-October/004267.html
I thought the purpose is making sure R5RS scripts can be executed on R7RS implementations but seems not. Then question is that if the 4 auxiliary syntaxes are bound in R5RS. If I see R5RS then indeed it doesn't define them explicitly, however this post indicates they are;
R5RS 3.1.
> An identifier that names a type of syntax is called
> a syntactic keyword and is said to be bound to that syntax.

R5RS 7.1.
> <syntactic keyword> -> <expression keyword>
> | else | => | define
> | unquote | unquote-splicing

"else" is syntactic keyword, and syntactic keyword is bound to syntax.
Therefore, "else" is bound.
http://lists.scheme-reports.org/pipermail/scheme-reports/2014-October/004265.html
I think this interpretation is rather rational so I've added those auxiliary syntaxes to export clause of (scheme r5rs). However, I can also think of the objection that could be something like this; being bound to a syntax doesn't mean they were bound in R5RS (or its environment).

Well, I've already decided to add them so I don't have much option about this anymore but it would be convenient if legacy R5RS scripts can be executed on R7RS implementations with just importing
(scheme r5rs).

2014-10-10

SRFI-34の紹介

(LISP Library 365参加エントリ)

SRFI-34はプログラムのための例外ハンドリングです。具体的にはwith-exception-handlerguardraiseです。

使い方はSRFIに山ほどあるのと、R6RS以降のSchemeでは標準になっているので、このSRFIと標準との定義の違いをあげます。
(call-with-current-continuation
 (lambda (k)
   (with-exception-handler (lambda (x)
                             (display "something went wrong")
                             (newline)
                             'dont-care)
     (lambda ()
       (+ 1 (raise 'an-error))))))
上記の動作はR6RSではエラーとして定義されていますが、このSRFIでは未定義です。これは例外が継続可能かどうかという部分に関わってきます。参照:Is the condition continuable?

SRFIの紹介から多少逸脱するのですが、R6RS及びR7RSではguardelseを持っていなかった場合にraise-continuableで例外を伝播させると定義されています。どういったいきさつがあったのかはR6RSのMLを探っていないので分からないのですが、これは以下のような場合に困ることになるかと思います。
(import (rnrs))

(define-condition-type &connection &error
  make-connection connection-error?)
  
(with-exception-handler
 ;; maybe you want to return if the condition is
 ;; warning
 (lambda (e) (display "condition is &error") (newline))
 (lambda ()
   (let retry () 
     ;; if it's connection error, then retry at this point.
     ;; if other point, it must be a fatal error.
     (guard (e ((connection-error? e)
                (display "connection error! retry") (newline)
                (retry)))
       ;; assume this is fatal
       (error 'connection "connection error")))))
コーディングバグといえばそれまでなのですが、投げられた例外が継続可能かどうかというのは例外を投げた手続きによって決定されるといのは一見スマートな解決案に見えて実際にはそうでもないという一例になるかと思います*1

今回はSRFI-34を紹介しました。

*1: 例えば投げられた例外が&seriousを含む&warningのようなものだとwarning?でチェックすると嵌ります。逆に&seriousを含むものでもraise-continuableで投げられた場合は継続可能になる等。個人的には筋が悪いなぁと思っています。

2014-10-03

SRFI-31の紹介

(LISP Library 365参加エントリ)


SRFI-31は再帰的評価のための特殊フォームrecです。正直それが何を意味しているのかよく分からないのですが、Rationaleにはこんなことが書いてあります。
  • 単純で直感的かつ数学的記述に近い表記
  • 一般的な再帰の許可
  • 手続き的にならない
このSRFIは上記を満たすものみたいです。

使い方は以下のようです。
(define F (rec (F N)
              ((rec (G K L)
                 (if (zero? K) L
                   (G (- K 1) (* K L)))) N 1)))
上記はfactを定義しています。これがどれくらい数学的記法に近いかは門外漢の僕には分かりかねるのですが、見たところ単に名前付きlambdaを作っているようです。(実際named-lambdaという言葉がRationaleに出てきます。)

定義を見ればrecは単にletrecに変換するマクロであることが分かります。
;; from reference implementation of this SRFI
(define-syntax rec
  (syntax-rules ()
    ((rec (NAME . VARIABLES) . BODY)
     (letrec ( (NAME (lambda VARIABLES . BODY)) ) NAME))
    ((rec NAME EXPRESSION)
     (letrec ( (NAME EXPRESSION) ) NAME))))
自分自身を参照するlambdaを束縛を作ることなく書く必要がある場合には便利かもしれません。

今回はSRFI-31を紹介しました。

2014-10-02

MQTT client and broker

I've implemented MQTT client and broker on Sagittarius. Now feeling like the broker implementation is compliant the specification (as far as I can see, there is probably a bug(s) though), so let me introduce a bit. APIs would be changed in 0.5.9 and probably I wouldn't write document until I think it can be fixed (nearest would be after 0.5.10).

If you don't need anything, even authentication, then the broker can be written like this;
(import (rnrs) (net mq mqtt broker))

(define broker (make-mqtt-broker "5000"))

(mqtt-broker-start! broker)
With this, broker runs on port 5000. When broker is ready then next step is client.

The basic functions for client are subscribing and publishing. Subscribing would be like this;
(import (rnrs) (net mq mqtt client))

(let ((conn (open-mqtt-connection "localhost" "5000")))
  (mqtt-subscribe conn "topic" +qos-exactly-once+
                  (lambda (topic payload)
                    (get-bytevector-all payload)))
  (let loop ()
    (let ((r (mqtt-receive-message conn)))
      (display r) (newline)
      (unless (eof-object? r)
        (loop))))
  (mqtt-unsubscribe conn "topic")
  (close-mqtt-connection! conn))

Subscribe procedure, currently, takes 4 arguments, MQTT connection, topic filter, QoS level and callback procedure. The callback procedure takes 2 arguments, topic name and payload. Payload is a binary input port. For now, we don't provide daemon thread for callback so users need to explicitly receive messages.

Publishing messages would be like this;
(import (rnrs) (net mq mqtt client))

(let ((conn (open-mqtt-connection "localhost" "5000")))
  (mqtt-publish conn "topic" (string->utf8 "Hello MQTT")
  :qos +qos-at-least-once+)
  (mqtt-publish conn "topic" #vu8())
  (close-mqtt-connection! conn))
Publish procedure, currently, requires 3 arguments and also can take some keyword arguments to specify how to publish such as QoS and retain. The application message must be a bytevector so that MQTT requires it to be binary data. Publishing empty bytevector would send empty payload.

Followings are some of design rationale (please add 'currently' before read).

[Socket connection]
Broker creates a thread per connection instead of dispatching with select (this is sort of limitation of underlying (net server) library). By default, max connection number is 10. If this is 2 then you can do private conversation and if it's 1 then you can be alone...

[Session control]
Managing session is done by one daemon thread which is created when broker is created. Default interval period it 10 second. So even if client keep-alive is 5 seconds and it idled for 6 seconds then send something, it can still be treated as a live session. Session could have had own timer however I don't have any lightweight timer implementation other then using thread and making thread is sort of expensive on Sagittarius. So I've decided to manage it by one thread.

[Client packet control]
Even though client needs to receive message explicitly however there is an exception. That is when server published a message to client and right after that client send control packet like subscribe. In that case client first consume the published message handling with given callback then sends control packet.

[QoS control for exactly once]
Broker publishes received message after PUBCOMP is sent. MQTT spec says it can initiate delivering after receiving PUBLISH.

[Miscellaneous]
When client subscribes a topic and publishes a message to the same topic, then it would receive own message. Not sure if this is correct behaviour...

Pointing a bug/posting an opinion would be grateful!

2014-09-30

Timer

When I write asynchronous script, sometimes I want to a timer so that I can invoke some procedure periodically or so. So I've looked at POSIX's timer_create and Windows' CreateWaitableTimer. Then found out both needs some special treatment. For example, POSIX timer_create requires signal handling which is lacking on Sagittarius. (Honestly, I've never properly understood how signal masking works...)

So I've wrote sort of mimic code with thread.
(import (rnrs) (srfi :18))

;; simple timer
(define-record-type ( make-timer timer?)
  (fields (immutable thread timer-thread))
  (protocol (lambda (p)
              (lambda (interval thunk)
                (p (make-thread 
                    (lambda ()
                      (let loop ()
                        (thread-sleep! interval)
                        (thunk)
                        (loop)))))))))

(define (timer-start! timer) (thread-start! (timer-thread timer)) timer)
(define (timer-cancel! timer) (thread-terminate! (timer-thread timer)))

;; use it
(define t (timer-start! (make-timer 2 (lambda () (print "It's time!!")))))

(define (heavy-to-do)
  (thread-sleep! 5)
  (print "It was heavy!"))
(heavy-to-do)
Above prints It's time!! twice then finish heavy-to-do. Now I'm wondering if this is enough or not. Without deep consideration, I've got couple of pros and cons with this implementation.

[Pros]
  • Easy to implement and could be portable.
  • Asynchronous.
[Cons]
  • Could be expensive. (Making thread is not cheap on Sagittarius)
  • Timer can't change parameters which is thread local.
I think above points are more like how we want it to be but it seems better that timer runs the same thread for me. Now, look at both Windows and POSIX timer APIs. Seems both can take callback function. However on POSIX, if I use SIGEV_THREAD then it would create a new thread (it only says "as if" so may not). And not sure if Sagittarius can call a procedure using parent thread's VM without breaking something. So, it's most likely not an option...

Then Windows. SetWaitableTimer can also take a callback function. And according to MSDN, the callback function will be executed on the same thread.
The completion routine will be executed by the same thread that called SetWaitableTimer. This thread must be in an alertable state to execute the completion routine. It accomplishes this by calling the SleepEx function, which is an alertable function.
Using Waitable Timers with an Asynchronous Procedure Call
Now, I'm not sure what's alertable state exactly means. Seems the target thread should be sleeping and if so, sucks...

Hmmmm, it may not an easy thing to do.

2014-09-27

SRFI-30の紹介

(LISP Library 365参加エントリ)

SRFI-30は複数行のコメントを扱うためのSRFIです。説明するよりコードを見た方が早いので、まずはコードです。
#|
This is the SRFI
  #|
    Nested comment is also okay (unlike C)
  |#
|#
この形式のコメントはR6RS以降のSchemeからサポートされています。SRFIが標準に格上げされたものの一つともいえます。(逆に言うとR5RS以前は複数行コメントは標準ではなかったという・・・)

実はこのSRFIで定義されているBNFをよくみると入れ子のコメントは扱えないようになっています。これはSRFIが決定されてからの議論で修正案が出ていて、参照実装をBNFにするとこうなるみたいです。
<comment> ---> ; <all subsequent characters up to a line break>
             | <srfi-30-comment>

<srfi-30-comment> ---> #| <srfi-30-comment-constituent>* |* |#

<srfi-30-comment-constituent> ---> #* <srfi-30-ordinary-char>
                                 | |* <srfi-30-ordinary-char>
                                 | #* <srfi-30-comment>

<srfi-30-ordinary-char> ---> <any character but # or |>

どうでもいい小話なのですが、この形式のコメントを使うとたまにEmacsがおかしなシンタックスハイライトをするようになるので個人的には多用していないです。求む回避方法。

今回はSRFI-30を紹介しました。

2014-09-26

簡単なサーバプログラム用フレームワーク

フレームワークというほどたいそうなものでもないのだが、何かしらサーバを書く際に唱えるおまじない部分を勝手にやってしまおうというもの。そろそろちょっとしたサーバがあると便利だよなぁという願望のもとえいや!っと書いてみた。

とりえあず、エコーサーバはこんな感じで書ける。
(import (sagittarius socket) (net server))

(define (handler socket)
  (let ((bv (socket-recv socket 255)))
    (socket-send socket bv)))

;; creates server object
(define server (make-simple-server "5000" handler))

;; start!
(start-server! server)
handlersocket-acceptで作られたソケットを受け取る。ちなみにフレームワーク側でソケットは閉じてくれるので明示的に閉じる必要はない。(閉じても特に痛くはないが)

これだけだと特にありがたみもないのだが、オプション引数で設定を取ることができる。こんな感じ。
(import (sagittarius socket) (net server))

(define (handler socket)
  (let ((bv (socket-recv socket 255)))
    (socket-send socket bv)))

;; server has daemon thread which watches :shutdown-port
;; for shutdown the server.
;; exception handler will be invoked when handler
;; raises an error.
;; given max-thread > 1 makes the server creates a
;; thread for each request. using (util concurrent)
(define config (make-server-config :shutdown-port "8888"
                                   :exception-handler (lambda (e s) (print e))
                                   :max-thread 10))

(define server (make-simple-server "5000" handler config))

(start-server! server)
上記の設定だと、最大スレッド数10、サーバを閉じる用のポート8888(この設定だとつないだ瞬間落とす)に例外ハンドラという感じになる。これ以外にもTLSソケットとかある。

(どうでもいいdesign rationale) なんでキーワード引数じゃなくconfigオブジェクトにしたかというと、こうしておくと拡張が楽かなぁという希望的観測があったからだったりする。例えばHTTPサーバを書こうと思った場合に継承してなんとかできないかぁという。どうなるかは実際に拡張を書いて見ないと分からないという・・・

ついでといっては何ではあるのだが、これを書くために(util concurrent)というJavaのjava.lang.concurrentにインスパイアされたライブラリを書いたりした(ぶっちゃけ名前だけ・・・中身は性質上にても似つかないという・・・)。中身はSRFI-18があれば限りなくR6RSポータルになっているが、SRFI-18をサポートしてる処理系の方が少ないという罠もある。

2014-09-20

SRFI-29の紹介

、(LISP Library 365参加エントリ)

SRFI-29は名前の通りローカライズ(localiseのいい訳募集)のためのSRFIです。世の中英語で書いておけば大体OKな風潮ではありますが、エラーメッセージ等の言語を変更したい場合などに使える(かもしれない)ものです。

言語や地域の設定は以下のようにします。
(import (srfi :29))

(current-language)
;; returns current language (e.g. en)

(current-language 'fr)
;; sets language to French

(current-country)
;; returns current country (e.g. us)

(current-country 'nl)
;; sets country to Netherlands

(current-locale-details)
;; returns list of details (e.g. (utf-8))

(current-locale-details '(utf-8))
;; sets details
処理系によってはこの辺の情報を環境変数からとってきたりもします。(e.g. Gauche、Sagittarius)

言語ごとにメッセージを設定するにはdeclare-bundle!を使います。store-bundle!及びload-bundleは可能であればメッセージの設定を永続化及び読み込みを行います。
(let ((translations
       '(((en) . ((time . "Its ~a, ~a.")
                (goodbye . "Goodbye, ~a.")))
         ((fr) . ((time . "~1@*~a, c'est ~a.")
                (goodbye . "Au revoir, ~a."))))))
  (for-each (lambda (translation)
              (let ((bundle-name (cons 'hello-program (car translation))))
                (if (not (load-bundle! bundle-name))
                    (begin
                     (declare-bundle! bundle-name (cdr translation))
                     (store-bundle! bundle-name)))))
             translations))
上記はSRFIの例からですが、load-bundlebundle-name(この場合はhello-program)の読み込みを試み、失敗すればdeclare-bundle!で設定、store-bundle!で永続化を試みます。実際に設定されたメッセージを取得するにはlocalized-templateを使います。

このSRFIではformatの拡張も定義されていて、~[n]@*n番目に与えられた引数を参照します。例えば以下のような手続きを定義します(SRFIの例です)
(define localized-message
  (lambda (message-name . args)
    (apply format (cons (localized-template 'hello-program
                                            message-name)
                        args))))

(let ((myname "Fred"))
  (display (localized-message 'time "12:00" myname))
  (display #\newline)

  (display (localized-message 'goodbye myname))
  (display #\newline))
;; If current language is 'fr' then
;; prints 'Fred, c'est 12:00.'
;; and    'Au revoir, Fred.'
多少端折った説明になったのは僕自身はこのSRFIを使っていないので、今一使いどころを把握していないからなのですが、まぁ、こういうものだという部分は伝えられたかと思います。

今回はSRFI-29を紹介しました。

2014-09-12

SRFI-28の紹介

(LISP Library 365参加エントリ)

SRFI-28は基本な整形文字列です(訳難あり)。CLでおなじみのformat手続きをSRFIで提供するというものです。ただし、使用できる置換文字は~a~sのみの非常に基本的なものです。(名前どおりですね)

使い方は以下の通り。
(format "~a~%" Hello world)
;; -> "Hello world"
CLと違い、format文字列の前に#tを入れてもエラーになります。~adisplay~swriteが使われます。

参照実装では与えられたformat-stringをリストにしていますが、処理系によっては文字列の参照はO(1)で行われるのでstring-refで行った方が高速になるかもしれません。メモリスペースも多少節約できます。ひょっとしたら処理系によっては文字列は文字のリストでstring->listが時間、空間ともにO(1)で終わるものもあるかもしれません(少なくともR5RS以降では規格違反ではありますが)。
 
今回はSRFI-28を紹介しました。

2014-09-10

Is the condition continuable?

Since R6RS, Scheme has continuable exception which I think a good thing so that libraries may choose its behaviour when warning happened. R6RS has even the condition type &warning to let users know this. A bad thing is that, there is no way to know how the condition is raised. Think about this piece of code.
(import (scheme base) (scheme write))

(define-syntax safe-execute
  (syntax-rules ()
    ((_ expr ...)
     (with-exception-handler
      (lambda (e) #t)
      (lambda () expr ...)))))

(guard (e (else (display e) (newline) (raise e)))
  (safe-execute (raise "error huh?")))
The safe-execute just wraps given expression with with-exception-handler so that it can propagate non continuable condition to caller but can continue the process if the condition is just an warning. Now the problem is that, it doesn't propagate the raised condition as it is but modifies to something &non-continuable. For example, if you write the same code in R6RS and run it on Chez Scheme then the original condition's information disappears. (not sure if this is a bug of Chez though.)

So to keep my mind calm and mental health as healthy as possible, it is better to detect if the given condition is raised by raise or raise-continuable. However there is no way to do it with portable way. If you are an R6RS user, you may have slight hope, which is checking &warning or &non-continuable. If a script just wants to tell you an warning, then it usually raises an &warning condition. Thus checking this would make you a bit happy. Although, raise can take any Scheme object including &warning so this solution depends on the behaviour or philosophy of libraries. Moreover, guard without else needs to re-raise the caught condition with raise-continuable. This may cause something unexpected result if such a guard expression is wrapped by with-exception-handler.

Now, look at R7RS. It becomes totally hopeless. It doesn't have condition type, so the only hope that R6RS has is gone. The behaviour of all of related procedures and macro are the same as R6RS and it doesn't provide a procedures which can check if the condition is continuable or not, either. So this is totally depends on implementations.

If this is the case, then how the implementations behave. I've tested above piece of code with 4 implementations, Sagittarius, Chibi, Gauche and Foment. The results are followings;
  • Sagittarius - compounded the condition with &non-continuable
  • Chibi - changed condition with "exception handler returned" message (I guess)
  • Gauche - didn't print message at all (bug?)
  • Foment - propagated original condition.
For Gauche, if I changed raise to error it did print an error object. So it may not allow user to raise non condition object.

I'm not yet sure how important handling runtime exception is on Scheme. I've never written code that considers error other than just catching and logging. So this may be a trivial case.

2014-09-09

FFIバインディングは人間が手で書くものではない

というのを1年前に掲げて絶賛放置していたプロジェクトを何とか動くところまで持っていった。

https://github.com/ktakashi/sagittarius-ffi-helper

前は確かGTkか何かのバインディングを書くのが嫌でこれ作ったんだけど、GTkのバインディングを書く方を放置プレイに追い込んでしまったので同じく放置されていた。っで、最近DB2を扱う必要が出てきて、わざわざSQL走らせるのにGUI起動視するとか馬鹿らしいのでDBDが要るということから、あれよあれよと動くまで持っていった。

0.5.7で動くんだけど、生成されるコードは0.5.8用という仕様。理由は0.5.7のFFIは共用体のサポートがないからだったりする。逆に言えば、共用体がないのであれば、0.5.7でも動く(はず)。

簡単な仕様としては、マクロで定義された定数、typedef、構造体、共用体、列挙子なんかはそれなりに出力される。関数は可変引数にまだ対応してないのでそれがあると死ぬ。genbindが生成用のスクリプトで、合計で4つのファイルが生成される。構造体、共用体、列挙子とtypedefは定義と同名で、定数と関数はScheme的な名前に変更される。オプションで制御可能。

中身が非常に汚いので、綺麗にリファクタリングしてくれる奇特な方がいたら大歓迎。

これからはFFIバインディング書くのが楽になりそうな雰囲気が出てきた。(っが何時までたっても必要ドリブンなので、気の向くまま何かを書くということはないが・・・)

2014-09-06

syntax-caseで嵌った話

前にも似たような経験をしてTwitterに投げただけでまとめてない気がしたので書いておく。

問題になるのは以下のようなコード。
(import (rnrs))

(define-syntax define-foo
  (syntax-rules ()
    ((_ name)
     (begin
       (define name 'foo)
       (letrec-syntax
           ((gen (lambda (x)
                   (syntax-case x ()
                     ((k proc)
                      (with-syntax ((bar (datum->syntax #'k 'bar)))
                        #'(define bar proc))))))
            (get (syntax-rules ()
                   ((_) (gen (lambda (o) o))))))
         (get))))))

(let ()
  (define-foo name)
  (bar 'a))
期待するのはlet内のbarが参照可能であることなのだが、実際にはこれは見えない。Sagittarius類似コードを書いていたので「またマクロのバグか」と思っていたのだが、他の処理系でもエラーになる。自分の処理系ほど信じていないという切ない話ではあるのだが、よくよく考えればエラーになるのが筋なのである。

R6RSの構文オブジェクト周りを理解するのは骨が折れるのだが、今回の話はdatum->syntaxなのでその定義を見てみよう。
Template-id must be a template identifier and datum should be a datum value. The datum->syntax procedure returns a syntax-object representation of datum that contains the same contextual information as template-id, with the effect that the syntax object behaves as if it were introduced into the code when template-id was introduced.
 datum->syntaxによって生成される構文オブジェクトはtemplate-idと同じコンテキストの構文オブジェクトになる。これを踏まえて上記のコードをかなり目を凝らして見てみると、datum->syntaxのコンテキストとlet内のbarのコンテキストは違うように見える。letで作成されるコンテキストA、define-foo内のマクロ作成されるコンテキストBという風に見る(のだと思う)。AはBの外側のコンテキストと取れる。これと健全性の定義を照らし合わせてみる。
A binding for an identifier introduced into the output of a transformer call from the expander must capture only references to the identifier introduced into the output of the same transformer call. A reference to an identifier introduced into the output of a transformer refers to the closest enclosing binding for the introduced identifier or, if it appears outside of any enclosing binding for the introduced identifier, the closest enclosing lexical binding where the identifier appears (within a syntax <template>) inside the transformer body or one of the helpers it calls.
 これが今一理解できてないのではあるが、外側のコンテキストは内側のコンテキストを参照できないと読めなくもない。(内側のコンテキストが先に作られるので、先にできた束縛を後から作られたものが参照可能だと健全性が壊れるような気がする。) コードをこう書き換えると分かりやすいかもしれない。
(import (rnrs))

(define-syntax define-foo
  (syntax-rules ()
    ((_ name)
     (begin
       (define name 'foo)
       (define-syntax gen 
         (lambda (x)
           (syntax-case x ()
             ((k proc)
              (with-syntax ((bar (datum->syntax #'k 'bar)))
                #'(define bar proc))))))
       (define-syntax get
         (syntax-rules ()
           ((_) (gen (lambda (o) o)))))
       (get)))))

(let ()
  (define-foo name)
  (bar 'a))
これなら、上記の解釈が正しいと仮定すると、getとgenによって作られたbarがlet内にあるbarとは別物に見える。

この辺に詳しい人の突込みが待たれるところである。

捕捉
ちなみに、Sagittariusではletを取り除いてやると動いてしまうのだが、これは上記の解釈によればバグである。 っが、今のところ直す気はない。(MPが足りてない)

2014-09-03

Resolving let-method (2)

I've got sharp comment on previous article. Apart from the comment, I've found sort of critical issue on thread local storage solution. That is evaluating library form in eval won't add method to generic function. Well library form is not allowed to evaluate with eval on both R6RS and R7RS however Sagittarius allows it. (And I might have already written such code ...)

So forget about thread local storage. For now add-method checks current environment whether or not it's a child environment which is created by either environment procedure or a thread. The name child environment may confuse you but I just don't have any good name for this, so bare with it :) If add-method is called in child environment then it adds method to only in that context. If not, then adds globally.

Difference? Well sort of the same but it has now a way to affect changes globally. In the remote REPL situation discussed in previous article comment, it can be done with with-library macro. Other changes are only in the child context.

;; in remote REPL and we want to apply a patch
(import (sagittarius control))

(with-library (foo)
  ;; fix it
  (define-method bar ...))
This is just a fix before I forget, so it's time to read the paper.

2014-09-02

Resolving let-method

The previous article showed that there is a multi threading issue on let-method. In general, current Sagittarius adds generic method globally even whenever it's defined. So if the load loads a script with define-method then the generic method is added to the global binding. Thus the effect is globally done even though it's loaded in child thread.

This is not a good behaviour I believe so I've changed it. Current head has following behaviour;
  • If a method is defined in main thread - adds method globally
  • If a method is defined during importing a library - ditto
  • If a method is defined in child thread and not library importing period - adds thread local storage.
  • Generic functions are inherited from parent thread but child thread can't contaminate parent.
The thread local storage is VM's register I've added, generics (not sure if the name is proper but reasonable isn't it?). The changes are done in three places, add-method, remove-method and compute-methods. There are slight change of slot accessor of generic function as well but this is trivial.

The change of compute-methods is not a big one. It now just considers generic methods of current thread. Like I mentioned above, generic methods are located two places, one is generic function's methods slot and the other one is thread local storage. Thus compute-methods needs to get all methods both the slot and storage.

add-method and remove-method are a bit more tricky. First it needs to detect whether or not it's running on main thread or during library importing period. If the definition is executed on that term then it adds the methods to generic function's slot. If not, then it adds thread local storage with some more information. (currently maximum required argument number.)

Now following piece of code runs as I expected.
(import (rnrs) (clos user) (srfi :1) (srfi :18) (srfi :26))
 
(define-generic local)
 
(define (thunk)
  (thread-sleep! 1)
  (let-method ((local (a b c) (print a b c)))
    (thread-sleep! 1)
    (local 1 2 3))
  (local "a" "b" "c"))
 
(let ((ts (map thread-start! (map (cut make-thread thunk &lt;>) (iota 10)))))
  (for-each thread-join! ts))
;; may prints some of the value
;; then raises an error.
The solution itself might be a bit ugly (treating generic function specially) but behaving properly is more important.

let-method

Sagittariusはlet-methodという総称関数のスコープを限定する構文をもっているのだが、これとマルチスレッドが絡むとうまく動かないという話。

例えば以下のようなのを考えてみる。
(import (rnrs) (clos user) (srfi :1) (srfi :18) (srfi :26))

(define-generic local)

(define (thunk)
  (thread-sleep! 1)
  (let-method ((local (a b c) (print a b c)))
    (thread-sleep! 1)
    (local 1 2 3))
  (local "a" "b" "c"))

(let ((ts (map thread-start! (map (cut make-thread thunk <>) (iota 10)))))
  (for-each thread-join! ts))
let-methodの外側で呼ばれるlocalメソッドはどのような場合でもエラーを投げることが期待されるのだが、実はこれが期待通りには動かない。理由はいたって簡単で、スレッドAが二度目のlocal呼び出しをする際にスレッドBがlet-method内であればlocal自体は特殊化されたメソッドを持っていることになる。あまり使わない構文な上に、マルチスレッド環境のことなど頭から抜け落ちていたのでこういうことに気付かなかった。

では、どうするかということもついでに考えてみる。現状では総称関数はスレッド関係なくグローバルに影響を与えるのだが、こいつをスレッド毎にしてしまえばいいだけの話ではある。総称関数だけを特別視するというのは多少気持ち悪い部分もあるのだが、パラメタと同じでVMにそれ様のレジスタを追加しスレッドが作成されたらコピーすればいいという話になる。そうすることで大元の総称関数は変更されない。

問題はdefine-genericもdefine-methodも単なるマクロで、内部的には総称関数を作ってdefineで定義しているだけという部分と、VMは識別子を一度参照するとGLOCに置き換えるという点である。最初の問題は実はそんなに大きくなく、束縛を作成する際に値が総称関数であれば現在のVMに追加してやればいい。(そもそも子スレッドで束縛を作るというのはいかがなものかという話もあるのだが。) GLOCの問題はGLOCがコンパイルされたコードに埋め込まれつつ、この中身が基本変更されないという前提があることに起因する。GLOCがあるとコピーされた総称関数が参照できないということになる。

とりあえず思いつく限りでは2つ解決策がある。
  • 総称関数の参照はGLOCにしない
  • 総称関数の呼び出し時にうまいこと解決する
一つ目は全体のパフォーマンスに影響を与えそうではあるが、常に参照を解決するようにして束縛を探す際にコピーされた総称関数を返してやればよさそうである。
二つ目はVMのレジスタが大元の総称関数とコピーされたものの紐付けを持っておき、呼び出し時にコピー側に解決するというもの。単なる参照として別の手続きに渡された際にどう解決するかというもの。GREFが走るたびにチェックを設けていてはGLOCの意味がない気がするが、総称関数を扱う手続き全てにコピーを探す何かを入れるのはだるい。(本質的にはadd-methodとremove-methodだけを特別視すればいいような気がしないでもないが、ちと自信がない。)

あまり使わない機能な上に直すとパフォーマンスに影響がでるから割と腰が重めである・・・

追記
add-methodがスレッドローカルなストレージにメソッドを格納してcompute-methodsがその辺をうまいこと何とかすればいけそうな気がしないでもない気がしてきた。 add-methodにオプション引数としてスレッドローカルか判別するフラグつけて、let-methodが呼び出すadd-methodはそのオプションを受け付けるようにしてやればよさそう。(もしくはadd-method-localを作るか。) remove-methodも同様にしてやる必要があるが、変に上記のようにごにょごにょするよりはすっきりしているかもしれない。

Ambiguous WSDL behaviour?

I haven't read WSDL 1.1 specification thoroughly yet but it seems there is an ambiguous behaviour to create a XML message.

I've prepared following files (it's extremely simplified to make this article short).
example.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<definitions targetNamespace="http://example.com/" name="ExampleService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://example.com/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/">
  <types>
    <xsd:schema>
      <xsd:import namespace="http://example.com/" schemaLocation="schema1.xsd"/>
    </xsd:schema>
    <xsd:schema>
      <xsd:import namespace="com.example" schemaLocation="schema2.xsd"/>
    </xsd:schema>
  </types>
  <message name="create">
    <part name="parameters" element="tns:create"/>
  </message>
  <message name="createResponse">
    <part name="parameters" element="tns:createResponse"/>
  </message>
  <message name="ExampleServiceException">
    <part name="fault" element="tns:ExampleServiceException"/>
  </message>
  <portType name="Interface">
    <operation name="create">
      <input message="tns:create"/>
      <output message="tns:createResponse"/>
      <fault message="tns:ExampleServiceException" name="ExampleServiceException"/>
    </operation>
  </portType>
  <binding name="ExampleInterfacePortBinding" type="tns:Interface">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
    <operation name="create">
      <soap:operation soapAction=""/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
      <fault name="ExampleServiceException">
        <soap:fault name="ExampleServiceException" use="literal"/>
      </fault>
    </operation>
  </binding>
  <service name="ExampleService">
    <port name="ExampleInterfacePort" binding="tns:ExampleInterfacePortBinding">
      <soap:address location="REPLACE_WITH_ACTUAL_URL"/>
    </port>
  </service>
</definitions>
schema1.xsd
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="http://example.com/" xmlns:tns="http://example.com/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns1="com.example">

  <xs:import namespace="com.example" schemaLocation="schema2.xsd"/>

  <xs:element name="ExampleServiceException" type="tns:ExampleServiceException"/>

  <xs:element name="create" type="tns:create"/>

  <xs:element name="createResponse" type="tns:createResponse"/>

  <xs:complexType name="create">
    <xs:sequence>
      <xs:element name="request" type="ns1:Request" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="createResponse">
    <xs:sequence>
      <xs:element name="response" type="ns1:Response" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="ExampleServiceException">
    <xs:sequence>
      <xs:element name="message" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>
schema2.xsd
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" targetNamespace="com.example" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ns2="com.example">

  <xs:import namespace="http://example.com/" schemaLocation="schema1.xsd"/>

  <xs:complexType name="Request">
    <xs:sequence>
      <xs:element name="id" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="Response">
    <xs:sequence>
      <xs:element name="id" type="xs:string" minOccurs="0"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>
Now, I've created a sample message on Soap UI and online WSDL analyser. The result of create messages are followings;
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:exam="http://example.com/">
   <soapenv:Header/>
   <soapenv:Body>
      <exam:create>
         <!--Optional:-->
         <request>
            <!--Optional:-->
            <id>?</id>
         </request>
      </exam:create>
   </soapenv:Body>
</soapenv:Envelope>
<ns1:create xmlns:ns1='http://example.com/'>
<!-- optional -->
  <request>
<!-- optional -->
    <ns2:id xmlns:ns2='com.example'>?XXX?</ns2:id>
  </request>
</ns1:create>
The point is that online WSDL analyser's one has namespace com.example and Soap UI one doesn't. Well, from the beginning, I don't understand why both request element doesn't have namespace at all.

As far as I know, JAX-WS requires Soap UI format so this is written some where in spec?

2014-08-30

SRFI-27の紹介

(LISP Library 365参加エントリ)

SRFI-27は乱数ビットのソースです。 名前で分かるとおり、乱数の生成及びその生成源を扱うSRFIです。最も簡単な使い方は以下。
(import (srfi :27))

(random-integer 100)
;; -> random integer up to 100

(random-real)
;; -> random flonum range between 0 < x < 1
このスクリプトを複数回流した際に返される値がランダムになるかは実装依存です。例えば、Sagittariusでは最初の1回目は常に1が返ります。

さすがにそれは嬉しくないので、以下の結果を毎回ランダムにする方法が用意されています。
(random-source-randomize! default-random-source)

(random-integer 100)
default-random-sourcerandom-integerrandom-realで使用される乱数ソースです。それをrandom-source-randomize!でいい感じにシャッフルします。

デフォルトを変更したくないという我侭な要望にも柔軟に対応可能です。 以下のようにします。
(define my-random-integer
  (let ((s (make-random-source)))
    (random-source-randomize! s)
    (random-source-make-integers s)))

(my-random-integer 100)
;; random number

(random-integer 100)
;; 1 (on Sagittarius)
random-source-state-ref及びrandom-source-state-set!で乱数ソースの状態を取得、設定することも可能です。乱数ソースが何であるかは言及されていないのですが、書き出し可能なオブジェクトである必要があります。例えば、Sagittariusではバイトベクタ、Gaucheではu32ベクタ、Chibiは数値、Mosh及びYpsilon(ポータブルSRFI)ではリスト、Racketはベクタになっています。

今回はSRFI-27を紹介しました。

2014-08-27

最適化

ちょっとした最適化をコンパイルに入れた。具体的にはインポートした束縛でキャッシュ可能なものは定数展開してしまうというもの。以下がその例。
(library (foo)
    (export a)
    (import (rnrs))
  (define a 'a))
(import (rnrs) (foo))
;;(set! a 'b) ;; uncomment this would also work...
(disasm (lambda () (print a)))
こんな感じのライブラリをインポートすると
;; size: 5
;;    0: CONST_PUSH a
;;    2: GREF_TAIL_CALL(1) #<identifier print#user (0x80402708):0>; (print a)
;;    4: RET
こんな感じで、GREF_PUSHの変わりにCONST_PUSHが使われるというもの。これだけだと、実はあんまり嬉しくないんだけど、これにコンパイル時の手続き呼び出しが加わるとかなり最適化ができる。例えばaが文字列"1234"で、スクリプトが(print (string->number a))だとすると、string->numberがコンパイル時に解決されて数値1234が直接VMインストラクションとして出される。まぁ、そんなに上手いことはまるケースは少ないだろうけど、こういうのは入れておくと後で効いてくるというのが経験側から学んだことなので入れておいて損はないだろう。

現状で気に入らないのはset!の挙動なのだが、再定義と代入は別ものとしてエラーにした方が精神衛生上いいような気がする。スクリプト上の環境(+evalの環境)は再定義+代入可能にしているのでこれが可能なのだが(#!compatibleをつけても同様)、禁止するとREPL上でも禁止になるという弊害もある。そうは入ってもREPL上で(set! + 'hoge)とかやるかという話にもなるのだが、例えばMoshとChezはこれを禁止している(psyntaxがかね?)。ちなみに、NMoshとYpsilonは許容している(NMoshは奇妙な挙動をするけど)。

ちなみに、再定義もしくは代入された後は定数展開されない。というか、同一ライブラリで定義された場合はされないという話。これは、例えばGambit benchmarkのcompilerみたいなの対策だったりする(展開されると正しい結果が出ない場合)。ただし、library及びdefine-libraryフォーム内では別のロジックが走るので、展開される可能性はある。でも、よく考えれば、定数しかやらないので展開してしまってもいいかもしれない。後で考えよう。

追記
いや、やっぱり同一ライブラリ内では駄目だな。以下のようなのが困る。
(define b 'b)
(define (foo) (print b))
(foo)
(set! b 'c)
(foo)
これを定数展開してしまうと、二度目のfooが正しく動かない。

追記2
インポートされた束縛に対するset!は禁止することにした。再定義はOK。そういえばこの変更でR6RS/R7RS準拠のevalが書けなくもないのだが(定義禁止等)、まぁそこまでする必要は今のところないかなぁ。

2014-08-23

SRFI-26の紹介

(LISP Library 365参加エントリ)

SRFI-26はカリー化を除いたパラメタの特殊化表記です。日本語にすると意味が分かりませんが(正しく訳せているのかすら疑問) 、要するにcutcuteです。どちらも手続きを作成するマクロです。使い方は以下:
(cut cons a <>)
;; -> (lambda (tmp) (cons a tmp))

(cut list a <> <>)
;; -> (lambda (tmp1 tmp2) (list a tmp1 tmp2))

(cut list a <> <...>)
;; -> (lambda (tmp1 . xs) (apply list a tmp1 xs))

(cute cons (+ a 1) <>)
;; -> (let ((a1 (+ a 1))) (lambda (tmp) (cons a1 tmp)))
どちらのマクロも第一引数を手続きとみなし、残りをその手続きの引数とします。<>及び<...>はプレースホルダーで、作成された手続きに渡される引数に置換されます。出現する順番どおりに引数に展開されます。cutは引数をそのままlambdaに展開し、cuteの方はプレースホルダーである<>以外の引数を評価を一度だけ行うことを保障します。

プレースホルダーの使い方ではまるのが以下の例です。
(cut cons a (list b <>))
;; -> (lambda () (cons a (list b <>)))
プレースホルダーはcutもしくはcuteと同じ深さにいないといけません。個人的にこれは不便だなぁと思う点ではあるのですが、ネストを考慮するとsyntax-rulesで書けない(もしくはものすごく頑張らないといけない)ので、まぁしょうがないのではないでしょうか。

今回はSRF-26を紹介しました。

2014-08-19

Performance turning - I/O

The R7RS benchmark showed that I/O was slow in Sagittarius. Well not quite, in R7RS library read-line is defined in Scheme whilst get-line is defined in C.This is one of the reason why it's slow. There is another reason that makes I/O slow which is port locking.

Sagittarius guarantees that passing a object to the port then it writes it in order even it's in multi threading script. For example;
(import (rnrs) (srfi :18) (sagittarius threads))

(let-values (((out extract) (open-string-output-port)))
  (let ((threads (map (lambda (v) 
   (make-thread (lambda ()
           (sys-nanosleep 3)
           (put-string out v)
           (newline out))))
        '("hello world"
   "bye bye world"
   "the red fox bla bla"
   "now what?"))))
    (for-each thread-start! threads)
    (for-each thread-join! threads)
    (display (extract))))
This script won't have shuffled values but (maybe random order) whole sentence.

To make this, each I/O call from Scheme locks the given port. However if the reading/writing value is a byte then the locking is not needed. Now we need to consider 2 things, one is a character and the other one is custom ports. Reading/writing a character may have multiple I/O because we need to handle Unicode. And we can't know what custom port would do ahead. Thus for binary port, we don't have to lock unless it's a custom port. And for textual port, we can use string port without lock.

Now how much performance impact with this change? Following is the result of current version and HEAD version:
% ./bench sagittarius tail
Testing tail under Sagittarius
Compiling...
Running...
Running tail:10

real    0m26.155s
user    0m25.568s
sys     0m0.936s

% env SAGITTARIUS=../../build/sagittarius ./bench sagittarius tail

Testing tail under Sagittarius
Compiling...
Running...
Running tail:10

real    0m19.417s
user    0m18.703s
sys     0m0.904s
Well not too bad. Plus this change is not for this particular benchmarking which uses read-line but for generic performance improvements. Now we can finally change the subjective procedures implementation. The difference between get-line and read-line is that handling end of line. R7RS decided to handle '\r', '\n' and '\r\n' as end of line for convenience whilst R6RS only needs to handle '\n'. Following is the result of implementing read-line in C.
% env SAGITTARIUS="../../build/sagittarius" ./bench -o -L../../sitelib sagittarius tail

Testing tail under Sagittarius
Compiling...
Running...
Running tail:10

real    0m5.031s
user    0m4.492s
sys     0m0.795s

Well it's as I expected so no surprise.

Performance turning - Windows (MSVC)

It turned out that Windows version of Sagittarius is extremely slow. If I try ack on Cygwin version then it can run about 60 sec (Core i7 3.40 GHz). Following is the result of three implementations that previous article mentioned.
% time gosh -r7 ack.scm
gosh -r7 test.scm  130.14s user 4.26s system 120% cpu 1:51.70 total

% time sagittarius ack.scm
sagittarius test.scm  54.48s user 3.01s system 124% cpu 46.022 total

% time chibi-scheme ack.scm
chibi-scheme test.scm  49.84s user 0.00s system 99% cpu 50.170 total

And this is the inside of "acm.scm".
(import (scheme base))
(define (ack m n)
  (cond ((= m 0) (+ n 1))
        ((= n 0) (ack (- m 1) 1))
        (else (ack (- m 1) (ack m (- n 1))))))

(ack 3 12)
Apparently Chibi is the fastest. And Sagittarius is not so slow. Well, I don't know why Gauche is slow in Cygwin environment. I think this is simply the performance of VM and its stack overflow handling. (at least on Sagittarius, not sure for others.)

Now let's see how is the Windows version's performance. Following is the result of ack using Cygwin's time command to see how slow it is.
D:>\cygwin\bin\time sash.exe ack.scm
0.00user 0.00system 1:12.13elapsed 0%CPU (0avgtext+0avgdata 220928maxresident)k
0inputs+0outputs (901major+0minor)pagefaults 0swaps
I don't know why it has some garbage but 30% slower. So indeed it is Windows version's issue. But why?

I think the reason why is it does not use direct threading due to the limitation of MSVC (if we use it should improve approx 30% of performance, so about 21 sec in this case?). However I've found this article: How not to make a virtual machine (label-based threading). Even though this article concluded not to emulate direct threading on MSVC however it wouldn't hurt to give it a shot.So I've modified VM a bit to emulate it (sources are in msvc32-emulate-direct-threading branch). Then run the above script.

Here is the result;
D:>\cygwin\bin\time.exe build\sash.exe ack.scm
0.00user 0.00system 1:40.56elapsed 0%CPU (0avgtext+0avgdata 220928maxresident)k
0inputs+0outputs (901major+0minor)pagefaults 0swaps
Well, it's always better to believe what people have done, I guess...So this really doesn't work. Now we only have 2 options, giving up or adopt to MinGW. Hmmmm.

2014-08-17

性能差

@SaitoAtsushiさんがLarcenyにあるR6RSベンチマークをR7RS用にして走らせた結果をツイートしていた。


Gaucheが速いのは分かっていたことなのだが(いずれ追い越す予定…未定…)、Chibiより遅いベンチがあるのは正直まずい。Chibiはランタイムが小さいためほとんどの手続きがSchemeで書かれているという特徴があるのだが、それより遅いのがあるということは基本的なVM等の動作が遅い可能性がある。

とりあえずChibiより遅いのが以下のベンチ
  • ack
  • cat
  • nucliec
  • quicksort
  • simplex
  • slatex
  • gcbench
  • mperm
こいつらに関わる手続きはすぐにでもパフォーマンスチューニングする必要がある。特にackなんて本当にシンプルなのでVMの性能がまずいということになる・・・(涙)

次いで、Gaucheに圧倒的に負けてるやつ
  • ctak
  • sum
  • sumfp
  • mbrot
  • mbrotZ
  • pnpoly
  • tail
  • read1
  • lattice
  • nqueens
結構あるのだが、このうち浮動小数点が関わってくるやつ(sumfp、mbrot及びmbrotZ)はちと分が悪いので後回し。Gaucheは浮動小数点数をある程度レジスタに持つのでキャッシュが効くうちはメモリ割付が発生しない(はず)。テキストポートが絡んでくるI/O周りも多少分が悪いところがあるのではあるが、何とかしたいところ。nqueensはどちらも絡まないのでVMもしくはコンパイラということになる。

所詮マイクロベンチマークという見方もあるかもしれないが、こういう地味な性能差は地道なチューニングの結果なのでここで手を抜くのはまずいというのが僕の考え方。

2014-08-12

What do you expect to be printed?

I was testing how include on Sagittarius works and found out something interesting.

Firstly, I've prepared 4 files related to library. The contents are very simple. Like this;
;; foo.sld
(define-library (foo)
    (import (scheme base))
    (export bar)
  (include "impl/bar.scm"))

;; impl/bar.scm
(include "buzz.scm")

;; impl/buzz.scm
(define bar 'bar)

;; buzz.scm
(define bar 'buzz)
Now I put those files following directory structure;
./ + foo.sld
   + impl
       + bar.scm
       + buzz.scm
   + buzz.scm
Then, wrote following script;
(import (scheme base) (scheme write) (foo))
(display bar) (newline)
The question is, what would be the expected value to be printed 'bar or 'buzz? I first expected to be print 'bar with Sagittarius but it was 'buzz. Then checked with Chibi which also printed 'buzz. At this point I thought, oh R7RS de-facto would be 'buzz. Well it wasn't. I've also check with Gauche and Foment and these printed 'bar.

I know why this happens actually. When the first include was processed then the current loading path returned to the same directly as "foo.sld". Then second include "buzz.scm" is processed. I don't know how Gauche and Foment do this properly (well R7RS doesn't specify how to resolve the path so both are proper behaviour though).

Well, by now I've never seen this type of code in anywhere other than in my test. But I would like to go more intuitive behaviour.

13th August, 2014
I've modified compiler to be able to handle above case. What I've changed is that
  • include and include-ci returns read expression and filename
  • compiler sets current load path after include is done according to above filenam
It's a bit ugly way to fix so I need to think how we can clean it up now.

2014-08-11

SQL in S-Expression

There is SXML which represents XML in S-Expression. If you're a professional programmer, it is very difficult to avoid using SQL (well, if you could, you are very lucky).

Writing SQL isn't so bad if the file is separated or the editor is smart enough to edit. However it's usually none of the case so I always need to suffer especially writing it in double quote. Now, Emacs is de-facto editor for all programmers (vim? why are you talking about jeans here? V.I.M.) and it's by default very good with editing s-expression. So if you can write SQL in s-expression then the world would be happier than before.

Now, there are bunch of projects that have the same idea I've got, CLSQL, SxQL, S-SQL for example (I think Clojure also has something similar but I'm not so familiar). The problem of them are very simple. It's using keyword which is not in R6RS/R7RS. Moreover, those use either reader macro or CLOS so not pure s-expression. What I want is light weight but easy to edit. (well, it is actually ok for me, Sagittarius has all of them anyway.)

So I'm thinking something like following;
(define ssql 
  '(select ((p id) (as (p first_name) name) (a street))
    (from (as person p))
    (inner-join (as address a) (on (= (p id) (a id_person))))
    (where (= (a country) "Nederlands"))))

(ssql->sql ssql)
;; "select p.id, p.first_name as name, a.street
;;  from person as p
;;  inner join address as a on p.id = a.id_person
;;  where a.count = 'Netherlands'" 
Hmmm, it looks parentheses are a bit too many so the readability is a bit lowered. Basically it doesn't have to be fancy but readable and easy to remember. So one-by-one mapping is ok. (if I want  OR-mapper, then I just need to construct it on top of it.)

If there is something similar in Scheme or better idea, I would like to know.

2014-08-08

SRFI-25の紹介

(LISP Library 365参加エントリ)

SRFI-25は多次元配列を扱うSRFIです。多次元配列自体はリストでもベクタでも表現できますが、このSRFIは2次元以上の配列の扱いをより自然にすることを目的としています。

使い方を見てみましょう。
(import (srfi :25))
;; create 3 dimension array, each dimension
;; contains bound start with 0 and end with 4
;; (exclusive)
(define arr (make-array (shape 0 4 0 4 0 4)))

;; set 'e1 on position x = 2, y = 2, z = 2
(array-set! arr 2 2 2 'e1)
;; -> unspecified value

;; ref it
(array-ref arr 2 2 2)
;; -> e1

;; returns dimensions of array
(array-rank arr)
;; -> 3
ベクタやリストを使うとアクセスが複雑になるところが、より直感的に操作可能になります。

もう少し複雑な例を見てみましょう。手続きshapeは配列境界のペアを受け取ります。例えば以下のように書けば0以外の数字から始まる配列も書くことが可能です。(配列が3次元を超えるを僕の理解の範疇を超えてくるので2次元で・・・)
;;; first dimension has 2 elements start with 4 end with 5
;;; second dimension has also 2 elements start with 5 end with 6
;;; the array is initialised with given objects as following
;;;   5  6 
;;; 4 nw ne
;;; 5 sw se
(array (shape 4 6 5 7) 'nw 'ne 'sw 'se)
このSRFI自体には含まれないのですが、このSRFIの著者はarlib.scmをSRFIに付随させており、その中にはtubulate-arrayarray-equal?といった便利手続きが定義されています。(なんでSRFI自体には含めなかったんだろう?)

今回はSRFI-25を紹介しました。

2014-08-05

LaTeX用環境構築

Schem Workshop 2014の主催者から「Sagittariusについてペーパー書かない?」というメールが来たのが昨日。これも何かの縁だろうということで頑張って書こうかと決めたのが昨日。Windows用のLaTeX環境が絶望的に乏しいことに気付いたのも昨日。昨日はなんだかいろいろあったなぁw

世の中にはCloud LaTeXなるものもあるらしく、おとなしくそれを使っておけという話なのかもしれないが、それらがEmacsキーバインドをサポートしているかどうかというのと、期日が9月5日という1ヶ月しかない中でサービス探しつつLaTeXを覚える(そして英語論文の書き方も・・・)というのをこなすのはちときついので、Emacsに環境を作ってしまおうという方に傾いてみることにした。軽くググッてみると世の中にはAUCTeXなるものがあるらしい。とりあえずこいつをLaTeXのプリビューが出来るまで動かせれば勝ちである。

とりあえず簡単な手順。
  1. ダウンロードページからAUCTeXを落としてくる。
  2. libpng14-14.dllとzlib1.dllの32ビット版をEmacsのbinフォルダに置く(PNGサポート)
  3. MikTeXをインストールしてパスを通す
    64ビット版を入れたが正しいかは不明
  4. Ghostscriptの32ビット版をインストールしてパスを通す
    最初64ビット版を入れて、GSWIN32C.EXEがないと言われてはまった
これでいいはずなんだけど、スクリーンショットのpreview-latexのようには見えない件。error in process sentinel: preview-reraise-error: End of Preview snippet 9 unexpectedなんていわれる。何かが足りてないのだろうが、何が足りてないのかは不明・・・しかし、なんで選択肢がMicorsoft WordかLaTeXというのがいけないんだ。OOoならあるのに・・・

ペーパー書く以外にも問題があるのだけど、そいつらは書けてから考えることにする。(USに入るための手続きとか、クレジットカード作らないととか、パスポートの更新とか、まぁ割りとたくさんあるなぁ・・・)

2014-08-04

Gaucheライクな文字セットリーダをサポートしてみた

事の発端は(text parse)のテストケースがなかったこと。

この数日バイナリの扱いを楽にするためのライブラリを書いていたのだが、(text parse)と対になる(binary parse)を格段になってテストケースがないことに気付いた。っで、Gaucheからテストケースを拝借しようと思ったら、文字セットのところで躓いたという話。Sagittariusはリーダーマクロをサポートしているのでさくっと書いてしまえばいい話ではあるのだが、この形式の読み取りで躓いたのはこれで何度目か分からないので。

以下のように使える。
#!read-macro=char-set
#[a-zA-Z]
;; => #<char-set ...>
あくまで読み取りで、書き出しの方は何もしてない。(pp)は変更してあるのでそれを使うということで。

実装は以下。
(library (char-set)
    (export :export-reader-macro)
    (import (rnrs)
            (sagittarius reader)
            ;; we don't want to export regex reader macro with this
            (only (sagittarius regex) parse-char-set-string))

  ;; Gauche compatible charset
  (define-dispatch-macro #\# #\[ (char-set-reader port subchar param)
    (define (read-string in out)
      (let loop ((depth 0))
        (let ((c (get-char in)))
          ;; we just need to put all chars until we got #\]
          ;; c could be eof so check
          (when (char? c) (put-char out c))
          (cond ((eof-object? c) 
                 (raise (condition
                         (make-lexical-violation)
                         (make-who-condition 'char-set-reader)
                         (make-message-condition "unexpected EOF"))))
                ((char=? c #\[) (loop (+ depth 1)))
                ((char=? c #\])
                 ;; done?
                 (unless (zero? depth) (loop (- depth 1))))
                (else (loop depth))))))
    (let-values (((out extract) (open-string-output-port)))
      ;; need this unfortunately
      (put-char out #\[)
      (read-string port out)
      (parse-char-set-string (extract))))
)
parse-char-set-stringは新たに足された手続き。インポートの際に問答無用でインポートしたライブラリのリーダーマクロを引き継ぐようになっていたのを修正。

スクリプト内で使う分にはこれで十分なはず。

2014-08-01

SRFI-23の紹介

(LISP Library 365参加エントリ)

SRFI-23はエラー通知について定めたSRFIです。要するにerror手続きです。使い方は以下のようになります。
(error "error reason" some error related objects)
たったこれだけです。このSRFIではエラーが通知された際に何が投げられる等のことは定義されていません。参照実装は潔く単にメッセージと原因のオブジェクトをプリントした後、処理系が何かしらエラーを投げると思われる手続きの呼び方をして終わりです。(例:(car 'a)等)

振る舞いはともかくとして、このSRFIはR7RSに丸ごと取り入れられています。R7RSではエラーオブジェクトを投げるとされていますが、エラーオブジェクトが何かということは触れられていません。

ちなみに、R6RSにも同名の手続きがありますが、こちはら引数の個数が違うので注意が必要です。また、投げられる例外オブジェクトについても細かく既定されています。 これもR6RSとR7RSの非互換の一つです。

今回はSRFI-23を紹介しました。

2014-07-30

Industriaのテスト

IndustriaというR6RS Scheme用のポータブルなライブラリをご存知だろうか?Göran Weinholt氏が作成しているライブラリで、暗号器からX86のアセンブラまであるという巨大なライブラリである。(GitHubで最後にコミットがあったのが去年の8月なのでひょっとしたら開発が停止しているかもしれない。)リポジトリのweinholt/netディレクトリの中にあるtcp.slsからサポートしている、もしくは検討された処理系を知ることができる。以下がそのリスト。
  • Chez
  • Guile
  • ikarus
  • IronScheme
  • Larceny
  • Mosh
  • mzscheme (Racket)
  • Sagittarius
  • vicare (ikarusの後継)
  • Ypsilon
Biwa Schemeを除いたr6rs.orgに挙げられている処理系のリストほぼそのままである。可搬性のない部分のみをうまく切り出して、あとはR6RSの仕様どおりに書かれているということであろう。

以前このライブラリで定義されているマクロがうまく動かないことがあったこともあり、マクロ展開器のバグを直すたびくらいにテストを走らせていたのだが、ふと他の処理系ではどうなのかと思い試してみた。依存しているSRFIとCygwinという関係上、テストを走らせることが出来たのは以下の3つ。
  • Sagittarius (0.5.7 HEAD)
  • Mosh (0.2.7)
  • Racket (6.0.1 Windows x86_64)
GitHubのIssueに挙げたのだが、weinholt/assembler/x86.slsにあるfind-instruction-encoding手続きで定義されているbailoutが0引数で呼ばれている箇所があるため、直してやる必要がある。(でないとSagittariusはコンパイルエラーを挙げる。)毎回コマンドを叩くのもあほらしいので以下のようなスクリプトを用意した。
#!/bin/sh

usage() {
    echo "run-test.sh CONFIG"
    echo " CONFIG argument must be a shell script file"
    echo " contains $program and $load_flag"
}

if [ -f $1 ]; then
    . `pwd`/$1
    if [ "$?" != "0" ]; then
 echo "source command failed"
 exit -1
    fi
else
    usage;
    exit -1
fi

load_path=`pwd`

init $load_path

cd tests
for f in *.sps
do
    echo "Testing $f"
# hack for Windows racket...
    temp_file=$f.tmp.scm
    echo -n ';;' | cat - $f >  $temp_file
    echo "$program $load_flag $f"
    "$program" $load_flag $temp_file
    rm $temp_file
done
っで、以下の設定ファイルをそれぞれ用意。
# sash.conf
#!/bin/sh
# -*- mode:shell; coding:utf-8; -*-

program=/opt/bin/sash
load_flag=

init() {
load_flag=-L$1
}

# mosh.conf
#!/bin/sh
# -*- mode:shell; coding:utf-8; -*-

program=mosh
load_flag=

init() {
    load_flag='--loadpath='$1
}

# racket.conf
#!/bin/sh
# -*- mode:shell; coding:utf-8; -*-

program='/cygdrive/c/Program Files/Racket/plt-r6rs'
load_flag=

init() {
    load_flag="++path ../"
}
後は以下のようなコマンドで走らせる。
$ ./run-tests.sh sash.conf
テストを走らせた結果、意外にもSagittariusだけが全てのテストをパスするということが起きた。(これを書きたかった。)

Moshはpsyntaxのエラーがあったり、行末文字に起因すると思われるエラーがあったりでいくつかのテストが失敗する。またSRFI-25をサポートしていないのでライブラリがないというエラーも見受けられた。NMoshで走らせた場合はマクロ展開に起因するエラーは発生しないものの、通常のMoshと同様のエラーは発生する。

Racketは恐らく行末文字に起因するエラーが一つといくつかのテストが走ることさえなく落ちていくという現象が起きていた。

また、テスト完了までの時間もSagittariusが最速であった。以下がtimeコマンドを用いたテストケースの起動結果。(Cygwin 32 bit on Windows 7 64 bit Core 7i)
% time ./run-tests.sh sash.conf > sash.log 2>&1              [~/work/industria]
./run-tests.sh sash.conf > sash.log 2>&1  68.94s user 6.14s system 103% cpu 1:12.23 total

% time ./run-tests.sh mosh.conf > mosh.log 2>&1              [~/work/industria]
./run-tests.sh mosh.conf > mosh.log 2>&1  111.95s user 2.98s system 99% cpu 1:55.56 total

% time ./run-tests.sh racket.conf > racket.log 2>&1          [~/work/industria]
./run-tests.sh racket.conf > racket.log 2>&1  0.21s user 0.71s system 0% cpu 5:56.48 total
以外なのはRacketが5倍近く遅かったことである。

この結果を受けて着実に使える処理系になっていっているということと勝手に解釈することにする。

2014-07-27

みんな仲良く

ドラクエ4の作戦みたいだw 博愛主義とは違うんだろうけど、こういうのなんていうんだろう?

生きていれば、好きになれない人、どうにも馬が合わない人、関わりたくない人なんていくらでも出てくると思う。そんな人との付き合いが発生したときにどうするかという話。あくまで僕個人の話で、これが唯一絶対ではないし、一般論ですらないかもしれないけど・・・(どちらかと言えば単なる愚痴というか精神衛生を保つために何かしら吐き出すという行為に近いw)

こういう話を一般論でするのは多少危険なので、少しだけ具体的な状況を記しておくことにする。相手は、僕のことを散々人格否定した人。ちなみにそれに関しての謝罪はない。状況としては第三者から今のギクシャクした関係をなんとかできないかという話を振られた。これ以上具体的に書くのは辛いのでこれくらい。

個人的にはそんな話が第三者からでてくること自体がおかしな話だと思うのだが、まぁ理解できなくもない範囲であるし、こういった場合にどう答えるのかというのが趣旨なのでそれは置いておく。僕の答えはもちろん「No」である。できるわけがない。

感情的に言えば、相手は僕の人生の中に存在しないレベルでいてほしいくらいになっている。そもそも謝罪もされていないのだから相手方にそんな意思はないと捕らえるのが普通で、そんな人間との関係をどうこうするつもりはない。まぁ、謝罪されたからといって許すつもりは全然ないし、謝罪がほしいとすら思っていない。そもそも、存在してほしくないのだ。

理性的には、そうするメリットがない。幸か不幸か相手の状況を大体把握しているのと、相手から学ぶことはない(IT界隈の人ではないという意味)のと、相手が持つコネクションは僕にとって価値があるものではない。最悪の場合、デメリットが発生する可能性の方が高い(金の無心等)。打算的にみて一切の利がないのであれば、関係を改善する理由はない。

感情的にも理性的にも「No」という答えがでてくるのだ、それこそどうしようもない。こういった場合に求められるのは「大人の対応」ができるかどうかだけである。人それぞれ「大人の対応」の定義に違いがあるとは思うが僕の定義では、必要最小限の会話で波風を立てずただやり過ごす、である。必要最小限の会話なんてビジネスの場でないのであればそれこそ挨拶程度でいいし、それも他に誰かいるときだけでいい。道端でばったり出会ったときは無視すればいい。社交性が求められるのは自分と相手の第三者がいるときだけである。

綺麗ごとだけで生きていける人からすれば、僕の対応は間違っているのかもしれないけれど、残念ながらそんな幸せな世界では生きていないみたいである。全ての人と仲良くする必要はないし、全ての人を許す必要もない、嫌な人は変な波風を立てないように適当にあしらえばいい。あんまり褒められたものではないが、30余年の人生で学んだ処世術の一つである。

2014-07-26

SRFI-22の紹介

(LISP Library 365参加エントリ)

SRFI-22はSchemeスクリプトをUnix上で実行形式であるかのうようにして走らせるためのSRFIです(日本語難しいな・・・)。何を定義しているかといえば、スクリプトの先頭に現れた#! /usr/local/bin/sashのような形式のラインを無視することと、main手続きの呼び出しです。

具体的な例を見てみましょう。(この例では/usr/local/binにSagittariusがインストールされていることを前提としています。)
#! /usr/local/bin/sash

(import (rnrs))

(define (main args)
  (display "hello world!") (newline))
このスクリプトを仮にsrfi-22.scmとして保存し実行権を付与すれば、Unixライクなシェル上で以下のように実行することができます。
$ ./srfi-22.scm
このSRFIで重要になるのは、実は#!の部分だけで、それ以外は処理をトップレベルにおいてしまえば動きます。(R5RS時代にcommand-line手続きがなかったのであればmainの意味は大きいとは思いますが、R6RS以降であれば単なるショートカットくらいの意味しかないでしょう。)

個人的にはもっともよく使っているSRFIの一つですが、割と賛否両論あるみたいです。

今回はSRFI-22を紹介しました。

2014-07-25

R7RS implementation differences

Recently, Gauche 0.9.4 has been out and it now supports R7RS. Now, I have couple of R7RS implementation in my hand, like followings;
  • Sagittarius (of course)
  • Chibi Scheme
  • Gauche
  • Foment
Well, only 3. I've also tried foment and picrin which are is supposed to be R7RS implementations however it didn't work on Cygwin which is my main environment. (Sorry, I'm Windows user...)

July 28th 2014: Foment compiled on VC2010 has been added.

The purpose of this article is to show some corner cases of R7RS. Sagittarius is R6RS/R7RS and Chibi and Gauche are R5RS/R7RS implementation so there should be something but how much? So I've prepared this test script.
;; #!r7rs is supported only Gauche and Sagittarius...
;; #!r7rs
(import (scheme base)
        (scheme write)
        (scheme read)
        (scheme file)
        (scheme inexact)
        (scheme lazy))

(define (print . args) (for-each display args) (newline))

;; string
;; allow #\null in string?
(print "String range")
(write "abc\x0;def")(newline)

;; file
;; are binary and textual port separated?
(print "Port separation")
(define-syntax check-port
  (syntax-rules ()
    ((_ opener reader data)
     (let ((in (opener data)))
       (guard (e (#t (print "ERROR: "(error-object-message e))
                     (close-input-port in)))
         (print 'opener ":" 'reader)
         (reader in)
         (close-input-port in))))))

;; files
(check-port open-binary-input-file read-char "r7rs-comp.scm")
(check-port open-binary-input-file read-u8 "r7rs-comp.scm")
(check-port open-input-file read-char "r7rs-comp.scm")
(check-port open-input-file read-u8 "r7rs-comp.scm")
;; memory
(check-port open-input-bytevector read-u8 #u8(0 1 2 3))
(check-port open-input-bytevector read-char #u8(0 1 2 3))
(check-port open-input-string read-u8 "abc")
(check-port open-input-string read-char "abc")

;; error object
(print "Error object")
(define-syntax check-error
  (syntax-rules ()
    ((_ pred expr)
     (guard (e (#t (print 'pred ":" (pred e))))
       expr))))
(check-error error-object? (car 'a))
(check-error read-error? (read (open-input-string "#0#")))
(check-error file-error? (open-input-file "this doesn't exist.txt"))

(define-syntax check
  (syntax-rules ()
    ((_ expr)
     (guard (e (#t (print 'expr ":" (error-object-message e))))
       (print 'expr ":" expr)))))

;; sqrt
(print "Procedures")
(check (sqrt 4))

;; promise
(print "Promise")
(check (+ (delay (* 3 7)) 13))
The script is constructed based on what I thought could be a corner case. Mostly port related procedures and error object. Then this is the output;
Sagittarius
String range
"abc\x0;def"
Port separation
open-binary-input-file:read-char
ERROR: textual port required, but got #<file-binary-input-port r7rs-comp.scm>
open-binary-input-file:read-u8
open-input-file:read-char
open-input-file:read-u8
ERROR: "binary-port" required, but got #<transcoded-textual-input-port r7rs-comp.scm utf8-codec>
open-input-bytevector:read-u8
open-input-bytevector:read-char
ERROR: textual port required, but got #<bytearray-binary-input-port>
open-input-string:read-u8
ERROR: "binary-port" required, but got #<string-textual-input-port>
open-input-string:read-char
Error object
error-object?:#t
file-error?:#t
Procedures
(sqrt 4):2
Promise
(+ (delay (* 3 7)) 13):"number" required, but got (13 #<<promise> 0x80599100>)
---
Chibi Scheme
String range
"abc\x00;def"
Port separation
open-binary-input-file:read-char
open-binary-input-file:read-u8
open-input-file:read-char
open-input-file:read-u8
open-input-bytevector:read-u8
open-input-bytevector:read-char
open-input-string:read-u8
ERROR: not a binary port
open-input-string:read-char
Error object
error-object?:#t
read-error?:#t
file-error?:#t
Procedures
(sqrt 4):2
Promise
(+ (delay (* 3 7)) 13):invalid type, expected Number
---
Gauche
String range
"abc\0def"
Port separation
open-binary-input-file:read-char
open-binary-input-file:read-u8
open-input-file:read-char
open-input-file:read-u8
open-input-bytevector:read-u8
open-input-bytevector:read-char
open-input-string:read-u8
open-input-string:read-char
Error object
error-object?:#t
read-error?:#t
file-error?:#t
Procedures
(sqrt 4):2
Promise
(+ (delay (* 3 7)) 13):operation + is not defined between 13 and #<promise 0x80445480>
Following it the output from Foment
String range
"abc^@def"
Port separation
open-binary-input-file:read-char
ERROR: read-char: expected an open textual input port
open-binary-input-file:read-u8
open-input-file:read-char
open-input-file:read-u8
ERROR: read-u8: expected an open binary input port
open-input-bytevector:read-u8
open-input-bytevector:read-char
ERROR: read-char: expected an open textual input port
open-input-string:read-u8
ERROR: read-u8: expected an open binary input port
open-input-string:read-char
Error object
error-object?:#t
file-error?:#t
Procedures
(sqrt 4):2
Promise
(+ (delay (* 3 7)) 13):+: expected a number
Hmmm, not much difference than I expected. The only difference is port related procedures. Gauche is the most tolerant then Chibi, Sagittarius is the strictest one. Well, Sagittarius is an R6RS implementation and it's required to be. Gauche's behaviour is a bit too flexible to me but still consistent. Chibi looks a bit tricky to me. It actually reject to pass texutal port to read-u8 however read-char accept binary port.

Even though, R7RS explicitly says that implementation may reject #\null in strings however none of implementations does it. Interestingly, Foment doesn't support writing #\null. So it writes as it is. It would be better to consider that it doesn't support properly or particially supported. So to write portable string library, users should not depend on null character.
 
I thought error object could have some difference but not really (or the test script doesn't cover that much).

I was hoping one of the implementations (Chibi or Gauche though) have implicit forcing but the result was none of them.

The difference is small and it's avoidable if users are consistent. So if you write R7RS scripts and it could be run on Sagittarius, then most likely it would run on the other implementations.

2014-07-24

バグは寝かせてみるといい

かなり長めに寝かせていたバグの糸口を掴んだのでメモ。

問題になるのは以下のようなコード
(import (rnrs))
(define-syntax wrap-let
  (syntax-rules ()
    ((_ expr)
     (let-syntax ((foo (lambda (x) (syntax-case x () ((_) #'expr)))))
       (foo)))))

(define-syntax wrap
  (lambda (x)
    (syntax-case x ()
      ((_ expr)
       #'(let ((temp expr))
           (wrap-let (car temp)))))))

(wrap '(a b c))
これがなぜ問題になるのかというのが重要で、展開された後を見るとなんとなく答えが出てくる。以下が展開形コードのイメージ。
(let ((temp.0 '(a b c)))
  (let-syntax ((foo (lambda (x) (syntax-case x () ((_) #'(car temp.0))))))
    (foo)))
問題はここからで、let-syntaxの中にある#'(car temp.0)が原因。Sagittariusのマクロ展開器は現状syntax-caseのパターン及びテンプレートを全てリネームする。さらにsyntaxが展開されるときにもリネームする。そうすると、元々のtemp.0は持っていない構文情報を付加されてしまい、結果的に一意の識別子になってしまう。

ならば、テンプレート変数と分かっている場合はリネームしなければいいだけのように見えるのだが、そうも行かないのが面倒なところ。テンプレート変数はリネームされなければならないのだ。とりあえずマクロ展開形が局所マクロを含んでいるというのは(おそらく)なんとか分かる気がするので、その場合のみを特殊扱いするという感じでいこうかと思う。既に不安材料が見えているのでそれが正しいのかは今一よく分からないのではあるが・・・

このバグ、6ヶ月くらい寝かせてあったのだが、最近になって気運が高まってきたのか原因が掴めるところまできた。どうやら困難なバグほど寝かせておいた方が角が取れて消化しやすくなるらしい。

2014-07-22

packratの機能追加

SagittariusにはChicken由来のpackratライブラリがあるのだが、このライブラリ数量指定子が使えなくて非常に使い出が悪い。例えば、0個以上の何かを取るというのを書くのに以下のようにする必要がある。
(import (rnrs) (packrat))

(define (generator tokens)
  (let ((stream tokens))
    (lambda ()
      (if (null? stream)
          (values #f #f)
          (let ((base-token (car stream)))
            (set! stream (cdr stream))
            (values #f base-token))))))

(define quantifier (packrat-parser
                    top
                    (top ((vs <- item*) vs))
                    (item* ((i <- item i* <- item*) (cons i i*))
                           (() '()))
                    (item (('+) '+))))


(let* ((q (generator '((+) (+) (+))))
       (r (quantifier (base-generator->results q))))
  (parse-result-semantic-value r))
;; -> (+ + +)
BNFで書かれた定義を持ってくる際に数量指定子がないというのは非常に面倒で、パーサを書くのを何度も何度も億劫にしてくれた。quantifierの定義はこう書けた方が直感的である。
(define quantifier (packrat-parser
                    top
                    (top ((vs <- (* item)) vs))
                    (item (('+) '+))))
ずっとほしいと思っていたのだが、そろそろあった方がいいなぁという気運が高まってきたので、えいや!っと中身を読むことにした。

中身を読んで分かったのは、複数回マッチするというのを何とかするAPIが足りていないのが原因ぽかったので、こんなのを追加してマクロ側にも手を入れてみた。
(define (packrat-many parser n m k)
  (lambda (results)
    (define (>=? many max) (and max (>= many max)))
    (define (return-results vs result)
      ;; if we inline map then for some reason step on a bug...
      (let ((vals (map parse-result-semantic-value vs)))
        (merge-result-errors ((k vals)
                              (parse-result-next result))
                             (parse-result-error result))))
    (when (and (zero? n) (eqv? n m))
      (error 'packrat-many "both min and max are zero" n m))
    (let loop ((i 0) (vs '()) (s #f) (results results))
      (if (>=? i m)
          (return-results (reverse! vs) s)
          (let ((result (parser results)))
            (cond ((parse-result-successful? result)
                   (loop (+ i 1) (cons result vs) result
                         (parse-result-next result)))
                  ((<= n i)
                   (return-results (reverse! vs) result))
                  (else result)))))))
なぜか、インライナーのバグを踏んだのはまぁご愛嬌ということで・・・
マクロの方は+、*、?に加えてマッチ回数を指定できる=も入っている。長いのでここには載せない。これで多少便利になる気がする。

2014-07-14

ライブラリの作られるタイミング

Chaton Gauche部屋から流れてきたこれ
REPLからエラーの起きるライブラリを繰り返しr7rs#importすると,エラーが起きなくなります.

;; temp.scm
(define-library (temp)
  (begin
    (f)))

;; REPL
gosh> (import (temp))
*** ERROR: Compile Error: Compile Error: unbound variable: f

"(standard input)":1:(import (temp))

Stack Trace:
_______________________________________
  0  (eval expr env)
        At line 179 of "/usr/local/share/gauche-0.9/0.9.4-rc2/lib/gauche/interactive.scm"
gosh> (import (temp))
#<undef>
gosh[r7rs.user]>
全く同じ問題がSagittariusでも起きる。原因も分かっていて、コンパイラがコンパイル時にライブラリを作ってしまうけど、エラー自体はランタイムで起きるのでimportが呼ばれた際にはライブラリはあるものとして扱われてしまうから。

あまりうまい解決策が思いつかないんだけど、とりあえずぱっと思いついたのがVM側で何とかするというもの。っが、実際に実装しかけては無理だなぁということが分かってしまったので破棄。案としては、evalの中身をwith-exception-handlerで囲ってしまい、エラーが起きた際に現在のライブラリと保存されたライブラリが異なっていたら現在のライブラリを破棄するというもの、だったのだが、これはライブラリの変更がインストラクションでのみ発生するということに依存している上に、それ以外の方法で変更することが可能なので火を見るより明らかにだめだろうという・・・

次に思いついたのが、ライブラリのコンパイル全体をwith-exception-handlerで囲ってしまうとうもの。実際の手順としては以下のようになる予定。
  • ダミーのライブラリを作る
  • 実行コードをwith-exception-handlerで囲う
  • コンパイル及び実行
    • エラーがあればダミーを破棄
    • なければ正式名で作成及び中身をコピー
こっちはまだ実装をしていないのだが、見えているだけで以下のデメリットががが
  • エラーが存在しない場合でも呼ばれる
    • キャッシュがでかくなる
  • 余分な手続きを2つ作る必要があるため、性能に影響が出そう
  • コピーが重い
  • マクロ展開がライブラリを使用しているのだけれど・・・
まぁ、ダミーのライブラリを作る必要はないかもしれないのでそこは下2つは無視できるかもしれないが。

REPLでしか起きない問題なので多少以上にやる気が低いのも問題だろう・・・

2014-07-11

SRFI-19の紹介

(LISP Library 365参加エントリ)

SRFI-19は日時を扱うライブラリです。関連性が強いのでSRFI-18と同じ著者だと思っていたのですが、実は違うということに今日気づきました。新たな発見です。

このSRFIでは時間と日付の両方を扱います。まずは簡単に使い方を見てみましょう。
(import (rnrs) (srfi :19))
;; simple stop watch
(define-syntax time
  (syntax-rules ()
    ((_ expr)
     (let* ((s (current-time))
            (r expr)
            (e (current-time)))
       (display "execution time: ")
       (display (time-difference e s))
       (newline)
       r))))

(define (tak x y z)
  (if (not (< y x))
      z
      (tak (tak (- x 1) y z)
           (tak (- y 1) z x)
           (tak (- z 1) x y))))

(time (tak 21 14 7))
;; prints
;; <time-duration 0.0000000>
;; -> 14
どのような形で表示されるかは処理系依存なので、値を真面目に取得するにはtime-secondやtime-nanosecond等のアクセサを使います。

current-timeで現在の時間オブジェクトを取得するのがよく使われると思いますが、計算して時間を求めることも可能です。その際にはmake-time及び計算用の手続きを使います。
(define one-hour (make-time time-duration 0 3600))
(let ((c (current-time)))
  ;; I don't want to do anything for an hour...
  (thread-sleep! (add-duration c one-hour)))

make-timeは最初に時間タイプを受け取ります。このSRFIでは以下のタイプが定義されています。
  • time-duration
    時間の差分を扱います
  • time-monotonic
    Monotonicな時間を扱います
  • time-process
    現在のプロセス時間を扱います
  • time-tai
    TAI時間を扱います
  • time-thread
    現在のスレッド時間を扱います
  • time-utc
    UTC時間を扱います
処理系によってはプロセス時間とスレッド時間はサポートしていない可能性があります。

日付を扱う手続きのいくつかを見てみましょう。
(current-date)
;; -> date object

(date->string (current-date))
;; -> "Sat Jul 12 19:03:24+0100 2014"

(date->string (current-date) "~Y-~m-~d")
;; -> "2014-07-12"

(string->date "2014-07-12" "~Y-~m-~d")
;; -> date object

(date->julian-day (current-date))
;; -> 7075728736710773/2880000000

(julian-day->date 7075728736710773/2880000000)
;; -> date object
上記以外にも日付⇔時間、修正ユリウス暦⇔日付or時間、TAI時間⇔UTC時間等の変換手続きが用意されています。

個人的に日付⇔文字列の変換は拡張性が無く、またXSD等が採用しているISO-8601のフォーマットのタイムゾーン形式に対応していなかったりと、多少使いにくい点があります。日付のアクセサは用意されているのでその辺は自前で書けということなのかもしれませんが・・・

今回はSRFI-19を紹介しました。

2014-07-09

Unicode character set

I'm implementing SRFI-115 on top of builtin regular expression engine adding missing feature to implement and struggling with its Unicode requirement. Even though the SRFI says Unicode support is an optional feature and those Unicode related SREs have no effect if it's not supported.

However I don't like taking conservative way. It's always better to implement full feature so that users don't have to consider implementation's limitation. Now the problem is how and compatibility with built in engine. If Unicode is supported then SRFI requires, for example, alphabetic to match L, Nl and Other_Alphabetic characters. Current builtin engine only considers ASCII for named character sets (e.g. [[:alpha]] or even \w). Well there are bunch of options to resolve this but followings are, I think, rational;
  1. Support these only in this SRFI
  2. Builtin engine should support Unicode as well
Option #1 is the easier way  to do it. Option #2 would break backward compatibility so I need to be very careful especially \w. I believe I've already wrote bunch of code which depend on the fact that it only matches ASCII characters.

Let's think about #1 first. This must be relatively easy so that I just need to convert the named character set to Unicode character set or ASCII character set depending on the expression. The problem, if I dare to call, is that I need to prepare 2 types of the same named character sets; one of them is already there, though. So all what I need are adding full Unicode character sets and switch them according to the context. Easy isn't it?

Now option #2. The problem is backward compatibility. There are 2 main possible breaking compatibility issues; one is regular expression itself and the other one is SRFI-14. The whole point to do this option is make my life easier for later stage to merging Unicode character sets to predefined ones such as char-set:letter. For regular expression engine, I probably just need to make sure by default it's ASCII context. However I'm not even sure whether or not I wrote a piece of code with SRFI-14 that depending on ASCII character set. (quick grep showed I have, so if I merge it then I need to check all code...)

So if I take #2 then the following things need to be done;
  • Separating Unicode/ASCII context on builtin regular expression engine
  • Adding full unicode set to builtin character sets (including SRFI-14)
  • Checks if there is a piece of code which depends ASCII charset
I feel somewhat it doesn't worth but cleaner than #1. Well, it's always good to have some challenges so I should take the harder way.

2014-07-05

SRFI-18の紹介

(LISP Library 365参加エントリ)

SRFI-18はマルチスレッドを扱うライブラリです。POSIXが採用しているスレッドモデルとほぼ同じなのでなれている人はほぼ同じように使えます(条件変数の扱いが多少違うのと、ミューテックスが破棄された状態を持つのが違う点かと思います)。

使い方を見てみましょう。以下は簡単なスレッドの作成です。
(import (srfi :18))
(let ((t (thread-start! (make-thread (lambda () (write 'a))))))
  (write 'b)
  (thread-join! t))
;; -> <unspecified>
;; prints ab or ba
スレッドの作成にはmake-thread、開始にthread-start!、終了を待つのにthread-join!を使います。

スレッドを使っていると数秒止めたくなることもあるでしょう。そんなときにはthread-sleep!を使います。
(import (srfi :18))
(thread-sleep! 1) ;; sleep 1 second
一応スレッドの停止(破棄)もサポートされています。このオペレーションは非常に危険なので注意して使えという注釈も付いています。
(thread-terminate! (current-thread)) ;; -> never return
thread-yieldは他のスレッドに処理時間を明け渡す手続きです。非常に重たい処理を行うスレッドが可能な限りCPUの空き時間に処理を行いたい場合などに使えます。
(import (rnrs) (srfi :18))

(define m (make-mutex))
(define (do-something-else) (print 'something-else) (thread-sleep! 1))

(let ((t (make-thread (lambda ()
                        ;; a busy loop that avoids being too wasteful of the CPU
                        (let loop ()
                          (if (mutex-lock! m 0) ; try to lock m but don't block
                              (begin
                                ;; Do some heavy process
                                (display "locked mutex m")
                                (mutex-unlock! m))
                              (begin
                                (do-something-else)
                                (thread-yield!) ; relinquish rest of quantum
                                (loop))))))))
  (mutex-lock! m 0)
  (thread-start! t)
  (thread-sleep! 1)
  (mutex-unlock! m))
僕自身はそんなミッションクリティカルな処理を書いたことが無いので使ったことのない手続きの一つですが・・・

次にミューテックス及び条件変数の使い方を見てみましょう。ミューテックスを扱う手続きは既に上記の例で出ていますが、ロックを書けるのにはmutex-lock!、外すのにはmutex-unlock!を使います。また、このSRFIで採用しているモデルでは条件変数はシグナルを送る以外の手続きを持っていないので、pthreadのpthread_cond_waitのようなことをするにはmutex-unlock!を使います。mutex-unlock!のオプショナル引数が条件変数を受け取った場合には、pthread_cond_waitとほぼ同様の動作をします。ただし、ミューテックスはロックされていないので、注意が必要です。

今回はSRFI-18を紹介しました。

2014-07-01

You gotta LOVE side effect...

Recently I've implemented the procedure that checks if given closure is transparent/no side effect. Then I thought that's cool so that compiler can eliminate some of no-side-effect expression or compute the result in compile time if the given argument is constant variable. Now I've got a problem.

To describe it, here is the small piece of code which causes the problem.
(import (rnrs))

(define label-counter (lambda () 0))
(define (new-lbl!) (label-counter))

(define (change-label-counter)
  (set! label-counter (lambda () 1)))

(define (print . args) (for-each display args) (newline))

(let ((lbl (new-lbl!)))
  (print lbl))
(change-label-counter)
(let ((lbl (new-lbl!)))
  (print lbl))
This should print 0 then 1. Well, it doesn't seem there is a problem except the change-label-counter. The defined label-counter is obviously constant so it'd be inlined in the new-lbl!. Now, the change-label-counter sets the new value to label-counter, here comes the problem. The new-lbl! has already compiled with inlined value and just returns 0 so change-label-counter doesn't have any effect to it. Then the result value would be always 0.

If these procedures are defined in a library, then Sagittarius compiler could check if the global defined variable would be changed or not however because this is defined in a script it can't and just tries to inline so that the target procedure looks constant.

What I can do is;
  • Don't inline them if it's in script.
  • Forbit set! for globally defined variable.
    • Make impossible to run some of R5RS code (e.g. Gambit benchmark's compiler)
  • Read all expression and wrap with a library before compiling a script
    • I can already see the horrible future...
Hmmmm, I guess I need to disable this check for now...

2014-06-30

Named let performance

When I was playing around with Sagittarius compiler, I've found that some of named let expression was compiled to (looks) slow code. Following piece of code is one of them;
(lambda (pred lst)
  (let loop ((lst lst))
    (cond ((null? lst) '())
          ((pred (car lst)) (loop (cdr lst)))
          (else (cons (car lst) (loop (cdr lst)))))))
You can see this type of code anywhere (although it's not tail recursive so I don't like it). And you would expect this not to have any performance issue other than stack consumption because of the recursive call.

Now, I was also thinking like that however things are bit worse. The compiled code describes everything;
;; size: 3
;;    0: CLOSURE #&lt;code-builder #f (2 0 0)>
;;   size: 13
;;      0: UNDEF
;;      1: PUSH
;;      2: BOX(0) !!!! IMPLICIT BOXING !!!!
;;      3: LREF_PUSH(0)
;;      4: LREF_PUSH(2)
;;      5: CLOSURE #&lt;code-builder loop (1 0 2)> !!!! CLOSURE CREATION !!!!
;;     size: 26
;;        0: LREF(0)
;;        1: BNNULL 3                  ; ((null? lst) '())
;;        3: CONST_RET ()
;;        5: FRAME 4
;;        7: LREF_CAR_PUSH(0)
;;        8: FREF(1)
;;        9: CALL(1)
;;       10: TEST 6                    ; ((pred (car lst)) (loop (cdr l ...
;;       12: LREF_CDR_PUSH(0)
;;       13: FREF(0)
;;       14: UNBOX
;;       15: LOCAL_TAIL_CALL(1)
;;       16: RET
;;       17: LREF_CAR_PUSH(0)
;;       18: FRAME 5
;;       20: LREF_CDR_PUSH(0)
;;       21: FREF(0)
;;       22: UNBOX
;;       23: LOCAL_CALL(1)
;;       24: CONS
;;       25: RET
;;      7: LSET(2)
;;      8: LREF_PUSH(1)
;;      9: LREF(2)
;;     10: UNBOX
;;     11: LOCAL_TAIL_CALL(1)
;;     12: RET
;;    2: RET
I've put comments where it causes performance issue.

The first one, implicit boxing, is because the loop is transformed to letrec and it needs to refer loop itself inside of it. The second one is creating a procedure each time the top procedure (I haven't named it though) is called because it has a free variable pred inside. Sounds reasonable? No, it doesn't at all to me now. If you write a piece of code with named let, then you would expect the calling named procedure would be compiled to just a jump. However this is compiled to a procedure call. Well, on the other hand this is not a tail recursive call so you wouldn't expect to be a jump though. Actually if the code was tail recursive then compiler would compile it to a simple jump without implicit boxing and creating a closure.

Now, then should users always be careful or adjust their code how compiler compiles to maximumised performance instructions? My answer is yes and no. In above case, I expect at least compiler should emit non implicit boxing code. In general, compiler should be smart enough to emit good quality code. Then problem is how? ... that's something I need to think, though.

2014-06-28

SRFI-17の紹介

(LISP Library 365参加エントリ)

SRFI-17は一般化したset!です。発案者はKawaの製作者Per Bothner氏ですね。(他のSRFIでも既にGuileやらLarcenyやらの製作者が出ていたのですが、文量の水増しです。)

CL使ってる方はsetfに近いものだと思ってもらえればいいです。使い方は以下のようになります。
(set! (car a) 'b) ;; -> (set-car! a 'b)
(set! (cdr a) 'c) ;; -> (set-cdr! a 'b)

(set! (vector-ref v 0) 'a) ;; -> (vector-set! v 0 'a)
実際にこのように展開されるかは実装次第なところもあるのですが、SRFI内では以下のようになると書かれています。
(set! (proc args ...) value) 
 ;; -> ((setter proc) args ... value)
例えば、Gaucheでは(set! (car a) 'b))はSRFIのように展開されますが、Sagittariusはset-car!が直接呼ばれるようにコンパイルされます(それがいいかどうかは別の問題ですが)。他の処理系は調べてません。

あまりに文量が少ないので多少余計な情報も。

このSRFIはどうやらかなり不評だったようです。
I have gotten no support for my proposal. While the srfi process allows for srfis that are controversal and don't have consensus, at this stage it seems kind of pointless to pursue it. I have no particular desire to push for something most people dislike, at least in this context. So if anybody out there thinks the generalized set! is a good idea, now is the time to speak up.
http://srfi.schemers.org/srfi-17/mail-archive/msg00049.html
この投稿が投げられる前にあったMatthias Felleisen氏が投げた投稿から始まる議論(罵り合い?)がなかなか面白いです。個人的には便利なSRFIだと思っているのですが、Scheme的ではないかもしれませんね。

今回はSRFI-17を紹介しました。

2014-06-15

How to emulate pthread_cond_wait with SRFI-18

I was implementing multi thread queue like Gauche has but in pure Scheme with SRFI-18. Then I've got confused how to wait condition variable. If it's POSIX then you can use pthread_cond_wait. However it's SRFI-18 and it doesn't provide a procedure to wait condition variable directly. Well, in the end there are some example code which explains how to do it but at that moment I couldn't figure it out. So there may be some people who also have the same issue as me. (It's basically because of my lack of knowledge of multi threading and, well, if there aren't at least this could be my memo to remember...)

The answer was a combination of mutex-unlock! and mutex-lock!. I was always thinking that pthread_cond_wait or similar procedure is the only way to wait for a condition variable. I skipped reading this section (and caused my confusion...)
NOTE: mutex-unlock! is related to the "wait" operation on condition variables available in other thread systems. The main difference is that "wait" automatically locks mutex just after the thread is unblocked. This operation is not performed by mutex-unlock! and so must be done by an explicit call to mutex-lock!. This has the advantages that a different timeout and exception handler can be specified on the mutex-lock! and mutex-unlock! and the location of all the mutex operations is clearly apparent. A typical use with a condition variable is:
(I don't complain this is very confusing but don't you think?) So what I needed to do is instead of searching something that specifically says condition-wait! or something, I needed to use mutex-unlock! and mutex-lock!. Following piece of code is more concrete example;
(import (rnrs) (srfi :18))

(define-record-type (<foo> make-foo foo?)
  (fields (immutable mutex foo-mutex)
          (immutable cv    foo-cv)
          (mutable   count foo-count foo-count-set!))
  (protocol (lambda (p)
              (lambda ()
                (p (make-mutex)
                   (make-condition-variable)
                   0)))))

;; Utilities for above foo record
(define-syntax lock-foo!
  (syntax-rules ()
    ((_ foo) (mutex-lock! (foo-mutex foo)))))
(define-syntax unlock-foo!
  (syntax-rules ()
    ((_ foo) (mutex-unlock! (foo-mutex foo)))))
(define (with-locking-foo foo thunk)
  (dynamic-wind
      (lambda () (lock-foo! foo))
      thunk 
      (lambda () (unlock-foo! foo))))

(define-syntax wait-cv
  (syntax-rules ()
    ((_ foo)
     (let ((r (mutex-unlock! (foo-mutex foo) (foo-cv foo))))
       (display "unlocked! with cv") (newline)
       ;; mutex is not locked so lock it if you need it.
       (mutex-lock! (foo-mutex foo))
       r))))

(define-syntax notify-foo
  (syntax-rules ()
    ((_ foo)
     (condition-variable-broadcast! (foo-cv foo)))))

(let ()
  (define foo (make-foo))

  (define (producer)
    ;; increment count
    (with-locking-foo foo
     (lambda ()
       (display "Increment count!") (newline)
       (foo-count-set! foo 1)
       (notify-foo foo))))

  (define (consumer)
    ;; wait until the count is one
    (with-locking-foo foo 
     (lambda ()
       (let loop ()
         (cond ((zero? (foo-count foo))
                (display "It's zero need to wait!") (newline)
                (if (wait-cv foo)
                    (loop)
                    (error #f "something went wrong")))
               (else (foo-count foo)))))))

  (let ((ct (thread-start! (make-thread consumer)))
        (pt (make-thread producer)))
    ;; consumer is waiting but make sure
    (thread-sleep! 10)
    ;; let producer increment
    (thread-start! pt)
    (thread-join! pt)
    (display (thread-join! ct)) (newline)))

#|
It's zero need to wait!
Increment count!
unlocked! with cv
1
|#
This just tries to emulate pthread_cond_wait using mutex-unlock! and mutex-lock!. The wait-cv is the emulation macro. notify-foo assumes that given foo
's mutex is locked. It took couple of hours to figure out this simple thing for me...
I haven't met any case that this mutex model is convenient but if this how it is I need to get used to it. (though, my case was just the name confusion...)

2014-06-14

SRFI-16の紹介

(LISP Library 365参加エントリ)

SRFI-16は可変長引数を受け取る手続きに対する構文です。要するにcase-lambdaです。R6RSから標準になった構文なので、あまり目新しいものは無いかもしれません。

以下のように使います。
;; because it's very famous I've become a bit lazy
;; so just a bit of taste...
(define foo
  (case-lambda
    ((c key) (foo c key #f))
    ((c key default) (ref c key default))))
上記の(いい加減な)例では、束縛される手続きは2つもしくは3つの引数を受け取ります。2つの場合は3つ目の引数にデフォルトの値として#fを設定して自身を呼びなおします。以下のように書くのとそんなに変わりません。
(define (foo c key . rest)
  (let ((default (if (null? rest) #f (car rest))))
    (ref c key default)))
ちなみに、参照実装だと上記のcase-lambdaは以下のように展開されます。
(define foo
  (lambda args
    (let ((l (length args)))
      (if (= l (length '(c key)))
          (apply (lambda (c key) (foo c key #f)) args)
          (if (= l (length '(c key default)))
              (apply (lambda (c key default) (ref c key default)) args)
              (error #f "Wrong number of arguments to CASE-LAMBDA"))))))
これだと多少手間が減る程度の恩恵しかありません。(もちろん処理系によっては引数のパックとapplyが異常なまでに安い処理系もあるかもしれませんが・・・) また、コンパイル時に手続きの呼び出しを行わない処理系だと、lengthの呼び出しが定義された分だけ呼び出されるので精神衛生上あまり好ましくありません。

さすがにこれはどうかとも思ったらしく、以前syntax-rulesのみを使った多少効率のいいcase-lambdaの実装を作っていました。(ちなみに、Sagittariusではもう少し前に書き直しています。やってることは一緒なのでsyntax-rules版にしてもいいかとは思いますが。)

これならば引数がパックされるだけでapplyを呼び出すことがないので、参照実装よりは多少効率がいいはずです(オプション引数として受け取るのと同等程度)。もう少し進めるのであれば、組み込みの構文にしてしまってコンパイラが受け取る引数をうまいこと解決するということも可能かもしれません。

今回はSRFI-16を紹介しました。RnRS準拠でもっと効率のいい実装があるよ!という方がいらしたらご一報くださいm(_ _)m