Syntax highlighter

2018-05-16

あるソフトウェア開発者の転職経験記

オランダ在住かつマーケット以上の給料をもらっているという前提条件があるので、あまり参考にならないかもしれないが、こんな感じでやったら成功したというのを一応記録しておく。

【条件設定】

転職する際にはなぜ転職したいのかを明確にしておくとゴールが設定しやすい。僕の場合は以下が大きな理由。
  • 今の会社での給料が頭打ちになった
    • 明確に開発者以外の方向にも行くようにと言われた
  • 会社の先行きに不安があった
  • 会社の変革する方向に不満があった
    • 言動不一致というかこれじゃない感が漂っていた(る)
    • 割とゆるい感じだったのが、締め付けられていく感もあった(る)
ここから以下の条件を設定した。
  • 手取りの給料が一緒かそれ以上
    • 今の会社はペンションプランがないので、それがある会社に行くと下がる可能性があった
  • ある程度アジャイルな環境
    • 今更フォーターフォールは嫌
    • せっかく開発者なのだから、なにか目に見えるもの開発したいよね
  • ある程度自由な社風
    • 俺に従え的な雰囲気があると辛い(経験済)
個人的に給料の下がる転職は不可能なので、一番上が一番重要だった。下2つは正直何を言われても入ってみないと分からないというのがあるので、最低でも一つは条件を満たさないとがっかりする転職になりかねないというのもあった。


【応募】

応募するのは基本以下の3つの経路があると思っている。
  • エージェント経由
  • コーポレートリクルーター経由(会社お抱えのリクルータ−)
  • 自前
エージェント経由は便利だが正直あまり良いイメージがない。理由は概ね以下
  • 興味があると言ったものと別のものを出してくる
    • 広告用の募集要項なんだそうだ
  • 必ず電話で話したいと言い出す
    • そんな時間ない(業務時間外でもOKという融通の効くところもある)
  • 希望する給料をいうと第一声が「下げられないか?」である
    • 高いのはわかっているが無理なものは無理だ
  • 転職後一年もするとまた電話してくる
上記が問題にならないのであれば、便利だと思う。履歴書のスクリーニングで落ちる可能性が格段に低くなるのもよい。

コーポレートリクルーターはコンタクトを取ってきたエージェントが希望する会社の人であれば一番よい。LinkedInやIndeed経由でメールが飛んでくるので気になる会社であれば返事をするのがいいだろう。

自前は茨の道であることは否めない。かなりの確率で履歴書のスクリーニングを通過できない。頑張ってカバーレターを書いたのに涙を飲むというのもままある。っが、希望する職にダイレクトに応募できるというメリットもあるので、鉄の心臓を持っている、時間的に余裕がある等頑張れる要因があるのであればこの道を取ってみてもいいかもしれない(ちなみに今回の転職はこれだった)。

【面接】

履歴書のスクリーニングを通過すると面接がある。会社によっては電話だったりスカイプだったりするが、そこまでの大差はない。いきなり課題を出す会社もあるが、プロセスの一環なので気にしない。昔から面接では苦労した記憶がないので、ここまで来るとオファーがもらえる確率が50%くらいという気持ちになる。面接では大抵以下のことが聞かれるので、適当に納得できそうなものを用意しておくといい:
  • 自己紹介(経歴等)
  • なぜ転職するのか
  • 自慢したい直近の成果
相手の会社について事前に調べる必要があったことはない。ほぼ100%面接の場で事業内容等を説明してくれる。質問があれば都度すればよいし、多分した方がよいと思われる。テクニカルな面接でない場合はコミュニケーション能力を計っている場合が主なので、無言の方が印象が悪いと思われる。(知らない単語が出てきたら、「なにそれ?」くらいの質問でいい気がする)

プロセスは前後することがあるが、面接の後は大抵課題が与えられる。課題は経験上以下の2種類がある:
  • 面接官付きの短期決戦型
  • お題が与えられる長期戦型
これらは見られているものが違うので注意しないと行けない。短期決戦型の課題はアルゴリズムに対する知識が問われるのが主なので、以下のトピックを習熟しておけば怖くない:
  • グラフ操作(BFS、DFS等)
  • ソート
  • 探索
  • ビッグ・オー記法
大抵の場合は上記の組み合わせまたは変形でなんとかなる。普段からHackerRank辺りで遊んでいるといいかもしれない。面接者は(おそらく)実際のコーディングの様子、問題解決のプロセス等を見ているので、思考を口に出すと割と喜ばれる。

長期戦型はあるトピックに対してのフルアプリを作成することが多い。短期決戦型と違って何が出てくるのか予測がつかないが、大抵4時間から1日くらい費やせば完了する程度の規模なので、週末を使ってじっくり取り組めば問題ない。短期決戦型と違い、デザイン、データ等実践的な解決を見られるので妥協しないで頑張った方がよい。(テストカバレッジ100%とか含む)

課題を無事通過すると、第2面接か、次の交渉のステージになる。第2面接は大抵課題の解決方法の理由を聞かれたりする。真面目に取り組んでいれば問題なく答えられるはず。

【交渉】

ここまでを無事通過するとサラリーの交渉になる。エージェントを経由する場合はこのステップはエージェント経由になるので、直には経験しないかもしれない。

人によっては現在の年収等を聞かれるのは好きではないかもしれないが、馬鹿正直に答える必要はない、自分が望む額を適当に言えばよいのである(今回ペイスリップを求められたが断った)。そうは言ってもあまり高すぎると向こうの予算オーバーになるので、適当に調節する必要はある。今回は現在の月収+€200くらいで答えたりした(前々回が+€400で前回は+€700だったかな、今回は今の会社を早く去りたかったというのもある)。基本的にここは交渉の場なので、向こうがそれでは高すぎると判断すれば下げられないか聞いてくる。もちろん、下げる必要はないし、ダメならオファーが来ないだけである。交渉の余地がない場合もあるので、その場合は金額と仕事内容で判断すればよい。僕は気長にやれば希望金額より上を出す会社もあるということを知っているので、大抵断ったけど。

交渉がまとまれば晴れて契約。ちなみに、ここまでは現在の会社に内緒で行う必要がる。契約書にサインしたら辞意を伝えればよい。

【その他】

ここからはオランダのソフトウェア開発者市場のみに当てはまるものの可能性があるので、あまり参考にならないかもしれない。

【職種】
今回の転職活動でSI/コンサル系の大手からもオファーをもらったのだが、えらくしょぼかった。もし次があった場合はSI/コンサル系は外した方がいいだろう。元々好きでもないし。そう言えば、あの会社は課題的なのなかったな。あんまり技術を売っていないのかもしれない…

【大手ホテル予約会社】
向こうから連絡してきて、アポ取れと言われたので取ったのだがシカト食らった。なんだったんだろう?(単なる愚痴)

【向こうの予算】
オランダはよくソフトウェア開発者不足と聞くが、給料はあまり高くない気がする。リクルーターに現在の年収を伝えると次から大抵連絡が来なくなるという現象が多発した(コーポレートリクルーターなら予算外と言われたり)。

まとまらなくなってきたので終わり。

2018-05-07

ライブラリの依存性

Scheme でライブラリを書いていると巨大なファイルになることがままある。最近だとこのファイル。まだ未完成なのだが既に1000行近くある(まだ1000行とも言えるが、個人的にファイル内の移動が既に辛い)。辛いなら分ければいいじゃない?という話なのだが、怠け者根性と分け方で迷っているというのの2つがあって今のところ放置している。

ライブラリを複数のサブライブラリに分ける際に大抵以下の2種類のパターンで迷う:
  1. 機能ごとに分ける(例:型、手続き、etc.)
  2. オブジェクト指向的に分ける
個人的にどちらが優れているという気はないし、ケースバイケース(その時の気分ともいう)で違う分け方をしている。今回は2の方向で分けたいと思っているのだが、これだと不都合がでる。ライブラリの循環だ。

今作っているライブラリはXMLのDOMを扱うものなのだが(最終的にどこまでサポートするかは不明)、DOMにはquerySelectorなる手続きがある。これはParentNodeで定義されているが、このインターフェースはDocumentDocumentFragment及びElementで実装されている。CLOSを使っていないので、これらは別々に実装される必要があって、どう実装するかは考えてないんだけど、3つの似たりよったりな手続きが実装されることになると思われる。そうすると、この手続きはどこか別の場所に分けたいのだが、ライブラリをオブジェクト指向的に分けるとこんな感じになって、循環する:
  • Elementライブラリ: Selectorライブラリが必要
  • Documentライブラリ: Selectorライブラリが必要
  • DocumentFragmentライブラリ: Selectorライブラリが必要
  • Selectorライブラリ: Elementライブラリが最低必要
 (Selectorライブラリに分けたいのはこいつが別仕様だからだったりする。)

こういう時は機能毎に分ければうまく行くんだけど、なんだかなぁという気がしていて、う〜ん。ライブラリの遅延ロードみたいなのを導入して循環参照を許してもいいんだけど、それもなぁ的な。Schemeのライブラリは生まれて日が浅い(と言っても既に数年経っているが)ので、こういう場合のベストプラクティス的なものはないし(Scheme的にはこういうケースでベストプラクティスが必要なのって言語仕様的にどうよってなる気もしないでもないがどうなの?)、さてどうしようかね…

2018-04-26

あなたがSchemeを使うべきではないn個の理由

ちょっと前にこんなことを呟いた。
Schemeは好きだがいろいろな理由を考えるとなかなかに現場では使えないというのは賛成である。そこで、あまり気乗りはしないが、使うべきではない理由をあげてみようと思う。これらは現状での改善点でもあるとは思うので、これを読んでなんとかしないとと思えたらぜひとも改善してみてほしい。また、ここに列挙するのは個人的に現場導入に躊躇する理由なので、他にもあるぞ!という方がいればコメントあたりに書いてくれると追記するかもしれない。

【標準だけでは機能が貧弱】

 よく言われることではある。っが、反論としては他の標準がある言語はどうよというのはある(例:CはPOSIX抜くとソケットすらない)。もう一つ反論としては、現在R7RS-largeのプロセスが(遅々として進まなないが)動いているので、ある程度決まってくると結構な機能が揃うと思う。

【開発環境が貧弱】

JavaならIntelliJ、Eclipse、NetBeansなどいろいろあるが、Schemeだと多分Emacs一択だと思う。 更に機能も貧弱。拡張としてGeiserがあるが、(使ったことないのでドキュメント眺めただけの情報だが)これもREPL+αぐらいしかなさそう(例えば、リファクタリング機能みたいなのはなさそうだ)。

【ビルドシステムが貧弱】

JavaならMaven+Nexus、JavaScriptならNPMがあるが、Schemeにはない。パッケージ管理で言えばSNOWがあるが、いまいち。処理系固有ならegg(Chicken Scheme)、PLaneT(Racket)があるが、処理系が固定される。(Racketは正式にはScheme方言になる)

【処理系が多い】

メジャーな処理系だけでも両手で足りないくらいある。選択肢が多いのはいいことだが、CのようにとりあえずGCCでよくね?的に決めるのは難しい。処理系選びに知識が必要になるので、初心者泣かせではあるだろう。(昔処理系の選び方を書いたが、今でもたまにアクセスがあるんだよね。2018年版でも書こうかな)

【コミュニティが小さい】

コミュニティの大きさ=知識ベースだと言える。つまり、困った時に自力で何とかするしかない場面が圧倒的に多くなる。例えばStack Overflowにある質問を見ても数も少なければCSの学生レベルの質問がメインだったりする。また、RacketでWebアプリ作ってるんだけど、みたいな質問にはなかなか回答がつかなかったりする。

【ライブラリが少ない】

ポータブルなライブラリがものすごく少ない。例えばデータベースにアクセスするポータブルなライブラリは知るところでは拙作のr7rs-postgresqlしか寡聞にして聞かない。この辺は標準+SRFIの貧弱差も出てしまうので難しいところではある。ちなみに、R6RS処理系ならば、拙作のr6rs-pffiがあるのでバインディングさえ作ってしまえば、夢は広がるはず(だれかChez対応のPR送ってくれないかなぁ、チラチラ)。

とりあえず思いついたのを列挙してみた。ライブラリや開発環境の充実は個人でも頑張ればできそうな部分ではあると思う。Rubyが今ほどにポピュラーになったのはRailsが出てきたからという意見もあることを思うと、Schemeでポータブルに動くキラーアプリが出れば現状を打開できるかもしれない。

2018-03-30

Ephemeron を使ってみる

Ephemeron は SRFI-124で定義されているがどういったものなのか、どういう挙動が期待されるのかというのはなかなかに想像しづらい。ということで、ちょっと触りつつ解説してみる。

まず、 ephemeron とは何か?ものすごく簡単に言えば循環構造を許す弱対といえる。SRFI-124で定義されている ephemeron は一種の鍵対で、鍵が値から参照されてたとしても、その参照のみが生きているのであれば GC 対象になるというもの。日本語は難しいのでコードで説明してみる。
(let* ((key (cons 1 2))
       (value (cons key 1)))
  (make-ephemeron key value))
こんなのが鍵が値から参照されている状態である。この場合だと、鍵は値のみから参照されているので、ephemeron を作った直後に GC が発生すればどちらも回収されるというものである。

さて、実際の挙動を観察してみよう。SRFI-124 によれば ephemeron は MIT Scheme または Racket でサポートされているという(ちなみに、Sagittarius にもなんちゃってはあるが Chibi Scheme と同様のバグを抱えている。っていうか、これ Boehm GC 上で実装できるの?) MIT Scheme は手元にないので Racket を使ってみる。コードは以下の通り:
#lang racket

(define (make-weak v)
  (let* ((a (cons 1 2))
         (b (cons a v)))
    (make-ephemeron a b)))

(define e (make-weak 'a))
(write (ephemeron-value e)) (newline)
(collect-garbage 'major)
(write (ephemeron-value e)) (newline)
出力結果は以下:
Welcome to DrRacket, version 6.3 [3m].
Language: racket; memory limit: 128 MB.
((1 . 2) . a)
#f
GC 後には ephemeron の値が回収されているのがわかる。

ちょっとした追記:
この挙動と何が違うのか考えて見たのだが、鍵となる a が大域に設定されているのがまずいのかなぁというありきたりな推測。 set! でリセットしているが、内部のどこかでは参照が生きているとかそんなのではないだろうか?流石に Racket の中まではわからないので推測に過ぎないが…

2018-03-23

Generatorの勧め

Schemeでリスト処理を書いていると中間リスト(処理の最中に作成されて捨てられるリスト)の存在が気にならないだろうか?ClojureにはTransducersがあるのにSchemeにはないのだろうか?そこでGeneratorである。

Generatorは元もとGaucheに実装されていたもので割と最近SRFIになったもの。これ自体は引数を取らない手続きかつ処理が終了するとEOFを返すというものである。(GaucheのGeneratorも元をたどるとPython辺りからインスパイアされたらしい(要出典)) このGeneratorのSRFIは紆余曲折あって2種類ある。SRFI-121とSRF-158だ。SRFI-158はSRFI-121の上位互換なので、サポートされているのであればそちらを使った方がよい。(ちなみに、Sagittariusは両方サポートしている)

では、これを使って中間リストの削減をしてみる。 まずは以下のような処理を書く。処理自体に特に意味はない。
(filter (lambda (x) (zero? (mod x 3)))                     ;; 最終リスト
        (map cdr                                           ;; 中間リスト
             (filter (lambda (x) (odd? (car x)))           ;; 中間リスト
                     (map (lambda (x) (cons x (square x))) ;; 中間リスト
                          (iota 1000))))))                 ;; 初期リスト
この処理では計3つの中間リストが存在する。iotaで作成されるものも含めると4つになる。Generatorを使うと以下のように減らすことができる。
(generator->list ;; リストの生成はここだけ
 (gfilter (lambda (x) (zero? (mod x 3)))
          (gmap cdr
                (gfilter (lambda (x) (odd? (car x)))
                         (gmap (lambda (x) (cons x (square x)))
                               (make-iota-generator 1000))))))
ここではmake-iota-generatorを使っているが、(list->generator (iota 1000))と書くと、既存の初期リストをGeneratorに変換するような処理になる。

見た目はほぼ同じ(よく目を細めるように)、ならば気になるのは性能だろう。ということでベンチマークを取ってみる。使うスクリプトは以下。
(import (scheme base)
        (scheme write)
        (srfi 1)
        (srfi 158)
        (time))

(define size 1000)
(define (target1)
  (filter (lambda (x) (zero? (mod x 3)))
          (map cdr
               (filter (lambda (x) (odd? (car x)))
                       (map (lambda (x) (cons x (square x)))
                            (iota size))))))


(define (target2)
  (generator->list
   (gfilter (lambda (x) (zero? (mod x 3)))
            (gmap cdr
                  (gfilter (lambda (x) (odd? (car x)))
                           (gmap (lambda (x) (cons x (square x)))
                                 (make-iota-generator size)))))))


(define count 1000)
(define-syntax dotimes
  (syntax-rules ()
    ((_ n expr ...) (do ((c n) (i 0 (+ i 1))) ((= c i)) expr ...))))

;; GCの結果も見るため、交互に計測する
(time (dotimes count (target1))) 
(time (dotimes count (target2)))
結果は以下の通り。
# 中間リスト作成版
$ sash -s test.scm

;;  (dotimes count (target1))
;;  0.385681 real    0.688000 user    0.000000 sys

;; Statistics (*: main thread only):
;;  GC: 21389312bytes heap, 293849719bytes allocated, 49 gc occurred
# Generator版
$ sash -s test.scm

;;  (dotimes count (target2))
;;  0.386407 real    0.480000 user    0.000000 sys

;; Statistics (*: main thread only):
;;  GC: 28368896bytes heap, 198322311bytes allocated, 28 gc occurred
性能に大きな差はないが、GC回数と割り付けられたメモリに大きな差がある。

メモリの圧迫が気になるときに思い出したように使うといいかもしれない。

2018-03-09

右回り

スタックオーバーフローにこんなのがあった。
Rotating a list to the right in scheme

真面目に実装するのはだるいなぁと思ったのでこんな風に書いてみた。
(import (rnrs)
        (srfi :1))

(define (rotate-right l)
  (if (null? l)
      l
      (cons (car (last-pair l)) (drop-right l 1))))

(rotate-right '(1 2 3 4 5))
;; -> (5 1 2 3 4)
一応O(N)で動くと言えば動く。SRFI-1にsplit-at-rightみたいなのがあればリストを舐めるのが一回で済みそうではある。

今週はネタも時間もなかったのでこんなので…(そのうちsplit-at-rightを実装してもう少しマシなのを書くかもしれない。)

2018-03-02

ファイルタイムスタンプとChicken Scheme

Sagittariusではファイルのタイムスタンプを変更する方法を用意していなかった。それが問題になったことがなかったというのが一番の理由である。が、先日これが問題になったので追加したという話。

問題になったもの
Scheme EnvでChickenのインストーラを作成していたら(archive)で展開したファイル郡をビルドできなかった。もちろんtarで展開してやれば動く。エラーとしては、Chickenが事前ビルドプロセスで生成しているファイルをビルドしようとしているというものだった。(例:library.scmからlibrary.cを生成する) これらは依存関係としてMakefileに記載されている。

原因
(当たり前のことで書くのも憚られるが)Makefileの依存関係の解決はファイルの更新日時に強く影響される。上記の場合だと、library.scmはlibrary.cよりも後に変更が加えられているのでmakeが変更有りとして解決しようとしたというもの。(.scmはアルファベット順で.cより後に来るから、展開順的にそうなった)

解決
change-file-timestamps!という手続きを追加し、(archive)で展開されたファイルのタイムスタンプを変更するようにした。特に何にひねりもない王道とも言える。

どうでもいい捕捉
新たに追加された手続きchange-file-timestamps!はこんな感じで使う。
(change-file-timestamps! file 1 (make-time time-utc 0 0))
fileは文字列で存在するファイル名を指す必要がある。残りの引数はそれぞれ、atimemtimeに相当し、最終アクセス日時と最終更新日を変更する。上記の場合だと、最終アクセス日時が現在時間+1秒になり、最終更新日がUnix時間の開始日(1970年1月1日 0時0分0秒 GMT)になる。更新をしたくない場合は#fを指定し、現在時にしたい場合は#t(時間オブジェクトまたは実数以外の真値)を与える。

これが入ったことで、今まで無理やり実装するしかなかったtouchが簡単にできるようになる。例えばこんな感じ。
(import (rnrs) (sagittarius))

(define (touch file)
  (if (file-exists? file)
      (change-file-timestamps! file #t #t)
      (call-with-output-file file (lambda (out)))))

(touch "foo")
上記はあると便利かもしれないが、今のところ用途がないので、どこのライブラリにも入れてない。要望があれば入れる感じ。

2018-02-26

多言語を扱う際のメンタルモデル

NetflixでDeath Noteを見ていた際に「L is dead」というメッセージが見えた。Lが実際に死んでからしばらく時間が立っているので、実は他にも「L is alive」等のメッセージが存在し現在のLの状況を伝えたという感じだったのだろうか。

「Death」「die」「dead」は全て英語で死を表すがそれぞれ、名詞、動詞、形容詞(または副詞)になる。北斗の拳の有名な台詞の一つである、「お前はもう死んでいる」は英語にすると「You are already dead」になる(と思う)が、「You are dying」 にはならない。もし上記のLの死亡を伝えるメッセージが、Lの現在の状態を表すものではないのであれば「L has died」の方がいい気がする。

形容詞としての「dead」対応する日本語の単語は存在するのだろうか?形容詞として「死んでいる状態」を表す単語というのを実は知らない。複数言語(といっても三つだけだが)を扱えるようになると、言語Aにはあるが言語Bに対応する単語がないということがままある。例えば「おにぎり」をずばり表す英訳や蘭訳はない気がする。逆に、蘭語の「beleg」のズバリは日本語にない気がする。(そういえば「忖度」という言葉の訳はドイツ語にズバリで「vorauseilender Gehorsam」とか「unausgesprochene Anweisung」とかいうそうだ。)

多言語を扱う際にはこういった「ある言語にはある概念」を扱う必要がでてくる。二言語だと言語Aで考えて言語Bに翻訳するみたいになる気がする。これが面倒になると最初から言語Bで考えるという風になる気がする(余談だが、この状態になると、酔っ払っていても言語Bで会話できる)。僕はこの状態で言語Cが入ってきた。言語Cの習熟度は他の二言語に比べると遥かに低いのだが、不思議なことに言語Cで話しているときは言語Cで考えているのである(もちろん、足りていない部分は先の二言語で補っているが)。これは一体どういうことなのだろうか?

思考という部分は置き換え可能なパーツでできているとすると割と理解できる動作なのかぁという気がする。脳内モデルが「入出力-思考-抽象概念」みたいな三層でできているとするのである。二言語を扱っていた最初期では「思考」の部分が母語になっていたが、これが徐々に第二言語に置き換えることが可能になった。そして、新たに覚えた言語は基本的な部分ができるようになると、置き換えの動作がスムーズにいき、習熟度が低くても置き換えられるようになったと仮定できる。

なんかこういったのを何度もブログに書いてる気がするなぁ、なんでだろ?自分のメンタルモデルを言語化するのが好きなのかね?

2018-02-24

MSYS2サポート 2

前回の続きと僕なりの回答。

前回MSYS2上でのシンボリックリンクの話とSagittarius上でどうするかというのを書いた。書いた後に助言やMSYSがどのようにシンボリックリンクを扱っているかというののヒント(調べた結果Qiitaの記事が古いのか現状の挙動と違った)をもらったのでそれを踏まえて実装してみた、という話。

【MSYS2上でのシンボリックリンク】

 MSYS2上ではシンボリックリンクはデフォルトでは作られず、ファイルのコピーとなる。この挙動を制御するためには環境変数MSYSを適切に設定する必要がある。2018年現在では以下の表のようになる。
挙動
  • winsymlinks
  • winsymlinks:lnk
ショートカットを作成する。
MSYS2上からはシンボリックリンクとして見える、かつWindows上ではショートカットになる。
  • winsymlinks:native
  • winsymlinks:nativestrict
シンボリックリンクを作成する。
Windows上でもシンボリックリンクになるが、管理者権限またはWindows 10かつDeveloper modeである必要がある。
失敗した場合は単にコピーになるか、作成されない。
  • 上記以外
  • 未設定(デフォルト)
ファイルをコピーする。
ソースも確認したのでこれであっているはず。ちなみに上記はsymlink(2)の挙動なので、ln -sは多少違うかもしれない(たぶんあってると思うけど)。

【Sagittarius上ではどうしたか】 

シンボリックリンクの作成を失敗したくないというのが大前提としてあったので、最低でもショートカットにフォールバックするようにしたかった。ということで、Sagittarius上では以下のフローでシンボリックリンクの作成をする。
  1.  環境変数のチェック
    1. コピー以外のいずれかならばsymlink(2)を使用する
    2. コピーならばWindows上のシンボリックリンクを試す
  2. ファイルが作成されているかチェック
    1. 作成されているなら終了
    2. 作成されていないなら、環境変数MSYSwinsymlinks:lnkを設定し#1へ 
(Windows上のシンボリックリンクを試す必要ないかもしれないなぁとこれ書いてて思ったので、ファイルチェック→環境変数セット→リトライのフローだけにするか。)

とりあえずこれで、create-symbolic-link手続きがMSYS上では何かしらシンボリックリンクっぽいものを作成することになる。いつも思うがサポートする環境を増やすときはこうい環境特有の workaround が必要になるの辛い。

2018-02-16

MSYS2サポート

Sagittarius 0.9.0からはMSYS2が実験的にサポートされる。実験的と書いているのは単にまだ安定しないのと、他のPOSIX環境と多少挙動が異なる点があるというところからである。挙動に関しては積極的に変えていく可能性があるので、WindowsかつMSYS2を使っている方がにフィードバックを送ってくれると反映される可能性が高い。

MSYS2という環境はCygwinに比べるとWindowsとの互換性を重要視したPOSIXエミュレーターに見える(あまり使っていないのでぱっと見の感想)。現状で一番困っているのはシンボリックリンクの扱いで、例えばPOSIXのsymlink(2)は必ず失敗する。失敗してエラーを投げられると困るので、現状はシンボリックリンク関係の部分はWin32APIのCreateSymbolicLinkを使って凌いでいるが、このAPIは管理者権限を要求するので普通にSagittariusを起動して使用すると失敗する(何も作ってくれない)。

個人的にWindows上で使うPOSIXエミュレータを以下の理由からMSYS2に乗り換えようかなと思っている:
  • プロセスの作成が失敗しない
  • メモリの制限がない
この二つは結構重要で、最近書いているこれはプロセスを多用するからである。そうすると上記のシンボリックリンクが問題になってくる。

代替案としては
一つ目は割と茨の道で、理由としては.lnk拡張子がネックになる。二つ目はボリューム跨ぎができない、ジャンクションポイントは絶対パスが必要になるとう結構制限がある。

個人的には管理者権限を要求してもいいかとは思うが、もしそれができない状態かつMSYS2上でscheme-envを使いたいという状況が出てきた場合に困りそうである。いろいろな意見がほしいところである。

2018-02-09

TLS実装2(完)

Sagittarius上にビルトインなTLSを実装し終えた。POSIXな環境(Linux、OSX)ではOpenSSLをWindowsな環境ではSchennal、CygwinとMSYS2上ではあればOpenSSL、なければSchannelを使うという感じになっている。

今までPure SchemeだったのがCでの実装になったのだから性能にもなにかしら影響があるだろうと思ってベンチマークを取ってみた。

意外にも有意な差がない。Schemeの性能が高いんだと喜びたいところだが、以下のような理由だろう:
  • 暗号、復号及びMACは元々Cでやられている
  • TLSパケットの送受信は上記がメイン
  • (おそらく)OpenSSLはスタックの破棄みたいなセキュリティ上のオーバーヘッドがある
元々ビルトインにしようと思ったのは別の理由からなので、あまりおまけを期待しすぎてはいけない。よりセキュアになったということにしておく。

一応2月のリリースには間に合ったということで。

2018-02-01

TLS実装

Scheme環境ツールでTLS実装をOpenSSLかSSPIに切り替えると書いて、実際にそうしているのだが、SSPI(正確にはSchannel)の実装が辛い。何が辛いか?
  • ドキュメントが飛び飛び
    • MSDNにAPIの説明はあるんだけど、フローの説明がない
    • 例えばX.509証明書と秘密鍵をメモリ上から読み取り、紐付けるということが(今のところ)ドキュメントから読み取れない
  • サンプルが少ない
    • Schannel SSL辺りでぐぐっても片手で数えられるくらいしか見つからない
    • それらのサンプルもあんまり使い込まれてないらしく、普通の用途しかサポートしてない
      • X.509証明書はファイルから読むとか
      • だめならSchannelでセルフ署名を作るとか
OpenSSLの実装は例も多く(逆にドキュメントがしょぼい気がする)割と簡単にいったので「これはSchannelもスムーズに行くのでは?」という淡い期待があったのだが、シャボン玉より簡単に弾けた…

これこの後後方互換を保つとかの作業もあるのだが、2月のリリースに間に合うのかね?不安になってきた。

2018-01-26

Mingwサポート

Ubuntu上でもWindows用のコードをコンパイルしたいと思いMingwのサポートをしてみた。実際にMingwでコンパイルされたバイナリを動かしていないのでコンパイルできる以上の意味を今のところ持っていない(そのうちCIでは回すようにしたいが、優先度的には多少低め)。

とりあえずMSVCとの非互換な部分は大きく以下:
  • struct timespecが存在する
  • .ccファイルをC++として認識する(と思われる)
    • Boehm GCのコンパイルで困らされた
  • __try__finallyがない
    • __try1__except1はうまいこと動かない
    • リンカがエラーを投げる
    • なので今のところ代替手段なし…
  • 細かいライブラリの名前が違う、かつ#pragma commentは動かない
    • ws2_32とか
これら以外は大きなコードの変更もなくコンパイルできた(動いたとは言っていない)。

Twitterで「移植性を考えたらC言語を使うしかない」というのをみたが、C言語を使ってかつ多大な努力を払わないと移植性なんて得られんなぁ…同じWindows上のバイナリを作るのにもこれだよ。

上記にもあるが、コンパイルできるだけで動くとは言っていないので、Mingwの環境がある方だれか試してくれないかなぁ(チラチラ Issue上げたりPR送ってくれたりするとなお嬉しいなぁ(チラチラ

2018-01-25

Scheme環境ツール

各種Scheme処理系をある程度便利に使いたいと思い、こんなんを書いている。なんでそんなことを思ったかというと:
  1. タイムゾーンなSRFIを書こうかな
  2. ポータブルな実装がいるよなぁ
  3. 処理系を簡単にスイッチできないかなぁ ← これ
完全にYak shavingである。今時かどうかは知らないが、目標としてはインストール不要の完全オンデマンドみたいなのを目指している。なので、READMEにも書いたがこんな感じでインストールかつ使用できる。
$ curl https://raw.githubusercontent.com/ktakashi/scheme-env/master/bin/install.sh | sh
$ export PATH=$HOME/.scheme-env/bin:$PATH
$ scheme-env install chibi-scheme
$ scheme-env switch chibi-scheme@master
$ scheme-env run
これでChibi Schemeをソースからビルドしてかつ使用可能にしてくれる。ソースからビルドするので、各処理系が要求する依存関係は予めインストールされている必要がある(流石にそこまで面倒見れない)。ちなみに最初のShellスクリプト以外は全部Scheme(Sagittarius)で書かれている。

今のところインストール可能な処理系はChibiとSagittariusだけだが、すぐにGaucheや他の処理系もサポートされる予定。

動作環境はUbuntuを想定しているが、ホスト処理系(Sagittarius)の要求するパッケージのインストールをしないのであれば、 他のPOSIX環境でも動くはず(自信ない)。Cygwinはプロセスを多用していることもあって多分無理(誰かこの問題直して…)。MingwサポートしてMsys上でとかかなぁ。

ここからはどうでもいい話なのだが、実はこれを作ったおかげで0.9.0のリリースが大分遅れることになった。理由は以下:
  •  Ubuntu上でHomeが暗号化されているとC Assertで落ちる
    • キャッシュファイルのPATHが長すぎるのが原因だけど、そのままだとあまりに不便
  • (rfc tls)でbitbucket.orgに繋げられない
    • TLS 1.2実装の問題
    • 流石に温かみのある手作り実装に疲れてきたのでOpenSSLやSSPIに切り替える
1つ目は直したんだけど、2つ目が流石に時間がかかる。OpenSSLは楽なんだけど、SSPIがねぇ。まぁ、来月くらいにはリリースできるんじゃないかなぁくらいの気持ちではいるが、さて。

2018-01-14

リストの作成

こんなツイートをしたが、どう書くかというのは示してなかったので(iPhone上からだったので)、せっかくだしブログのネタにしようという話。



リストの生成方法にはいくつか方法があるが、今回は有名どころを二つ書いておく。
cons + reverse
cons + reverseはまぁよく使われるパターンで、この名前で呼べば大体どんなものかの想像がつくくらいだ(と思う)。ツイートで言及しているQiita記事にある手続きfをこのパターンで書き直してみた。
(define (f/cons+reverse init n)
  (let loop ((i 0) (r init))
    (if (< i n)
        (loop (+ i 1) (cons (tent_func (car r)) r))
        (reverse r))))
nthが何をしているものなのかよくわからなかったが、おそらく(nth -1 L)はリストの最終要素を取り出すものと予想。(そのように実装して元の手続きを動かしたらそれっぽい値返したし。)
これだとappendがなくなるので、O(n^2)がO(n)になる。一時リストの生成が嫌ならreverse!を使えば生成されるリストもたかだか一個になる。
doで以下のように書くこともできる。
(define (f/cons+reverse init n)
  (do ((i 0 (+ i 1)) (r init (cons (tent_func (car r)) r)))
      ((= i n) (reverse r))))
どちらがいいかは好み次第。
ちなみに、reverseがないので保留と言われた(記憶、Twitter上で見つからん)のだが、reverseはこんな風に書ける。
(define (reverse l)
  (do ((l l (cdr l)) (r '() (cons (car l) r)))
      ((null? l) r)))
RnRS準拠の処理系なら絶対持ってるはずだが、自作の処理系だったという可能性も踏まえてみる。 (積極的に無駄な処理をするコードを直していくスタイル)

unfold
Schemeの標準にはないが、SRFI-1 (R7RS-large)にはunfold という手続きがある。これを使うと上記は以下のように書ける。
(define (f/unfold init n)
  (unfold (lambda (x) (< (car x) 0))
          cdr
          (lambda (x) (cons (- (car x) 1) (tent_func (cdr x))))
          (cons n init)))
こっちは多少メモリ効率が悪い(シードの生成に必ずペアを作ってる)し、unfoldの使用方法が多少変則的な感はある。

ループ内で(append result (list ...))みたいなコードを見つけたら積極的に書き換えていきたいところである。

2018-01-08

日付と時間 その2

前回の続き

Shiroさんの案では calendar 自体が (y, m, d, H, M, S) の組を持つというもので、SRFI-19 の date は (y, m, d, H, M, S) + timezone というもの。っで、 calendar の種類は複数あって、例えばグレゴリアン歴の2018年1月8日は D = (2018, 1, 8, 0, 0, 0)だけど、これと同等のユリウス暦は2017年12月26日 D = (2017, 12, 26, 0, 0, 0)になる。更に、それぞれの calendar が時間の演算を持つというもの。例えば calendar A では一日が30時間だとすると、その calendar における日付B+1日というのは Gregorian calendar では1日と6時間を足したことになるが、この差異を calendar がよしなにしてくれるというもの。(意図を正しく理解していれば)

個人的には非常に可搬性が高く、火星に行っても火星用の calendar を作成すれば使えるいいアイデアだと思うんだけど、地球上でプログラムをするのであれば、RFC3339で定義されている時間で概ね事足りると思ったりもする。(まぁ、宇宙開拓史みたいなゲームを作って火星のカレンダーが必要だみたいなのはあるかもしれんが、レアケースと割りきってもいいよね?)

そうするとこんな感じで簡略化するとよくね?という気にもなる。
日付 D = (y, m, d, H, M, S)
カレンダー C
タイムゾーン TZ
として、日付Dは常にRFC3339で表現できるものとする。日付の演算はデフォルトでは以下のようにできる:
D+1d = (y, m, d+1, H, M, S) = (y, m, d, H + 24, M, S) = ...
カレンダーCは例えばユリウス通日等の計算や、そのカレンダー上での一日を日付D上に加算するのに使ったりするこんな感じ。
(calendar:days->date calendar:julian 0) ;; -> D(-4714 11 24 12 0 0)
タイムゾーンTZはまぁ、普通にタイムゾーンで、SRFI-19の date は D+TZ になる。

目を凝らすと日付Dは (y, m, d, H, M, S) + RFC3339 と言えなくもなく、デフォルトの演算は calendar Crfc3339 によって提供されるものとも言えるので、多少の利便性だけとも言えなくもない。じゃあ、最初からそうすればよくね?という気もしてきた。う〜ん。

2018-01-04

日付と時間

日付や時間はプログラムを書く上で鬼門になりがちなものである。例えば、2018年1月4日11時10分と言った時に、この時間は常に同じ時間を指すだろうか?答えはNoである。例えばオランダと日本では別々の時間になる。同一時刻を指すようにするにはタイムゾーンの指定が必要になる。

Schemeには日付と時間を扱うSRFIがある。SRFI-19である。多くの場合はこのSRFIで事足りるのだが、局所時間を扱う際にこまることがある。例えば上記の日付をタイムゾーンを気にせず表せない。
;; これはエラー
(make-date 0 0 10 11 4 1 2018 #f)
;; これは常にUTC
(make-date 0 0 10 11 4 1 2018 0)
;; これだと常にUTC+1:00、夏時間どうする?
(make-date 0 0 10 11 4 1 2018 3600)
とりあえず全ての日付はUTC+0にして扱うという手もあるのだが、バッチ処理を日付が変わるときに行うというようなことが面倒になる。また、このSRFIには日付のみ、時間のみを扱う術がない。例えば11時15分という時間と(make-date 0 0 15 11 0 0 0 0)は等価か?あるいは2018年1月4日は(make-date 0 0 0 0 4 1 2018 0)と等価であるかということである。答えはケースバイケースであるとは思うが、多くの場合はNoではないだろうか?

Javaは1.8からjava.timeというパッケージが導入された。これは上記のようなジレンマを解決してくれそうな雰囲気がある(使ったことないので未確認)。Sagittariusにもこれと似たようなものを入れるべきかなぁと考えている。こんな感じでの階層だろうか?
+-------+   +-------+
| date  |   | time  |
+---+---+   +---+---+
    |           |
    +-----+-----+ 
          |
    +-----+-----+   +-------------+
    | date-time |   | zone-offset |
    +-----+-----+   +------+------+
          |                |
          +-------+--------+
                  |
       +----------+----------+
       | offsetted-date-time | <-- SRFI-19 dateと等価
       +---------------------+
時間が常にローカル時間かというのはわからないので、offsetted-timeみたいなのもあっていいかもしれない。timeだと名前が被るから別名にする必要も有りそうだが。

ちょっと考える必要があるが、割と早めに導入したい気持ちもある。

追記
Shiroさんのアイデア


これを使ってSRFI-19を実装するとすると、Calendarは3つ必要になりそう。(たぶん)Gregorian、JulianとModified Julian。内Julian calendarとModified Julian calendarは要らんかもしれんが、あると綺麗っぽい。これを踏まえると以下のようにするといいだろうか?
                  +------------+
                  |  timezone  |
                  +------------+
                  | - Offset   |
                  +------+-----+
+------------+           |
|    date    |           |
+------------+           |
| - timezone |<>---------+                  +------------+
| - calendar |<>------------+               | date-tuple |
+------------+              |               +------------+
                 +----------+----------+    | -(Y,M,D)   |
                 |      calendar       |    +-----+------+
                 +---------------------+          |
                 | - YMD: date-tuple   |<>--------+
                 | - HmS: time-tuple   |<>--------+
                 +---------------------+          |
                                            +-----+------+
                                            | time-tuple |
                                            +------------+
                                            | -(H,m,S)   |
                                            +------------+
calendarは演算手続きの集合にして、こうした方がいいだろうか?
+--------------+                                  +------------+ +------------+
|   calendar   |                                  | date-tuple | | time-tuple |
+--------------+                                  +------------+ +------------+
| - operations |                                  | - (Y,M,D)  | | - (H,m,S)  |
+------+-------+                                  +------+-----+ +------+-----+
       |                                                 |              |
       |             +-------------------+               |              |
       |             |     local-date    |               |              |
       |             +-------------------+               |              |
       |             | - YMD: date-tuple |<>-------------+              |
       +-----------<>| - calender        |                              |
       |             +-------------------+                              |
       |                                                                |
       |             +-------------------+                              |
       |             |     local-time    |                              |
       |             +-------------------+                              |
       |             | - HmS: time-tuple |<>----------------------------+
       +-----------<>| - calendar        |
                     +-------------------+
                              :
                            so on
                              :
どっちもしっくりこない感じがするなぁ。もう少し考える必要がありそうだ。

2018-01-02

謹賀新年

年末年始を病院で過ごしていたのでいわゆる「今年を振り返って」みたいなのが書けなかった。(僕自身が病気ではないです、後は察してください)

昨年はいろいろあって、一年が長かったのか短かったのかよく分からない状態である。少なくとも去年の1月に何をしていたのかは全く思い出せない。引っ越しがあったのでそれに記憶の大部分を取られている気もする。

今年の抱負と目標:
  • ジムに週二で通う
    • 割と毎年言ってるなぁ
  • Scheme関連のブログ記事を週一で書く
    • 習慣にすればなんとか行けるかなぁ
    • 最悪「Sagittariusの中身」とか書きだせば…
  • 今作ってるWebアプリを公開する
    • モチベーションが落ち気味なので宣言しておけば嫌でもやるだろう
あんまり関係ないけど、転職も考えていたりする。完全リモートワークOKならオランダ国外でも問題ないです。

2017-12-17

95%の人は満足するけど残りの5%は不満を持ち得るシステムの5%に入ってしまった件

表題が示す通りなんだけど、そんな境遇に陥った。

今の会社に入って1年半たつのだが、買収があったりして給与システムの大幅な見直しみたいなのがあった。今までは、なんとなく評価された人が昇給されたりボーナスがあったりみたいな超おおざっぱな感じだったんだけど(特に決められたルールがなかった)、スケール制(ランク制?)みたいなのが導入されたり、固定値のボーナス(オランダ的には13ヵ月目ともいう)が導入されたりしてきた。

これだけ見ると改善があって、透明性が増したからいいじゃんなんだけど、導入されたシステムには頭打ち もあって、あるランクで最高額に到達したら昇給が発生しないのである。この状態で昇給するにはランクアップするしかない。僕はこの状態であるという通知を受けた。一応満額まで数パーセントあるように見えるが、微妙な曖昧性があるので、満額と言われてもおかしくない状態。

っで、現状ではランクアップの方法は不明かつシニアエンジニアとして雇われているので、この職責上でこの上ってなんになるんだろう的なのもある。

周りの反応を見るに、この状況にある被雇用者は本当に少数っぽくて、ちょっと話を聞いていただけだと、普通評価でも毎年3~4%の昇給が望めると喜んでいる人の方が多いくらいだった。いままで昇給のルールすらなかったのだから気持ちは分かるが…

このシステムが導入される際に一応プレゼンがあったんだけど、そこで言われたのが、「頭打ちになった際にはランクアップするために別のポジションに着く必要があるが、社内にそのポジションがなかったら社外で探すしかない」という旨の言葉だったりする。こういう状況の人間が発生することは分かっていたうえでのこの発言+特に救済(現状の職責より1ランク高いものを割り当てておくとか)をしなかったのは潔いなと思う反面、いま開発者が去るだけで入ってこない状況を軽めに見てるのかなぁとも思ったり。(もしくは僕は穀潰しとみなされているか、それはそれで悲しいが…)

今週のどこかで昇給についての面接があるらしいので、ランクアップの可能性があるのかないのか聞かないとなぁ。可能性0ならしょうがない、次の職場をまじめに探すかねぇ。

2017-11-24

プログラミング言語Schemeの学び方

これに触発されてみた。
調簡易なHTTPサーバーをR7RS+SRFIで作ってSchemeを学ぼうという話。スライド145にある項目をとりあえず列挙
  • Socketの扱い
  • 正規表現
  • リソースの開放
  • 並行処理
  • 文字列の扱い
【Socketの扱い】
Scheme標準にはないのでSRFI-106を使う。サーバーSocketはこう作る。
(define server-socket (make-server-socket "8080"))
そして、こんな風に待ち受ける。
(let loop ()
  (let ((socket (socket-accept server-socket)))
    (loop))
特に何もしないソケットがリークしまくるサーバーの出来上がり。

【正規表現】
Scheme標準にはないのでSRFI-115を使う。HTTPリクエストの最初の一行をパースする正規表現はこんな感じで書ける。
(define first-line
  '(: "GET" (+ space)
      (-> path (: "/" (+ ascii))) (+ space)
      "HTTP/" (: num "." num)))
こんな感じで使う
(cond ((regexp-matches first-line line) =>
       (lambda (m)
         (let ((path (regexp-match-submatch m 'path)))
           ;; get the content of the path
           ))))
サブマッチに名前は付ける必要はないが、あるとわかりやすい。

【リソースの開放】
Schemeに便利な汎用リソース開放構文というのはないので、都度用途に合わせて作ったり標準にあるものを用いる。例えば、ポートの開放はclose-portを使い、call-with-portを使えば、正常処理後にはポートを閉じてくれる。Socketの開放はsocket-shutodownsocket-closeを用いる。
サーバをであれば、以下のようなものが便利に使えなくもない。
(define (finish)
  (close-port in)
  (close-port out)
  (socket-shutdown socket *shut-rdwr*)
  (socket-close socket))
inoutはソケットポートである。

【並行処理】
Scheme標準にはないのでSRFI-18を使う。SRFI-18はプリミティブなスレッドとミューテックスしか提供しないので、高度なものが必要であれば自分で作る必要がある。
投げっぱなしスレッドは以下のように作れる。
(thread-start! (make-thread (lambda () (handle-request socket))))
処理系によってはスレッドの作成は高価な場合があるので、可能であればスレッドプール等は作っておきたいところ。R6RS処理系かつSRFI-18をサポートしているのであれば、拙作の(util concurrent)が使える。

【文字列の扱い】
スライドにあるような便利なものはない。連結したければstring-append等を使う必要がある。文字列操作は高価な場合があるので(例:参照にO(n)かかる)、使える場面ではポートを使いたいところ。

さて、上記全てを踏まえて非常に簡易なGETリクエストのみに対応したHTTPサーバは以下になる。R7RSではバイナリポートと文字ポートは分かれていて、処理系によっては厳しく分けてあつかう(特にR6RS/R7RSな処理系、Sagittariusなど)ので、I/Oの部分がどうしても煩雑になる。例えば、出力の際には文字列を一旦バイナリに変換している。

(import (scheme base)
        (scheme write)
        (scheme file)
        (srfi 18)
        (srfi 106)
        (srfi 115))

;; Assume all ASCII
(define (read-binary-line in)
  (let ((out (open-output-string)))
    (let loop ((b (read-u8 in)))
      (case b
        ((#x0d)
         (case (peek-u8 in)
           ((#x0a) (read-u8 in) (get-output-string out))
           (else (write-char (integer->char b) out) (loop (read-u8 in)))))
        (else (write-char (integer->char b) out) (loop (read-u8 in)))))))

(define (handle-request socket)
  (define in (socket-input-port socket))
  (define out (socket-output-port socket))
  (define first-line
    '(: "GET" (+ space)
        (-> path (: "/" (+ ascii))) (+ space)
        "HTTP/" (: num "." num)))
  (define (finish)
    (close-port in)
    (close-port out)
    (socket-shutdown socket *shut-rdwr*)
    (socket-close socket))
  (define (http-error status e)
    (define message (string->utf8 "Not okay"))
    (report-error e)
    (write-bytevector (string->utf8 "HTTP/1.1 ") out)
    (write-bytevector (string->utf8 (number->string status)) out)
    ;; laziness...
    (write-bytevector (string->utf8 " BOO\r\n") out)
    (write-bytevector (string->utf8 "Content-Type: text/plain\r\n") out)
    (write-bytevector (string->utf8 "Content-Length: ") out)
    (write-bytevector (string->utf8
                       (number->string (bytevector-length message))) out)
    (write-bytevector (string->utf8 "\r\n\r\n") out)
    (write-bytevector message out)
    (finish))
    
  (guard (e (else (http-error 500 e)))
    (let ((line (read-binary-line in)))
      (cond ((regexp-matches first-line line) =>
             (lambda (m)
               (let ((path (regexp-match-submatch m 'path))
                     (bout (open-output-bytevector)))
                 (guard (e (else (http-error 404 e)))
                   (let ((file (string-append "." path)))
                     (call-with-port (open-binary-input-file file)
                       (lambda (in)
                         (define buf (make-bytevector 1024))
                         (let loop ((n (read-bytevector! buf in)))
                           (write-bytevector buf bout 0 n)
                           (when (= n 1024)
                             (loop (read-bytevector! buf in))))))))
                 (write-bytevector (string->utf8 "HTTP/1.1 200 OK\r\n") out)
                 (write-bytevector (string->utf8 "Content-Type: text/plain\r\n") out)
                 (let ((bv (get-output-bytevector bout)))
                   (write-bytevector (string->utf8 "Content-Length: ") out)
                   (write-bytevector (string->utf8
                                          (number->string
                                           (bytevector-length bv))) out)
                   (write-bytevector (string->utf8 "\r\n\r\n") out)
                   (write-bytevector bv out)
                   (finish)))))
            (else (http-error 403 #f))))))

(define server-socket (make-server-socket "8080"))

(display "Starting server on port 8080") (newline)
(let loop ()
  (let ((socket (socket-accept server-socket)))
    (thread-start! (make-thread (lambda () (handle-request socket))))
    (loop))
もう少し簡単に書きたいと思ったら、Sagittariusに付属している(net server)を使うか、拙作Paellaを使うと簡単にHTTPサーバが書ける。後者はサーバというよりはWebアプリが簡単に書けると言うべきか。