2013-05-30

FFIの可変長引数

必要がないのでサボっていたのだがGTKのバインディングをまじめに考えるなら必要になることがわかったのでちょっと頑張って実装してみた。

こんな感じで使える
(import (rnrs) (sagittarius ffi))

(define libc (open-shared-library "msvcrt.dll" #t))

(define snprintf (c-function libc int _snprintf (char* ___)))

(let ((buf (make-bytevector 10)))
  (snprintf buf 10"%d:%s\n" 100 "test")
  (print (utf8->string buf)))

(snprintf) ;; error
まだ実装が適当(与えられる引数の制限が多い)なのと、libffiのバージョンによっては正式にサポートされていないので警告文が出たりする(ぱっとソース見た感じだと特殊な処理が必要なアーキテクチャの方が少ないみたいだし、メジャーどころは要らなさそうなので、デフォルトで警告を出す必要は無いかもしれないが・・・)。

libffiで可変長の引数を扱うのは結構泥臭くて、呼び出し側は全ての引数を把握していないといけないのと、*残り*みたいな引数型はないのでffi_storageを引数個確保しておく必要がある。この制約のせいで通常の関数とは違って多少オーバーヘッドがかかるようになってしまった。

通常はC関数オブジェクトの作成時に必要な領域(引数型情報の配列)を確保しているのだが、可変長の場合は呼び出しごとに作成する必要がある。利便性を取るか速度を取るかといった感じである(ベンチマークとってないのでどれくらい性能に影響を与えるかは分かっていなかったりするが)。

あと、コールバックは可変長に対応していなかったりする。今のところ必要な場面が思いつかないのと、可変長引数を受け付けるコールバックを見たことがないというのが理由。まぁ、単なる手抜きである。

2013-05-25

MOP improvement(?)

On Sagittarius,  MOP was not totally compatible with Tiny CLOS's MOP. That's because of my laziness. However I have noticed that once I use non builtin generic class, then it's not possible to use method qualifiers. This is not good to me. So I have improved some stuff.

The problem was that it was only implemented in C code and not in Scheme code so once I used custom generic class then it won't check those qualifiers. So I have removed builtin compute-applicable-method and moved to Scheme. Then implemented all required procedures and duplicated the logic in Scheme. (I actually don't want to do this but so far I couldn't find any better way.)

Now, I can do something like this;
(import (rnrs) (clos user) (clos core) (srfi :1))
 
(define-class <my-generic> (<generic>) ())
(define-generic foo :class <my-generic>)

(define-method compute-applicable-methods ((gf <my-generic>) args)
  (let ((m* (generic-methods gf)))
    (let-values (((supported others)
                  (partition (lambda (m) 
                               (memq (method-qualifier m)
                                     '(:before :after :around :primary)))
                             m*)))
      (for-each (lambda (m) (remove-method gf m)) others)
      (let ((methods (call-next-method)))
        (for-each (lambda (m) (add-method gf m)) others)
        (append others methods)))))

(define-class <human> ()())
(define-class <businessman> (<human>) ())
(define-method foo :append ((h <human>))
  (print "something else")
  (call-next-method))

(define-method foo :around ((h <human>))
  (print "human around before")
  (call-next-method)
  (print "human around after"))

(define-method foo :before ((h <human>))
  (print "human before"))
(define-method foo :before ((b <businessman>))
  (print "businessman before"))
 
(define-method foo ((h <human>))
  (print "human body"))
(define-method foo ((b <businessman>))
  (print "businessman body"))
 
(foo (make <businessman>))
#|
something else
human around before
businessman before
human before
businessman body
human around after
|#
I have no idea what I'm doing in above code!! Well, default implementation of method qualifier refuses non supported keywords so first remove other keywords from generic function then compute builtin qualifiers and adds the removed ones. At last append the non supported qualifier methods in front of the computed ones. The result is the other qualifier one is called first then the rest. If you put this append after the around method then all methods need to call call-next-method otherwise it won't reach there.

I have no idea if I will use this or not but at least I have something if I want to change the behaviour!

2013-05-24

総称関数の:beforeとか

ほぼ初めて実用でこの辺の機能を使おうとしてふと不満に思ったこと。

SagittariusのCLOSはXeroxのTiny CLOSの動作を基本にして作られていて、:beforeとかもその動作を元にしている(たぶんこれは以前にも書いた気がする)。っで、ふとそれだとまずいというか、嬉しくないなぁというパターンが出てきて、ちょっと動作のおさらいをしている。

とりあえずは、以下のコード
(import (rnrs) 
        (rename (clos user)
                (define-class %define-class)
                (define-method %define-method))
        (srfi :0))

(define-generic foo)
(define (print . args) (for-each display args) (newline))
(cond-expand
 (mosh
  (define-syntax define-class
    (syntax-rules ()
      ((_ name parents (slots ...))
       (%define-class name parents slots ...))))
  (define-syntax define-method
    (syntax-rules (:before :after :around)
      ((_ name :before (specifiers ...) body ...)
       (%define-method name 'before (specifiers ...) body ...))
      ((_ name :after (specifiers ...) body ...)
       (%define-method name 'after (specifiers ...) body ...))
      ((_ name :around (specifiers ...) body ...)
       (%define-method name 'around (specifiers ...) body ...))
      ((_ name (specifiers ...) body ...)
       (%define-method name (specifiers ...) body ...)))))
 (sagittarius
  (define-syntax define-class (identifier-syntax %define-class))
  (define-syntax define-method (identifier-syntax %define-method))))

(define-class <human> ()())
(define-class <businessman> (<human>) ())
(define-method foo :before ((h <human>)) 
  (print "human before"))
(define-method foo :before ((b <businessman>)) 
  (print "businessman before"))

(define-method foo ((h <human>)) 
  (print "human body"))
(define-method foo ((b <businessman>)) 
  (print "businessman body"))

(foo (make <businessman>))
#|
businessman before
human before
businessman body
|#
まじめに書いてないのでまともに動きはしないのだが、Moshとの互換レイヤが入っている。気になるのは出力結果。動作を合わせてあるので現状は同じ出力を返すのだが、「human before」はcall-next-methodがあった場合にのみに出力されてほしい気がする。というか、そうじゃないと綺麗に書けないコードを書いていて、もにょっている感じ。

本家のCLではどうなっているのかもついでに試してみた(これも以前試したっけ?)

(defclass human () ())
(defclass businessmane (human) ())

(defmethod foo :before ((h human)) (print "before human"))
(defmethod foo :before ((h businessmane)) (print "before businessmane"))

(defmethod foo ((h human)) (print "body human"))
(defmethod foo ((h businessmane)) (print "body businessmane"))

(foo (make-instance 'businessmane))
#|
"before businessmane"
"before human"
"body businessmane"
|#

あぁ、本家もそうなのか。そうなると逸脱するのも微妙だなぁ・・・

2013-05-22

CLOS based GUI library

Most of the script I have written so far is command line script and I didn't feel any inconvenience with it. However, sometimes I felt like if there is GUI to do it, it might make my life easier.

So I have decided to write a GUI library using FFI binding (currently working only Windows or Cygwin). There is a huge problem that is I have never written it before so I have no idea what is the best way to do it. After couple of days, I decided to use CLOS based method (named by me :-P).

It's not done yet but the code looks like this;
(import (clos user) (turquoise))

(let ((w (make <frame> :name "Sample" :width 200 :height 200))
      (b (make <button> :name "ok" :x-point 37 :y-point 50)))
  (add! w b)
  (add! b (lambda (action) (print action)))
  (show w))
The library (turquoise) is the GUI library. This piece of code just shows a window contains a button which prints action argument to standard output when it's pressed. The basic idea is using class as a component representation the same as other modern libraries and show thoese classes to users so that they can extend it easily.

I'm not sure if I should make add-component! or add-action-listner! instead of using generic method add!. If you have opinions about it, please let me know.

2013-05-19

公用語が英語な会社

この記事が書かれたきっかけ
  1. 自分の会社が最近オランダ人を雇っていないと気づく
  2. これって募集要項が英語だからじゃね?
  3. そういえば公用語を英語にするって表明した会社があったなぁ
  4. 募集要項を英語のみにすれば必然的に英語ができるかつ優秀なやつがくるんじゃね?
という流れで、どれくらいの企業が日本人向けの募集要項を英語で出しているのか調べてみた。(ちなみに、自分が今勤めている会社はオランダ語の募集要項は無い。ってか、サイトにオランダ語の選択肢すらない。いいのか、それで?)

楽天とユニクロは有名だけど、他にどこがあるんだろうとまず調査。以下のNaverのページに載っていた公用語を英語にすると発表した企業に限定(昇進に英語必須とか含めると大変だったのでw)
楽天とユニクロ以外に「社内英語公用語」を発表している企業様まとめ 
っで以下が結果。

【日本語で募集】
楽天、ユニクロ、日産、SHARP、 日本硝子

【英語で募集】
SMK

SMKはロケールで変わるのかもしれないが、トップページがSMK Japan in Englishに飛ばされたので。日本語ページを見ると日本語の募集要項が載っていた。その他の企業は基本トップページが日本語だった、ひょっとしたら英語ページは英語なのかもしれない。

別に何ということもないのだが、個人的には企業がどれくらい公用語英語をまじめに実践しているかということを外からしる一つの指標になるのではないだろうかと思ったり。もちろん実情は知りようがないし、面接は英語で行われるとかも調べていないが・・・

ついでに、企業がまじめに英語を公用語にしているということは、「英語は話せて当たり前、その上で何ができるの?」というスタンスでいると思うので、「英語話せます」だけでは採用されないということだと思ったりもしている。というか、意思疎通の手段としているわけだからそこを言及すること自体そもそもおかしいわな。

2013-05-17

Sagittarius 0.4.5 リリース

Sagittarius Scheme 0.4.5がリリースされました。今回のリリースはメンテナンスリリースです。
ダウンロード

修正された不具合
  • define-c-structがアライメントを無視したサイズの構造体を作る不具合が修正されました
    • この修正によって(sagittarius ffi)がより正確なアライメントを計算するようになりました
  • parameterizeが値を正しく復元しない不具合が修正されました
  • (/ 1 -0.0)が+inf.0を返す不具合が修正されました
  • sashの-Iオプションがエラーを投げる不具合が修正されました
改善点
  • キャッシュファイルがマルチプロセス環境でより安全になりました
    • これによりmake -j nでのビルドが可能です
  • parameterのメモリ使用量が少なくなりました
新たに追加された機能
  • eql sepecializerが組み込みになりました
  • open-shared-libraryが共有ライブラリが見つからなかった際にエラーを投げるオプショナル引数を受け付けるようになりました
  • generate-secret-keyでDES3の秘密鍵を生成する際に8及び16バイトの鍵を受け付けるようになりました
  • lock-port!及びunlock-port!が追加されました(明文化はされていません)
  • call-with-port-lockが(util port)に追加されました(明文化はされていません)
新たに追加されたドキュメント
  • (dbi)及び(odbc)のドキュメントが追加されました

2013-05-16

Should let-syntax family make a scope?

On R7RS ML, there was a discussion for this topic and I'm wondering about it.

On current draft of R7RS, it says 'The let-syntax and letrec-syntax binding constructs are anologous to let and letrec' so I would say it should make a scope. However the reference implementation, Chibi Scheme, doesn't.

Following quote is from the ML:
Please try to keep a grip on the fact that R7RS-small `let-syntax`, like the R5RS version, is a scope rather than being spliced into the surrounding scope. See ticket #48 and WG1Ballot2Results.

From http://lists.scheme-reports.org/pipermail/scheme-reports/2013-May/003439.html
However, seeing this issue from Chibi Scheme, it's not making a scope intentionally.

The reason I'm wondering about it is actually I don't want to make a scope for neither let-syntax nor letrec-syntax. Current compiler checks the mode and switches the path. Well, it's not heavy operation so it won't improve much performance if I remove it (just a tiny bit). However it does make a big change to write some memory efficient code.

Let's say you want to write sort of following code:
#!r6rs
(library (foo)
    (export command1 command2)
    (import (rnrs))
  (letrec-syntax ((define-command
                    (syntax-rules ()
                      ((_ name body ...)
                       (define name (lambda () body ...))))))
    (define-command command1 (display 'command1) (newline))
    (define-command command2 (display 'command2) (newline))))
This only works when you put #!r6rs annotation on the library defined file (I'm talking about Sagittarius). So, if you want to make some keyword argument or so, then you need to remove the annotation and make define-command global binding.

You might say, "as long as you don't export define-command, then it won't harm your code.", well sort of yes. The problem is acutally on cache file. If you define global macros, then cache file must contain it since we can access it without exporting, using with-library macro. (I know it's a back door stuff, but it's sometimes convenient!) However, if we use let-syntax then cache file won't have it because all local macros are already expanded.

On Sagittarius, if you really don't want to export internal macro you need to write it like above in R6RS mode. Then you don't have any chance to use keyword features.

Should I follow Chibi's behaviour or what R7RS (implicitly) says?

2013-05-14

パラメタ

面白いというか、目の付け所が鋭いバグ報告が届いた。(面倒なバグとも言う・・・)

具体的な再現コードは以下のようになる。
(import (rnrs) (srfi :39))
(define x (make-parameter 3 (lambda (x) (+ x 3))))
(print (x))
(parameterize ((x 4)) (print (x)))
(print (x))
#|
6
7
9
|#
SRFI-39的にもR7RS的にも最後の9は6じゃないといけない。原因はすぐに分かったんだけど、問題はどう解決するかという部分。

ちなみに、原因はparameterizeとparameterの実装にある。parameterizeはdynamic-windを使って実装されているのだが、afterのthunkが値をセットしなおす際に保存された値が変換されるという悲しいことがおきる(この場合は6が保存されて、6をセットする際に9に変換される)。 まぁ、解決方法は簡単で、after thunkで保存された値をセットする際に、変換を行わなければいい。

言うは易し、行うは難しの典型である。なぜか、そんなAPIが無いからだ。現状ではパラメタはYpsilonの実装を移植したものを使っている。この実装ではパラメタは単なるlambdaである。つまり、その中身にアクセスする方法などないということだ。(もちろん、同様の問題がYpsilonでも発生する。ちなみにChezでも起きた。意外とこのバグはいろんな実装で穴になっているっぽい。)

とりあえずぱっと思いつく解決方法は2つ。
  1. パラメタ作成時に直接値を設定できる手続きを作って一緒に保存する
  2. せっかくobject-applyがあるんだし、CLOSで実装してしまう
1の方法だと使用するメモリの量が不安になる、単純計算でパラメタ作成にかかるメモリコストが倍になる。
2の方法だとパラメタを呼び出すときのオーバヘッドが気になる。なんだかんだで総称関数の呼び出しは現状では重たい。

さて、どうしようかな。

2013-05-13

eql specializer

This article is continued from the previous one (in Japanese).

Sagittarius has the library to do eql specializer however it was a bit limited if I wanted to use it from C level. So I wanted it builtin, yes I made it :)

Basically almost nothing is changed to use just it became a bit convenient. So you can write it like this (without (sagittarius mop eql) library);
(import (clos user))
(define-method fact ((n (eql 0))) 1)
(define-method fact ((n )) (* n (fact (- n 1))))
(fact 10) ;; -> 3628800
Now you don't have to define a generic procedure before defining methods.

Current implementation is simply ported from the old library so it might not be so efficient. (Well, it's written in C now so it should be faster than before!)


Ah, I thought I wanted to write more but guess not :(

暗号ライブラリの不満

Sagittariusは自前で暗号ライブラリを持っているのだが、ちょっと不満が出てきた。何が不満かと言えば、DES3の鍵生成で必ず24バイト要求するというものだ。これだといわゆるDES2が使えなくて、でも16バイトの鍵がわたってきた場合に困ることになるというもの。

現状では秘密鍵の生成は以下のようにして鍵オブジェクトを作ってやる必要がある。
(import (crypto))
(generate-secret-key DES3 #vu8(...)) ;; must be 24 byte bytevector
generate-secret-keyは総称メソッドなので、DES3がわたってきた際に特異なものを作ってやればいいという話になる。

本来ならという注釈がつくのがポイント。ドキュメントにちょろっと書いてあるのだが、この鍵アルゴリズムの名前は専用のクラスを作って唯一のオブジェクトを登録するのが望ましい、と書いてあるだけだったりする。つまり、なんでもいいのである。 (今思ったのだが、なんでオブジェクトを作る必要があるんだ?クラスだけでもいいんじゃね?) っで、その裏ルールに則って組み込みの秘密鍵なアルゴリズムは文字列が登録されていたりする。(これはバックエンドで使っているLibTomCryptoが文字列でディスクリプタを登録しているため)

そんなときのためにeql-specializerがあるんじゃないかとも思ったのだが、こいつは組み込みではサポートしていないので総称メソッドの定義時にメタクラスとして指定してやる必要があってうまくいかない。

解決方法はとりあえず思いつく中でスマートなものは以下の2つ。
  1. RSAと同様に秘密鍵のアルゴリズムも文字列じゃない何かにする
  2. eql-specializerを組み込みでサポートする
1.はクラスの階層を考えたり、現在サポートしている全てのアルゴリズムに適用する必要があったりでひたすら面倒だけど王道な解決策。
2.はどこと無くadhocな感じはするが、eql-specializerが組み込みに出来るチャンスともいう。

さて、どっちにしようかな・・・

2013-05-09

FFI周りの改善

SagittariusのFFIでCの構造体を定義する際にメタな情報を付与してユーザーにアライメントの計算を強いないようにしている。ここまでは単にユーザーフレンドリな仕様で済むんだけど、内部のアライメント計算処理があまりにも適当すぎて32ビットと64ビットでの違いが吸収できてないとか、オフセット計算が間違ってて特定のメンバにアクセスするとSEGVるとかのバグがちらほらあった。

個人的にあまり問題にしていなかったのだが(Cの構造体を弄る場面が少なかった) 、なんとなく隙間の時間があったので「えいやっ!」と直すことにした。

0.4.4以前で問題になるのは以下のようなコード。
(import (sagittarius ffi))
(define-c-struct foo
  (int   i0)
  (char  c)
  (short s)
  (int   i1))
(size-of-c-struct foo)
定義された構造体のサイズは12(sizeof(int) == 4)でなければならないが、0.4.4では11を返す。これは、構造体のパディングとサイズの(意図的ではないが)ルールを無視しているためだったりする。また、メンバのオフセット計算もおかしかったりで、複雑な構造体を扱うのは危険だったりもした。(メンバが全部intとかvoid *もしくは型が違ってもサイズが同じとかなら問題はない。)

とりあえず、以下のWikipediaのページを参考にしつつ、もうちょっとまともな計算をするように改善。
http://en.wikipedia.org/wiki/Data_structure_alignment
現在のHEADではかなりまともな計算をするようになっている。(個人的に怪しい部分はあったりするが・・・)

また、define-c-structの定義をちょっと変えて、アクセサを同時に定義するようにした。たとえば上記の構造体なら以下のアクセサが自動的に定義される。
foo-i0-ref
foo-i0-set!
 ...
foo-i1-ref
foo-i1-set!
;; 参考 foo-i0-refとfoo-i0-set!の定義イメージ
(define (foo-i0-ref p)
  (c-struct-ref p foo 'i0))
(define (foo-i0-set! p v)
  (c-struct-set! p foo 'i0 v))
実際には内部構造体のメンバを扱うためにオプショナル引数を受け付ける。このオプショナル引数は個人的には美しくないと思っているので(特に-set!側)、なんとかしたいのだがいい案が思いつかない。

さらに、構造体の定義内に配列を含めた際の参照と代入が(ほぼ全く)サポートされていなかったのでそれも直した。配列で定義されたメンバの参照をすると現在はベクタを返すようになっている。代入もベクタで行う必要がある(以前は代入は自前でオフセット計算してやる必要があった)。

個人的にこの辺の機能を使うことがほとんどないので、誰かハードに使ってくれる人を募集してますw

2013-05-07

Yet Another Syntax-case Explanation

Unlikely my (own) rule, this article is in Japanese (if you want it in English, please comment so).

世の中syntax-caseの解説なんて(たぶん)山ほどあるだろうけど、もう一つGoogleの検索結果を汚してやろうという話。

この記事のsyntax-rulesは使えるけど、syntax-caseとwith-syntaxを絡めて使えないという方をターゲットとしてます。マサカリ大歓迎ですw

【syntax-caseって】
まずは、簡単にsyntax-rulesとsyntax-caseの違いを見てみよう。
(import (rnrs))
(define-syntax print-rule
  (syntax-rules ()
    ((_ o o* ...)
     (begin (display o) (print-rule o* ...)))
    ((_ o)
     (begin (display o) (print-rule)))
    ((_) (newline))))

(define-syntax print-stx
  (lambda (x)
    (syntax-case x ()
      ((_ o o* ...)
       #'(begin (display o) (print-stx o* ...)))
      ((_ o)
       #'(begin (display o) (print-stx)))
      ((_)
       #'(newline)))))
どちらのマクロも同じことをします。これだけ見れば、違いは以下ぐらい:
  • syntax-rulesがsyntax-caseになった
  • lambdaで囲まれて、syntax-caseの引数(と呼ぶのもおかしいが)にlambdaの引数が渡った
  • テンプレート部分がsyntax (#')で囲まれた
はい、この程度のものを書くならsyntax-rulesだけで十分です。じゃあ、syntax-caseを使うと何が嬉しいのか見ていきましょう。

【低レベルな操作】
何を持って低レベルとするのかはさておき、ここでは与えられて式の内容を操作することを低レベルと呼びます。syntax-rulesでは式の変形はできても、中身を操作することはできません。たとえば以下のようなコードは、syntax-rulesでは実現不可能です。
(define-syntax define-foo-prefix
  (lambda (x)
    (define (add-prefix name)
      (string->symbol 
       (string-append "foo-" (symbol->string (syntax->datum name)))))
    (syntax-case x ()
      ((k name expr)
      ;; need datum->syntax to compliant R6RS
       (with-syntax ((prefiexed (datum->syntax #'k (add-prefix #'name))))
         #'(define prefiexed expr))))))

(define-foo-prefix boo 1)
foo-boo ;; -> 1
さて、ここでwith-syntaxが出てきました。こいつが何をしているのかの説明がこの記事のメインなのでここで解説です。

構文的なものはR6RSでも見てもらえばいいとして、何をしているのか。名前が示すとおり、with-syntaxは新たに構文オブジェクトの束縛を作ります。 ここでは、prefixedがそれにあたります。なぜこんなことが必要かといえば、syntax-caseのテンプレートは構文オブジェクトを返す必要があるからです。そして、syntax (#')構文内では構文オブジェクトの束縛のみが参照可能ということも大きな要因です。上記のような、低レベルな操作健全なマクロで行うためにあるといっても問題ないでしょう。

また、with-syntaxで作られた構文オブジェクトが保持する情報も重要になってきます。R6RSではテンプレート部分にどこにも定義されていない名前が出てくると、それはユニークな名前に変更されます。define-valuesなどの定義で、dummyとか使われているあれです。しかし、with-syntaxで束縛された構文は束縛された名前がそのまま使えます。

いまいちイメージがつかめない方のために、R5RSとCommon Lispのマクロの議論でよく引き合いに出されるaifをwith-syntaxを使って書いて見ましょう。こんな感じの定義になると思います。
(define-syntax aif
  (lambda (x)
    (syntax-case x ()
      ((_ pred then)
       #'(aif pred then #f))
      ((k pred then else)
       ;; ditto
       (with-syntax ((it (datum->syntax #'k 'it)))
         #'(let ((it pred))
             (if it then else)))))))
(aif (memq 'a '(b c a e f)) it 'boo) ;; -> (a e f)
aifはpred部分の評価結果を変数itに暗黙的に束縛します。なので、マクロユーザからはその定義は見えず、いきなり現れたように見えます。Schemer的にはいまいち気持ち悪い気もしますが、あれば便利な機能です。
ここで使われているwith-syntaxが何をしているかといえば、シンボルitを構文オブジェクトに変換してテンプレート内で参照可能にしています。このitはリネームされないため、あたかも突如現れたかのように使うことができるのです。

【それ、quasisyntaxでもできるよ?】
はい、できます。with-syntaxとquasisyntaxはほぼ同等の力を持っていると思っていいです。なので、上記の例は以下のように書き換えることが可能です。
(define-syntax define-foo-prefix
  (lambda (x)
    (define (add-prefix name)
      ;; ditto
      (datum->syntax name
       (string->symbol 
 (string-append "foo-" (symbol->string (syntax->datum name))))))
    (syntax-case x ()
      ((_ name expr)
       #`(define #,(add-prefix #'name) expr)))))

(define-syntax aif-quasi
  (lambda (x)
    (syntax-case x ()
      ((_ pred then)
       #'(aif pred then #f))
      ((k pred then else)
       ;; ditto
       (let ((it (datum->syntax #'k 'it)))
       #`(let ((#,it pred))
           (if #,it then else)))))))
どちらの方がいいかはユーザの感性に寄るとは思いますが、個人的にはwith-syntaxを使った方が綺麗かなぁと思います。quasisyntaxはCommon Lispのマクロを連想させるという理由だけなのですが・・・。ただ、あまりに多くの構文を導入する必要があると無駄に長くなるという弊害もあるので、ケースバイケースで使い分けるのがいいでしょう。

追記:
Twitterで早速突っ込みが入ったのでdatum->syntaxで明示的に構文オブジェクトに変換するようにコードを修正。SagittariusとYpsilonはこの辺多少チェックがゆるい。
それに伴って多少文言の修正。with-syntaxは構文オブジェクトを束縛する等々。