Syntax highlighter

2014-10-31

SRFI-37の紹介

(LISP Library 365参加エントリ)

SRFI-37はargs-fold: プログラム引数処理器です。 何をするかといえば、プログラムに渡された引数をいい感じに処理してくれます。使い方は以下。
(import (rnrs) (srfi :37))

(define options
  (list (option '(#\l "long-display") #f #f
                (lambda (option name arg seed1 seed2)
                  (values (cons 'l seed1) seed2)))
        (option '(#\o "output-file") #t #f
                (lambda (option name arg seed1 seed2)
                  (values (acons 'o arg seed1) seed2)))
        (option '(#\d "debug") #f #t
                (lambda (option name arg seed1 seed2)
                  (values (acons 'd arg seed1) seed2)))
        (option '(#\b "batch") #f #f
                (lambda (option name arg seed1 seed2)
                  (values (cons 'b seed1) seed2)))
        (option '(#\i "interactive") #f #f
                (lambda (option name arg seed1 seed2)
                  (values (cons 'i seed1) seed2)))))

(let-values (((opts operands) (args-fold (command-line) options
                                         (lambda (option name arg seed1 seed2)
                                           (values (acons '? name seed1) seed2))
                                         (lambda (arg seed1 seed2)
                                           (values seed1 (cons arg seed2)))
                                         '() '())))
  (write opts) (newline)
  (write operands) (newline))
上記を例えば以下のように実行すると、
% sash test.scm -l --output-file=a.out -d the rest of argument
こんな感じの出力が得られます。
((d . #f) (o . "a.out") l)
("argument" "of" "rest" "the" "test.scm")
このSRFIはかなり柔軟に作られていて、引数の順番は定義順である必要がありません。また、短いオプションではスペースを挟んでも挟まなくてもよく、長いオプションでは=の変わりにスペースが使えます。

肝になる点は以下の3点です。
  • option手続きによる引数が何をとるかの指定
  • args-foldの第3引数の定義外オプションの扱い手続き
  • args-foldの第4引数のオプション以外の引数の扱い
args-foldという名の通り引数を畳み込んでいくイメージで使います。

正直このままでは使いにくいなぁと思ったので、Sagittariusではこれを薄いマクロで包んだ(getopt)というライブラリを提供しています。

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

2014-10-27

ふと思い出した話

僕がまだ日本で働いていたときのことである。当時働いていたのは町工場がそのまま世界規模の会社になったような体制の会社であった。それがいい悪いは置いておいて、そういう会社であった。僕が入社した少し前から年功序列ではなく成果主義へという暗黙のスローガンを掲げているという話でもあった。年功序列がいい、成果主義がいいという話でもないので、その辺を期待しているの荒期待はずれになると思われる。

その会社では成果主義を達成するため(かどうかはしらないが)として総合職、一般職の2種類の給与体系(名目上は違うが、ほぼ給与体系だった)の他に役職のようなものがあった。部長、課長のような大まかなものではなく、ランク付けのようなものである。よく覚えていないのだが、最初がBで次がGとかだった記憶である。またBのランクでも1~5まで格付けがあって格によって給料が違ってきた。僕は転職組みであったが、経験年数が2年(前職を二年で辞めたのだよ)と短かったのでほぼ新卒と同じ扱いだった記憶である。

生ぬるい仕事と、募集要項に記載されていた仕事内容との剥離に嫌気がさして辞めようとしていた矢先に格付けを上げる研修なるものに行かされた。研修用に論文と呼ばれる職務感想文を書かされ、研修施設に週末1泊2日で送られた。その間の給料はでないけど、ほぼ強制だった記憶である。(1ヶ月後には辞めるのに出る必要がるのか?と上司に聞いたら、「出ろ」という話だった記憶。) 研修内容は、2日に渡る各自が書いた論文の内容に関しての質疑応答及び議論であった。

研修施設に行くと監督者から夕食後は各自の自由だが、これを気に先輩社員や同期との交流を深めるといいというようなアドバイスがあった。要するに適当な時間まで飲み会やるから参加しろよという意味である。僕はといえば、体調が優れなかったこともありさっさと寝てしまった。話によると飲み会は日付が変わっても続いていたようである。

あまりやる気のないなか2日目の研修が始まり、自分の感想文を発表する順番が来た。まぁ特に何かがあるわけでもなく、概ね無難に終わったような気がする。その後昼食時に同じグループの参加者と話をしたのだが、その際今でも理解できない面白いことを言われた。要約すると以下の3つである。
  • 会社に不満があるならもっと人と話をするべきだ
  • 昨日の飲み会はその一つのチャンスだったはずだ
  • そういうのに積極的に参加しないのが問題ではないか?
僕が体調が優れなかったから寝たのだと言うと、それならしょうがない、みたいな風になったのだが、個人的には今でも理解できない面白い意見だと思っている。言った人の名前すら覚えていないのだが、悪い奴ではなく会ったのが辞める直前の職場でなかったら友人になれていたのではないかなぁと思うくらいにはいい奴だった記憶である。彼の言わんとしていたことは分からなくもない。特に最初の項目はその通りだと思う。2つ目以降は正直理解できない。個人的には飲み会みたいな場で会社の不満を吐くのが好きではないのと、その場で成された議論を職場でもう一回するのは労力の無駄だと思っている。

例えば今の職場では夏にBBQ、冬にクリスマスパーティ等あり、毎週金曜日はBeer o'clockと呼ばれる会社の金でビール飲み放題な日がある。参加するも自由、しないも自由で、参加しなかったら次の日からバツの悪い雰囲気になるわけでもない。逆に言うと、参加したからといって職場環境がよくなるわけでもない。あくまで経費会社負担の単なるイベントである。同僚との関係も悪くないし、自分の財布を痛めるわけでもないので割りと積極的に参加しているが、娯楽の一環に過ぎない。(そういえば、最初の会社のときは新入社員歓迎会と称した飲み会があったけど、あれ自腹だったなぁ。さすがに新入社員は払ってなかったけど。)

特に何か言いたいわけではないのだが、ふと思い出した話。

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?