2017-04-26

文字列とコーデック

R6RSに於いてポートはバイナリと文字列を明確に区別している。例えば、バイナリポートに対して文字列の書き込みはできない。この2つの橋渡し役がトランスコーダになる。トランスコーダはコーデックを受け取り生成され、transcoded-port等の手続きでバイナリポートを文字列ポートに変換する。ここまでは特に問題ないだろう。

さて、ここからが問題である。以下のコードは何を出力するだろうか?
(display "\xFF;\xD8;")
期待する挙動としては"\xFF;\xD8;"がそのまま出力される、つまり0xFF 0xD8として2バイト出力されることを期待するだろうか?

上記の挙動を期待した人は手をあげなさい。
(・ω・)ノシ
手を上げた人のそのまま残りなさい、補習があります。挙げなかった人はこのまま帰ってもよいです。もちろん補習を受けてもよいですよ。

さて、実際の挙動を見てみよう。大抵の処理系では出力される文字列は"ÿØ"となり、これのバイナリ表現はUTF-8で0xC3 0xBF 0xC3 0x98になるだろう。勘がいい方は気づいたかもしれないが、この挙動の正体はトランスコーダの仕業である。displayに出力ポートを指定しなかった場合current-output-portが使われるのは周知のことであると思われる。current-output-portにはどんな文字列ポートが割り当てられているのだろうか?R6RSによると以下である。
These return default textual ports for regular output and error output. Normally, these default ports are associated with standard output, and standard error, respectively. (omit) A port returned by one of these procedures may or may not have an associated transcoder; if it does, the transcoder is implementation-dependent.
要約:規定の出力ポート。通常は標準出力に割り当てられる。返されるポートにはトランスコーダが紐つけられているかもしれない。もしそうならそれは処理系依存である
処理系依存である。例えばSagittariusでは(native-transcoder)が割り当てられるし、Chezはトランスコーダを割り当てていない。

処理系依存の挙動ではいまいち納得が行かないので、これを処理系非依存の挙動で書いてみる。
(call-with-bytevector-output-port
 (lambda (out) (put-string out "\xFF;\xD8;"))
 (make-transcoder (utf-8-codec)))
このコードを実行すると上記と同様の出力が得られる。文字#\xFF#\ÿなるのかというのはUCS4とUTF-8の変換表を見てもらいたい。

ではどうすれば変換せずに出力できるのか?答えは割と簡単でlatin-1-codecを使うと良い。上記のコードを以下のように書き直すと予定通りに動く:
(call-with-bytevector-output-port
 (lambda (out) (put-string out "\xFF;\xD8;"))
 (make-transcoder (latin-1-codec)))
もちろん、文字列の中に多バイト文字が混じっていた場合はエラーになるので気をつける必要があるが。

余談ではあるが、この問題を回避するポータブルな方法はR7RSにはない。つまり、文字列をバイナリ表現として扱う非処理系依存なコードは現状のR7RSでは書けないということになる。また、少なくともChibiとGaucheでは文字列"\xFF;\xD8;"displayで出力したら"ÿØ"が出力された。R7RS-largeに期待したい類の問題である。

2017-03-10

JSONユーティリティ

最近の動向として猫も杓子もJSONとなっている感じがあるが、SagittariusではJSONのサポートが薄い(Githubに拙作のJSON Toolsを置いているが本体に入れようかなぁ)ので多少手厚くしようかなぁと思いSchemeオブジェクトに変換するライブラリを書いたりしてみた。まぁ、将来的にJWTとか実装しようと思うと生のデータは扱いづらいがDSLクエリーでは効率が悪いという話もあった。

以下のように使える
(import (rnrs) (text json object-builder))

;; JSON string
(define json-string "{
  \"Image\": {
    \"Width\":  800,
    \"Height\": 600,
    \"Title\":  \"View from 15th Floor\",
    \"Thumbnail\": {
      \"Url\":    \"http://www.example.com/image/481989943\",
      \"Height\": 125,
      \"Width\":  100
  },
    \"Animated\" : false,
    \"IDs\": [116, 943, 234, 38793]
  }
}")

;; records represent JSON object
(define-record-type image-holder
  (fields image))
(define-record-type image
  (fields width height title thumbnail animated ids))
(define-record-type thumbnail
  (fields url height width))

;; JSON -> Scheme object definition
(define builder (json-object-builder
                 (make-image-holder
                  ("Image"
                   (make-image
                    "Width"
                    "Height"
                    "Title"
                    ("Thumbnail"
                     (make-thumbnail
                      "Url"
                      "Height"
                      "Width"))
                    "Animated"
                    ("IDs" (@ list)))))))

;; Scheme object -> JSON definition
(define serializer (json-object-serializer
                    (("Image" image-holder-image
                      (("Width" image-width)
                       ("Height" image-height)
                       ("Title" image-title)
                       ("Thumbnail" image-thumbnail
                        (("Url" thumbnail-url)
                         ("Height" thumbnail-height)
                         ("Width" thumbnail-width)))
                       ("Animated" image-animated)
                       ("IDs" image-ids (->)))))))

(let ((image (json-string->object json-string builder)))
  ;; do some useful thing with image

  ;; ok I want JSON string
  (object->json-string image serializer))
;; Formatted for convenience
;; -> {
;;      "Image": {
;;        "Width": 800,
;;        "Height": 600,
;;        "Title": "View from 15th Floor",
;;        "Thumbnail": {
;;          "Url": "http://www.example.com/image/481989943",
;;          "Height": 125, "Width": 100
;;        },
;;        "Animated": false,
;;        "IDs": [116, 943, 234, 38793]
;;      }
;;    }
リストとかベクタとかも扱えたりするので、まぁ要りそうな機能はざっくりあるかなぁという感じ。

CLOSを使ってJavaのアノテーションよろしくゴテゴテした感じにしてもよかったんだけど、最近そういう書き方を避けてるのと、既存のレコード等も再利用可能にするためにこんな感じのデザイン。SXMLに対してのオブジェクト構築と似た感じになってるのはそっちもそんな感じのデザインで作ってあるから。

使い勝手は(当面のJSONが必要な場面がこれなので)JWTを実装しつつ見ていくことになるが、そこまで大きな変更はないだろうなぁと思ったり。

2017-03-02

Hibernateでクエリー爆発した話

JavaでORMと言えば真っ先に思い浮かぶのがHibernateであろう。データベースをほぼJava Beansのように扱える便利なライブラリである。ともすれば、裏で何が起きているのか全く感知しなくてもいいので、SQLが嫌いな人にはなかなかに受けが良いようである(要出典)

さて、裏で何が起きているのかを意識しなくていいというのは多くの場合便利な反面問題が起きた際の検知または解決が遅れるということでもある。データベースアクセスというのは大体の場合物理ファイルへのアクセスがあり、クエリの実行にはソケットが使われるので通信が発生する。これらはパフォーマンスを大きく損なう操作でもあり、一般的(誰?)は可能な限り避けるべきとされている(要出典)。ここではHibernateによって隠されたこれらの操作が引き起こすパフォーマンス劣化の体験談を今後の戒めとして記録しておく。

問題になったのはだいたいこんな感じのテーブル。
                       +--------------+
   +--------+        |  X_User_Dep  |    +-------------+          +-----------+
   |  User  |          +--------------+           |  X_UD_Prod  |          |  Product  |
   +--------+ 1   1..* |  + ID        | 1    1..* +-------------+          +-----------+
   | + ID   | o------o |  + User_ID   | o-------o |  + UD_ID    | 1..*   1 |  + ID     |
   | + Name |          |  + Dep_ID    |         |  + Prod_ID  | o------o |  + Name   |
   +--------+        +--------------+    +-------------+          +-----------+
                        o 1..*
           |
           |
                 o 1
                       +--------------+
                       |  Department  |
                       +--------------+
                       |  + ID        |
                       |  + Name      |
                       +--------------+
Java側はこんな感じのエンティティ
@Entity
@Table(name = "User")
public class User {
    @Id
    private int id;
    @Column(name="Name")
    private String name;
    @OneToMany(mappedBy = "user")
    private Set<UserDepartment> userDepartments;
}

@Entity
@Table(name = "Department")
public class Department {
    @Id
    private int id;
    @Column(name="Name")
    private String name;
    @OneToMany(mappedBy = "department")
    private Set<UserDepartment> userDepartments;
}

@Entity
@Table(name = "Product")
public class Procdut {
    @Id
    private int id;
    @Column(name="Name")
    private String name;
}

@Entity
@Table(name = "X_User_Dep")
public class UserDepartment {
    @Id
    private int id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "User_ID")
    private User user;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "Dep_ID")
    private Department department;
    @OneToMany(mappedBy = "product")
    private Set<Product> products;
}

@Entity
@Table(name = "X_UD_Prod")
public class UserProduct {
    @Id
    private int id;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "UD_ID")
    private UserDepartment;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "Prod_ID")
    private Product product;
}
ビジネスロジックは以下のようなことを実行する:
  • ユーザAが所属する部署に所属する全てのユーザが保持するプロダクトを得る
  • 適切なオブジェクトに変換し返す
普通に考えれば、まず条件を満たす全てのレコードを取得し変換処理をするだろう。僕が目にしたコードは以下のようなことをしていた。
  • ユーザAの所属する部署を得る
  • 上記の部署に所属する全てのユーザを得る
  • 上記で得られたユーザ毎にプロダクトを取得し、変換する
SQLが直接見えるのであればこの処理がO(n*m)かかるひどいものだとすぐに気づくのだが、Javaのコードを見ると大体以下のように実装されていた。

getAllUserInDepartment(thisUser.getDepartment()).stream()
    .map(UserDepartment::getProducts)
    .map(convert)
    .collect(Collectors.toList);
これの困ったところは、最初のgetAllUserInDepartment以外は目に見えたデータベースアクセスがないということ。実際にはproductsはLAZYに初期化されているので、convertないで他の何らかProductのプロパティにアクセスして初めて取得のクエリが走る。EAGERに取得しろよという話もあるが、このオブジェクトが使われているのはここだけではないので影響範囲に責任持ちたくないという無責任さからやめた。キャッシュという選択肢もあったんだけど、使われているデータベースへのアクセスがJavaアプリケーション以外からもあるので多分キャッシュの整合性が取れないということ(+僕自身Hibernateにそこまで精通していないの)で断念。

どうしたかといえば、エンティティ定義は変えずに、ひたすら下請けのSQLがどんな風になるかを想像しながらDetachedCriteriaを書いて逃げた。正直、SQL直接書かせてください、お願いしますという気分にはなったが…こういう(キャッシュ使えない、エンティティ定義変更できない)時はどうするのがベストプラクティスなのか興味があるが、僕のググり力の低さのせいで解決方法は見つからず。

Hibernate便利なんだけどある程度SQLが書けると隠しすぎてて辛いという時があるという話。

2017-02-09

楕円曲線暗号

今年の抱負の一つ、楕円曲線暗号を実装している。とりあえず肝の一つであるECDSAを実装し終えた。こんな感じで使える。
(import (rnrs) (crypto))

(define keypair (generate-key-pair ECDSA :ec-parameter NIST-P-521))

(define msg "this message requires digital signature")

(define ec-signer (make-cipher ECDSA (keypair-private keypair)))

(define signature (cipher-signature ec-signer (string->utf8 msg)))

(define ec-verifier (make-cipher ECDSA (keypair-public keypair)))

(cipher-verify ec-verifier (string->utf8 msg) signature)
;; -> #t
一応NISTが推奨する楕円曲線パラメータは全部実装してある。テストベクタもあるので、当然テストもしてある。おかげでテスト時間が増大した。処理が重い…SECGが推奨するパラメタも実装しないとなぁと思いつつ割と現状で満足している感もあり、既に随時追加する方向にシフトしつつある。

今となっては笑い話だが、実装している間で一番苦労したのが「正式な仕様を見つけること」だったりする。暗号系に限らず、プロトコルの実装をする際は可能な限り「正式な仕様」(RFCとか)を当たるようにしているのだが、楕円曲線暗号はこれを見つけるのに苦労した。Google先生にお伺いを立てると大抵RFCに当たるんだけど、探した限りだとRFCにはECDSAの拡張はあってもそれ自体がない。まさかWikipediaに書いてあるのをそのまま(実際正しかったんだけど)鵜呑みにするのもなぁと思い探したら、行き着いた先はANSIだったという。最終的にはX9.62でググるのが正解というのに辿り着いた。こういうの知らんと探せんなぁと思った次第。

実はこれを実装してようやくスタートラインに立ったというのが正直なところ。ここからECDH、SSHとTLSにECDSA+ECDHを追加していく予定。まだ先は長いが、今年の抱負なので、今年中に終わればいいだろうくらいな気持ちでいたりはする。

2017-02-01

ひらけ!ポンキッキ

r/lisp_jaとTwitterに以下の投稿があった。
前者がGuileで後者はR6RSで実装されている。文字列をグルグルさせるのならSRFI-13のxsubstringが使えるだろう思い、僕も書いてみた。
(import (rnrs) (srfi :13))

(define s "ひらけ!ポンキッキ")

(define-syntax do-while
  (syntax-rules ()
    ((_ ((var init ...) ...) (pred r) commands ...)
     (do ((first #t #f) (var init ...) ...)
         ((and (not first) pred) r)
       commands ...))))

(do-while ((t s (xsubstring t 1))) ((string=? s t) #t) (display t) (newline))
Cのdo ... whileを真似たdo-whileマクロは正直いらんけど…
以下は実行結果
$ sash hirake.scm
ひらけ!ポンキッキ
らけ!ポンキッキひ
け!ポンキッキひら
!ポンキッキひらけ
ポンキッキひらけ!
ンキッキひらけ!ポ
キッキひらけ!ポン
ッキひらけ!ポンキ
キひらけ!ポンキッ
毎回文字列の比較をするので、当然効率は良くないが、まぁこういうこともできるということで。

2017-01-31

一移民として

オランダに移住してそろそろ8年になる。移民の定義的(Wikipediaが正しいかは知らんが)には移民で問題ないだろう。

大統領令

さて、一移民として最近ニュースサイトから目が離せないでいる。もちろん今を賑わす時の人、第45代アメリカ大統領ドナルド・トランプ氏関係のニュースである。先日あった特定7カ国籍を持つ人のアメリカ入国拒否には大きな衝撃を与えられた。
なぜこのニュースが大きな衝撃になるのか疑問に思うかもしれない。ニュースサイトの論調はともかくとして、傍目から見ればテロリストの抑止にも見えなくない(それにしても、ビザを持っているのに入国できないという事実があったことには目を瞑る必要はあるのだが…)。一移民の視点、少なくとも僕個人の視点、から見ればこれは移民排斥の第一歩に写った。

トランプ氏は就任式の際に「アメリカ第一」と唱えている。
それと同時に大統領選挙の際のスピーチにいくつか人種差別的な発言もある。
これらのニュースから導き出された僕個人が描くトランプ像は白人アメリカ主義者というものになっている。

この「アメリカ第一」と個人的なトランプ像から、先日の大統領令はある種の試金石的な位置づけにあるのではないかと思っている。つまり、テロリスト排除という錦の御旗を掲げることでどれくらい潜在的な人種または宗教差別を隠せるか。そこから発展させて最終的には白人主義に持っていく道筋を建てようとしたのではないか。

ここまで来ると偏執病ではないかと思わなくもないが、過去にナチが存在したという事実もある。あまり考えたくないが歴史は繰り返されるものであるのであれば、ナチズムが再び起きることもありえなくない。

対岸の火事?

事件はアメリカで起きているのだ、オランダに住む僕にはあまり関係ないのではないか?と思わなくもない。っが、意外にも周りのオランダ人(国籍的に、人種的には違う)的にはトランプの大統領令を歓迎する人もいる。難民に関して言えば、ヨーロッパも難民問題に悩まされている。隣国のドイツからは難民関係の事件が絶え間なく流れてくる。
また、割と多数(少なくとも僕の周り)のオランダ人は「中東からくるイスラム系移民は全てテロリスト」と誇張を含むとはいえそれなりに真剣に言っている。

中東移民全テロリスト発言も井戸端会議で話されている程度であればまだ可愛げもあるかもしれない。しかし、それを掲げる政治家がいるとなれば話は多少違ってくる。

Geert Wildersはオランダの反イスラム主義政治家である。現状のところ反イスラム主義だけではあるが、いつ移民排斥になるかは検討がつかない。実際、彼はヨーロッパの難民受け入れ体制に反対している。(もっとも難民の多くはイスラム系なので難民の受け入れに反対なのかは判断が難しいところではあるが…)
また、彼が率いるPVV(Partij Voor de Vrijheid、訳:自由党)は2009年に議席数を150議席中32議席と大きく伸ばしている。(この年はちょうどオランダに来た年で、極右の政党が大幅に議席数を伸ばしたこのニュースはオランダに住むことを不安にさせられた。) 議席を伸ばしたということは、少なくない数のオランダ人が彼の政策に賛同しているということである。それが、反イスラム主義なのか、別の政策に大してなのかはわからないが、賛同者にとって反イスラム主義は問題にならないとも言える。

まとめ

自国を離れていると、こういうニュースには常に戦慄させられる。いつか自分がその対象になる可能性があるからだ。トランプ大統領政権下で何が起きるのか見てみたいという好奇心と、とっとと彼を引きずり下ろして心に平穏を与えてほしいという相反する2つの感情がせめぎ合っている気がする。

2017-01-22

R7RS-largeサポート

本当は日本語リリースノート的なのにするつもりだったのだが、あまりにも眠くてリリース作業だけして寝てしまったという…

Sagittariusは0.8.0からR7RS-largeのRedEditionをサポートするようになった。具体的には以下のライブラリが使用可能になる。
  • (scheme list) - SRFI-1のエイリアス
  • (scheme vector) - SRFI-133のエイリアス
  • (scheme sort) - SRFI-132のエイリアス
  • (scheme set) - SRFI-113のエイリアス
  • (scheme charset) -  SRFI-14のエイリアス 
  • (scheme hash-table) - SRFI-125のエイリアス
  • (scheme ilist) - SRFI-116のエイリアス
  • (scheme rlist) -  SRFI-101の手続きをリネーム
  • (scheme ideque) - SRFI-134のエイリアス 
  • (scheme text) -  SRFI-135のエイリアス
  • (scheme generator) -  SRFI-121のエイリアス 
  • (scheme lseq) - SRFI-127のエイリアス
  • (scheme stream) -  SRFI-41のエイリアス
  • (scheme box) -  SRFI-111のエイリアス 
  • (scheme list-queue) - SRFI-117のエイリアス
  • (scheme ephemeron) -  SRFI-124のエイリアス
  • (scheme comparator) -  SRFI-128のエイリアス
ライブラリ名等がR7RSのWikiにこそっと修正されて上がっていた。個人的にこういうのはどこかに投げてほしい、c.l.sとか。でないとマジで見落とす…

注意するところとして、
  • (scheme ilist)で作られる不変リストは通常のcar等では扱えない
  • (scheme rlist)で作られるランダムリストは、同上
  • (scheme ephemeron)は厳密にはephemeronではない(が仕様は満たしている)
くらいだろうか。

RedEditionだと単に便利ライブラリが標準に追加されたくらいの感覚しかないが、それでもR7RS-largeをフルサポートしている処理系は現状ではSagittariusだけではないだろうか。

そういえば、その昔R7RS-largeに(scheme inquery)というライブラリが追加されたことがあるのだが、これって正式に仕様になってるのかな?SRFI-112とかWG2とか見ても見つからないから、ライブラリ名は先走りだったかな?