Let's start Scheme

2016-10-19

Connection and session model

CAUTION: It's rubbish. Just my brain storming.

I'm not even sure if this is a proper model name or not. But I often see in low level communication specification, there is a connection and sessions (or channels) belong to the connection. Sometimes (or most of the time?) channels can be recovered on different connections, but here I discuss non recoverable channel for my ease.

I've written couple of scripts which handles this type of things already. For example AMQP. I think one of the pros for this model is preventing actual connection count. Recently, I've faced a concurrency problem caused by reading data from one single connection. At that moment, I didn't want to create much of connections (but in the end realised that there's no other way, though). Then I've started thinking about it.

Now I'm thinking can this be done in generic way or at least something which can be reused instead of writing the same mode everywhere? Suppose,a connection have a port. If a session is derived from the connection, then connection gives the session a session id or so. Whenever data are read from sessions, then actual connection dispatches read data according to the required session id. Something like this:
(import (rnrs))

(define-record-type connection
  (fields port ;; suppose this is input/output port
          sessions)
  (protocol (lambda (p)
              (lambda (port)
                (p port (make-eqv-hashtable))))))
(define-record-type session
  (fields id connection))

(define (open-connection port) (make-connection port))
(define (open-session connection)
  (let* ((sessions (connection-sessions connection))
         ;; FIXME
         (id (+ (hashtable-size sessions) 1))
         (session (make-session id connection)))
    (hashtable-set! sessions id session)
    session))

(define (read-from-session session)
  ;; IMPLEMENT ME
  (read-for-session
   (session-connection session)
   (session-id session)))
To keep it clear, here connection is the physical connection and sessions are logical layers derived from a connection. A channel is a communication pipe between connection and session.

Obviously, this doesn't work as it is even if I implement reading part, because:
  1. There's no way to know which message should go which session
  2. There's no negotiation how to open a session, this type of things are usually client-server model.
To know how to dispatch messages, connections need to know how message frames look like. So connections should have something like receive-frame field which contains a procedure receiving a message frame?
(define-record-type connection
  (fields port
          ;; suppose this returns 2 values, message id and payload
          receive-frame
          negotiate-session
          sessions)
  (protocol (lambda (p)
              (lambda (port receive-frame negotiate-session)
                (p port receive-frame  negotiate-session (make-eqv-hashtable))))))
Is it good enough? But what if there are like these 2 process is simultaneously running; opening a session and reading payload from a session. So it might be a good idea to have channel like this?
(define-record-type connection
  (fields port
          receive-frame
          ;; takes session-id, channel-id and connection.
          ;; returns physical-session-id
          negotiate-session
          sessions
          channels)
  (protocol (lambda (p)
              (lambda (port receive-frame negotiate-session)
                (p port receive-frame
                   negotiate-session
                   (make-eqv-hashtable)
                   (make-eqv-hashtable))))))
(define-record-type session
  (fields id channel-id connection
          (mutable physical-session-id)))
(define-record-type channel
  (fields id buffer))

(define (open-connection port) (make-connection port))
(define (open-session connection)
  (let* ((sessions (connection-sessions connection))
         (channels (connection-channels connection))
         ;; FIXME
         (session-id (+ (hashtable-size sessions) 1))
         (channel-id (+ (hashtable-size channels) 1))
         (channel (make-channel channel-id
                                ;; IMPLEMENT ME
                                (make-buffer)))
         (session (make-session session-id channel-id connection #f)))
    (hashtable-set! sessions session-id session)
    (hashtable-set! channels channel-id channel)
    session))
Hmmm, would it be enough? I'm not sure yet. And I'm not even sure if this is useful. Maybe I need to implement a prototype.

2016-10-10

SXMLオブジェクトビルダー

なんとなくSXMLをレコード変換するフレームワーク的なものがあると便利かなぁと思って作ってみた。まだ作りこみが甘い部分もあるが、必要そうな部分は動いているのでちょっと紹介。

ライブラリは(text sxml object-builder)としてみた。基本的な使い方はこんな感じ。
(import (rnrs)
        (text sxml ssax)
        (text sxml object-builder)
        (srfi :26))

(define-record-type family
  (parent xml-object))
(define-record-type parents
  (parent xml-object))
(define-record-type child
  (parent xml-object))
(define-record-type grand-child
  (parent xml-object))

(define family-builder
  (sxml-object-builder
   (family make-family
    (parents make-parents
     (? child make-child
        (? grand-child make-grand-child))))))

(define sxml (call-with-input-file "family.xml" (cut ssax:xml->sxml <> '())))

(sxml->object sxml family-builder)
;; -> family object
family.xmlはこんな感じ。
<?xml version="1.0" ?>
<family>
  <parents father="Daddy" mother="Mommy">
    <child name="Big bro">
      <grand-child name="Bekkie">Boo!</grand-child>
      <grand-child name="Kees">Bah!</grand-child>
    </child>
    <child name="Sis"/>
  </parents>
</family>
sxml-object-builderマクロはなんとなくいい感じにオブジェクトビルダーを構築してくれる。基本的には(tagname constructor . next-builder)みたいな構文。next-builderが空リストだと渡ってきたSXMLそのまま使う感じ。後は量指定子だったり、複数タグにしたり。最終的にはXSDと同等の表現力を持てたらなぁとは思っている(ほぼカバーしてるつもりだけど、仕様書を真面目に読んだことがないので何が足りてないのかが分かっていないという…)。

ここではレコードを定義してるけど、constructorは3引数受け取る手続きなら何でもいい。便利かもしれないということで、xml-objectなるものをデフォルトで用意してるけど、特に使う必要はない。

これだけだとイマイチ嬉しくないんだけど、もうひとつSchemeオブジェクトからSXML変換するライブラリを考えていて、それがあれば多少嬉しくなる予定ではいる。

2016-10-06

スペシャリテ

料理の話ではない。

Scheme処理系は世の中に山ほどある。RnRS準拠(n≧5)に絞ったとしてもかなりある(というかR5RSが多い)。そこで、有名処理系が選ばれる理由を考えてみた。

有名所の処理系

R5RS
  • Chicken
    • Eggが便利
    • Cに変換すれば高速に動く
    • コミュニティが大きい
R6RS
  • Chez
    • 現状最高速の処理系
  • Guile
    • GNU公式拡張言語
    • コミュニティが大きい
  • IronScheme
    • .Netで動く
  • Larceny
    • 高速
    • R7RSハイブリッド
  • Racket
    • 高速
    • コミュニティが大きい
    • Planetが便利
    • リッチな環境がついてくる(DrRacket)
  • Mosh
  • Ypsilon 
  • Vicare
    • ファンが多い?
R7RS
  • Chibi
    • コンパクト
    • C拡張が書きやすい
  • Gauche
    • 日本語ドキュメント
    • 手軽?
  • Kawa
    • JVMで動く

あんまりよく考えてないけど考察

Chicken、Guile、Racketはコミュニティの多きさ≒問題解決のしやすさとライブラリの豊富さが大きいかなぁと。Chezは元商用の処理系だけあって速い。Gaucheは日本語ドキュメントが大きいかなぁ(日本限定で考えれば)

KawaとIronSchemeは環境固定されるのでその環境で使いたいならほぼ一択状態。流石にJVMだと他にもあるけど、知名度的にはKawa一択だろう。

Larcenyがどれくらい使われているかはちと分からんけど、超有名な処理系なのでそこそこユーザーはいるんじゃないかなぁ。

Mosh、Ypsilon、Vicareはリリースが年単位でされてないから(MoshとVicareはリポジトリの更新があるけど、Ypsilonに関しては多分放棄されてる)、正直これらを選ぶ理由が見つからない。バグリポートしても直らんし。強いて言えばファンが多いくらい?

スペシャリテ?

じゃあどうするか?そもそも知名度が低いという点でどうしようもないんだけど、宣伝するにしても「それなら〇〇でいいじゃん」ってことにならないようにしたい。っで、現状ある武器で考えると以下かなぁ:
  • R6RS/R7RSハイブリッド
  • リーダーマクロ
  • そこそこ手軽
  • Cトランスレータ有り(バイナリ走らせるのにランタイムがいるけど)
挙げてみて思ったけど、ユーザー視点でいいことないなぁ。これだと「〇〇でいいじゃん」にしかならない。将来的に入れようかなぁと思っているのは日本語ドキュメントなんだけど、入れたところでGaucheの牙城を崩せる気はしないしなぁ。

地道にライブラリを増やすとか、環境を整えるとかなぁ。