Syntax highlighter

2014-07-24

バグは寝かせてみるといい

かなり長めに寝かせていたバグの糸口を掴んだのでメモ。

問題になるのは以下のようなコード
(import (rnrs))
(define-syntax wrap-let
  (syntax-rules ()
    ((_ expr)
     (let-syntax ((foo (lambda (x) (syntax-case x () ((_) #'expr)))))
       (foo)))))

(define-syntax wrap
  (lambda (x)
    (syntax-case x ()
      ((_ expr)
       #'(let ((temp expr))
           (wrap-let (car temp)))))))

(wrap '(a b c))
これがなぜ問題になるのかというのが重要で、展開された後を見るとなんとなく答えが出てくる。以下が展開形コードのイメージ。
(let ((temp.0 '(a b c)))
  (let-syntax ((foo (lambda (x) (syntax-case x () ((_) #'(car temp.0))))))
    (foo)))
問題はここからで、let-syntaxの中にある#'(car temp.0)が原因。Sagittariusのマクロ展開器は現状syntax-caseのパターン及びテンプレートを全てリネームする。さらにsyntaxが展開されるときにもリネームする。そうすると、元々のtemp.0は持っていない構文情報を付加されてしまい、結果的に一意の識別子になってしまう。

ならば、テンプレート変数と分かっている場合はリネームしなければいいだけのように見えるのだが、そうも行かないのが面倒なところ。テンプレート変数はリネームされなければならないのだ。とりあえずマクロ展開形が局所マクロを含んでいるというのは(おそらく)なんとか分かる気がするので、その場合のみを特殊扱いするという感じでいこうかと思う。既に不安材料が見えているのでそれが正しいのかは今一よく分からないのではあるが・・・

このバグ、6ヶ月くらい寝かせてあったのだが、最近になって気運が高まってきたのか原因が掴めるところまできた。どうやら困難なバグほど寝かせておいた方が角が取れて消化しやすくなるらしい。

2014-07-22

packratの機能追加

SagittariusにはChicken由来のpackratライブラリがあるのだが、このライブラリ数量指定子が使えなくて非常に使い出が悪い。例えば、0個以上の何かを取るというのを書くのに以下のようにする必要がある。
(import (rnrs) (packrat))

(define (generator tokens)
  (let ((stream tokens))
    (lambda ()
      (if (null? stream)
          (values #f #f)
          (let ((base-token (car stream)))
            (set! stream (cdr stream))
            (values #f base-token))))))

(define quantifier (packrat-parser
                    top
                    (top ((vs <- item*) vs))
                    (item* ((i <- item i* <- item*) (cons i i*))
                           (() '()))
                    (item (('+) '+))))


(let* ((q (generator '((+) (+) (+))))
       (r (quantifier (base-generator->results q))))
  (parse-result-semantic-value r))
;; -> (+ + +)
BNFで書かれた定義を持ってくる際に数量指定子がないというのは非常に面倒で、パーサを書くのを何度も何度も億劫にしてくれた。quantifierの定義はこう書けた方が直感的である。
(define quantifier (packrat-parser
                    top
                    (top ((vs <- (* item)) vs))
                    (item (('+) '+))))
ずっとほしいと思っていたのだが、そろそろあった方がいいなぁという気運が高まってきたので、えいや!っと中身を読むことにした。

中身を読んで分かったのは、複数回マッチするというのを何とかするAPIが足りていないのが原因ぽかったので、こんなのを追加してマクロ側にも手を入れてみた。
(define (packrat-many parser n m k)
  (lambda (results)
    (define (>=? many max) (and max (>= many max)))
    (define (return-results vs result)
      ;; if we inline map then for some reason step on a bug...
      (let ((vals (map parse-result-semantic-value vs)))
        (merge-result-errors ((k vals)
                              (parse-result-next result))
                             (parse-result-error result))))
    (when (and (zero? n) (eqv? n m))
      (error 'packrat-many "both min and max are zero" n m))
    (let loop ((i 0) (vs '()) (s #f) (results results))
      (if (>=? i m)
          (return-results (reverse! vs) s)
          (let ((result (parser results)))
            (cond ((parse-result-successful? result)
                   (loop (+ i 1) (cons result vs) result
                         (parse-result-next result)))
                  ((<= n i)
                   (return-results (reverse! vs) result))
                  (else result)))))))
なぜか、インライナーのバグを踏んだのはまぁご愛嬌ということで・・・
マクロの方は+、*、?に加えてマッチ回数を指定できる=も入っている。長いのでここには載せない。これで多少便利になる気がする。

2014-07-14

ライブラリの作られるタイミング

Chaton Gauche部屋から流れてきたこれ
REPLからエラーの起きるライブラリを繰り返しr7rs#importすると,エラーが起きなくなります.

;; temp.scm
(define-library (temp)
  (begin
    (f)))

;; REPL
gosh> (import (temp))
*** ERROR: Compile Error: Compile Error: unbound variable: f

"(standard input)":1:(import (temp))

Stack Trace:
_______________________________________
  0  (eval expr env)
        At line 179 of "/usr/local/share/gauche-0.9/0.9.4-rc2/lib/gauche/interactive.scm"
gosh> (import (temp))
#<undef>
gosh[r7rs.user]>
全く同じ問題がSagittariusでも起きる。原因も分かっていて、コンパイラがコンパイル時にライブラリを作ってしまうけど、エラー自体はランタイムで起きるのでimportが呼ばれた際にはライブラリはあるものとして扱われてしまうから。

あまりうまい解決策が思いつかないんだけど、とりあえずぱっと思いついたのがVM側で何とかするというもの。っが、実際に実装しかけては無理だなぁということが分かってしまったので破棄。案としては、evalの中身をwith-exception-handlerで囲ってしまい、エラーが起きた際に現在のライブラリと保存されたライブラリが異なっていたら現在のライブラリを破棄するというもの、だったのだが、これはライブラリの変更がインストラクションでのみ発生するということに依存している上に、それ以外の方法で変更することが可能なので火を見るより明らかにだめだろうという・・・

次に思いついたのが、ライブラリのコンパイル全体をwith-exception-handlerで囲ってしまうとうもの。実際の手順としては以下のようになる予定。
  • ダミーのライブラリを作る
  • 実行コードをwith-exception-handlerで囲う
  • コンパイル及び実行
    • エラーがあればダミーを破棄
    • なければ正式名で作成及び中身をコピー
こっちはまだ実装をしていないのだが、見えているだけで以下のデメリットががが
  • エラーが存在しない場合でも呼ばれる
    • キャッシュがでかくなる
  • 余分な手続きを2つ作る必要があるため、性能に影響が出そう
  • コピーが重い
  • マクロ展開がライブラリを使用しているのだけれど・・・
まぁ、ダミーのライブラリを作る必要はないかもしれないのでそこは下2つは無視できるかもしれないが。

REPLでしか起きない問題なので多少以上にやる気が低いのも問題だろう・・・

2014-07-11

SRFI-19の紹介

(LISP Library 365参加エントリ)

SRFI-19は日時を扱うライブラリです。関連性が強いのでSRFI-18と同じ著者だと思っていたのですが、実は違うということに今日気づきました。新たな発見です。

このSRFIでは時間と日付の両方を扱います。まずは簡単に使い方を見てみましょう。
(import (rnrs) (srfi :19))
;; simple stop watch
(define-syntax time
  (syntax-rules ()
    ((_ expr)
     (let* ((s (current-time))
            (r expr)
            (e (current-time)))
       (display "execution time: ")
       (display (time-difference e s))
       (newline)
       r))))

(define (tak x y z)
  (if (not (< y x))
      z
      (tak (tak (- x 1) y z)
           (tak (- y 1) z x)
           (tak (- z 1) x y))))

(time (tak 21 14 7))
;; prints
;; <time-duration 0.0000000>
;; -> 14
どのような形で表示されるかは処理系依存なので、値を真面目に取得するにはtime-secondやtime-nanosecond等のアクセサを使います。

current-timeで現在の時間オブジェクトを取得するのがよく使われると思いますが、計算して時間を求めることも可能です。その際にはmake-time及び計算用の手続きを使います。
(define one-hour (make-time time-duration 0 3600))
(let ((c (current-time)))
  ;; I don't want to do anything for an hour...
  (thread-sleep! (add-duration c one-hour)))

make-timeは最初に時間タイプを受け取ります。このSRFIでは以下のタイプが定義されています。
  • time-duration
    時間の差分を扱います
  • time-monotonic
    Monotonicな時間を扱います
  • time-process
    現在のプロセス時間を扱います
  • time-tai
    TAI時間を扱います
  • time-thread
    現在のスレッド時間を扱います
  • time-utc
    UTC時間を扱います
処理系によってはプロセス時間とスレッド時間はサポートしていない可能性があります。

日付を扱う手続きのいくつかを見てみましょう。
(current-date)
;; -> date object

(date->string (current-date))
;; -> "Sat Jul 12 19:03:24+0100 2014"

(date->string (current-date) "~Y-~m-~d")
;; -> "2014-07-12"

(string->date "2014-07-12" "~Y-~m-~d")
;; -> date object

(date->julian-day (current-date))
;; -> 7075728736710773/2880000000

(julian-day->date 7075728736710773/2880000000)
;; -> date object
上記以外にも日付⇔時間、修正ユリウス暦⇔日付or時間、TAI時間⇔UTC時間等の変換手続きが用意されています。

個人的に日付⇔文字列の変換は拡張性が無く、またXSD等が採用しているISO-8601のフォーマットのタイムゾーン形式に対応していなかったりと、多少使いにくい点があります。日付のアクセサは用意されているのでその辺は自前で書けということなのかもしれませんが・・・

今回はSRFI-19を紹介しました。

2014-07-09

Unicode character set

I'm implementing SRFI-115 on top of builtin regular expression engine adding missing feature to implement and struggling with its Unicode requirement. Even though the SRFI says Unicode support is an optional feature and those Unicode related SREs have no effect if it's not supported.

However I don't like taking conservative way. It's always better to implement full feature so that users don't have to consider implementation's limitation. Now the problem is how and compatibility with built in engine. If Unicode is supported then SRFI requires, for example, alphabetic to match L, Nl and Other_Alphabetic characters. Current builtin engine only considers ASCII for named character sets (e.g. [[:alpha]] or even \w). Well there are bunch of options to resolve this but followings are, I think, rational;
  1. Support these only in this SRFI
  2. Builtin engine should support Unicode as well
Option #1 is the easier way  to do it. Option #2 would break backward compatibility so I need to be very careful especially \w. I believe I've already wrote bunch of code which depend on the fact that it only matches ASCII characters.

Let's think about #1 first. This must be relatively easy so that I just need to convert the named character set to Unicode character set or ASCII character set depending on the expression. The problem, if I dare to call, is that I need to prepare 2 types of the same named character sets; one of them is already there, though. So all what I need are adding full Unicode character sets and switch them according to the context. Easy isn't it?

Now option #2. The problem is backward compatibility. There are 2 main possible breaking compatibility issues; one is regular expression itself and the other one is SRFI-14. The whole point to do this option is make my life easier for later stage to merging Unicode character sets to predefined ones such as char-set:letter. For regular expression engine, I probably just need to make sure by default it's ASCII context. However I'm not even sure whether or not I wrote a piece of code with SRFI-14 that depending on ASCII character set. (quick grep showed I have, so if I merge it then I need to check all code...)

So if I take #2 then the following things need to be done;
  • Separating Unicode/ASCII context on builtin regular expression engine
  • Adding full unicode set to builtin character sets (including SRFI-14)
  • Checks if there is a piece of code which depends ASCII charset
I feel somewhat it doesn't worth but cleaner than #1. Well, it's always good to have some challenges so I should take the harder way.

2014-07-05

SRFI-18の紹介

(LISP Library 365参加エントリ)

SRFI-18はマルチスレッドを扱うライブラリです。POSIXが採用しているスレッドモデルとほぼ同じなのでなれている人はほぼ同じように使えます(条件変数の扱いが多少違うのと、ミューテックスが破棄された状態を持つのが違う点かと思います)。

使い方を見てみましょう。以下は簡単なスレッドの作成です。
(import (srfi :18))
(let ((t (thread-start! (make-thread (lambda () (write 'a))))))
  (write 'b)
  (thread-join! t))
;; -> <unspecified>
;; prints ab or ba
スレッドの作成にはmake-thread、開始にthread-start!、終了を待つのにthread-join!を使います。

スレッドを使っていると数秒止めたくなることもあるでしょう。そんなときにはthread-sleep!を使います。
(import (srfi :18))
(thread-sleep! 1) ;; sleep 1 second
一応スレッドの停止(破棄)もサポートされています。このオペレーションは非常に危険なので注意して使えという注釈も付いています。
(thread-terminate! (current-thread)) ;; -> never return
thread-yieldは他のスレッドに処理時間を明け渡す手続きです。非常に重たい処理を行うスレッドが可能な限りCPUの空き時間に処理を行いたい場合などに使えます。
(import (rnrs) (srfi :18))

(define m (make-mutex))
(define (do-something-else) (print 'something-else) (thread-sleep! 1))

(let ((t (make-thread (lambda ()
                        ;; a busy loop that avoids being too wasteful of the CPU
                        (let loop ()
                          (if (mutex-lock! m 0) ; try to lock m but don't block
                              (begin
                                ;; Do some heavy process
                                (display "locked mutex m")
                                (mutex-unlock! m))
                              (begin
                                (do-something-else)
                                (thread-yield!) ; relinquish rest of quantum
                                (loop))))))))
  (mutex-lock! m 0)
  (thread-start! t)
  (thread-sleep! 1)
  (mutex-unlock! m))
僕自身はそんなミッションクリティカルな処理を書いたことが無いので使ったことのない手続きの一つですが・・・

次にミューテックス及び条件変数の使い方を見てみましょう。ミューテックスを扱う手続きは既に上記の例で出ていますが、ロックを書けるのにはmutex-lock!、外すのにはmutex-unlock!を使います。また、このSRFIで採用しているモデルでは条件変数はシグナルを送る以外の手続きを持っていないので、pthreadのpthread_cond_waitのようなことをするにはmutex-unlock!を使います。mutex-unlock!のオプショナル引数が条件変数を受け取った場合には、pthread_cond_waitとほぼ同様の動作をします。ただし、ミューテックスはロックされていないので、注意が必要です。

今回はSRFI-18を紹介しました。

2014-07-01

You gotta LOVE side effect...

Recently I've implemented the procedure that checks if given closure is transparent/no side effect. Then I thought that's cool so that compiler can eliminate some of no-side-effect expression or compute the result in compile time if the given argument is constant variable. Now I've got a problem.

To describe it, here is the small piece of code which causes the problem.
(import (rnrs))

(define label-counter (lambda () 0))
(define (new-lbl!) (label-counter))

(define (change-label-counter)
  (set! label-counter (lambda () 1)))

(define (print . args) (for-each display args) (newline))

(let ((lbl (new-lbl!)))
  (print lbl))
(change-label-counter)
(let ((lbl (new-lbl!)))
  (print lbl))
This should print 0 then 1. Well, it doesn't seem there is a problem except the change-label-counter. The defined label-counter is obviously constant so it'd be inlined in the new-lbl!. Now, the change-label-counter sets the new value to label-counter, here comes the problem. The new-lbl! has already compiled with inlined value and just returns 0 so change-label-counter doesn't have any effect to it. Then the result value would be always 0.

If these procedures are defined in a library, then Sagittarius compiler could check if the global defined variable would be changed or not however because this is defined in a script it can't and just tries to inline so that the target procedure looks constant.

What I can do is;
  • Don't inline them if it's in script.
  • Forbit set! for globally defined variable.
    • Make impossible to run some of R5RS code (e.g. Gambit benchmark's compiler)
  • Read all expression and wrap with a library before compiling a script
    • I can already see the horrible future...
Hmmmm, I guess I need to disable this check for now...

2014-06-30

Named let performance

When I was playing around with Sagittarius compiler, I've found that some of named let expression was compiled to (looks) slow code. Following piece of code is one of them;
(lambda (pred lst)
  (let loop ((lst lst))
    (cond ((null? lst) '())
          ((pred (car lst)) (loop (cdr lst)))
          (else (cons (car lst) (loop (cdr lst)))))))
You can see this type of code anywhere (although it's not tail recursive so I don't like it). And you would expect this not to have any performance issue other than stack consumption because of the recursive call.

Now, I was also thinking like that however things are bit worse. The compiled code describes everything;
;; size: 3
;;    0: CLOSURE #&lt;code-builder #f (2 0 0)>
;;   size: 13
;;      0: UNDEF
;;      1: PUSH
;;      2: BOX(0) !!!! IMPLICIT BOXING !!!!
;;      3: LREF_PUSH(0)
;;      4: LREF_PUSH(2)
;;      5: CLOSURE #&lt;code-builder loop (1 0 2)> !!!! CLOSURE CREATION !!!!
;;     size: 26
;;        0: LREF(0)
;;        1: BNNULL 3                  ; ((null? lst) '())
;;        3: CONST_RET ()
;;        5: FRAME 4
;;        7: LREF_CAR_PUSH(0)
;;        8: FREF(1)
;;        9: CALL(1)
;;       10: TEST 6                    ; ((pred (car lst)) (loop (cdr l ...
;;       12: LREF_CDR_PUSH(0)
;;       13: FREF(0)
;;       14: UNBOX
;;       15: LOCAL_TAIL_CALL(1)
;;       16: RET
;;       17: LREF_CAR_PUSH(0)
;;       18: FRAME 5
;;       20: LREF_CDR_PUSH(0)
;;       21: FREF(0)
;;       22: UNBOX
;;       23: LOCAL_CALL(1)
;;       24: CONS
;;       25: RET
;;      7: LSET(2)
;;      8: LREF_PUSH(1)
;;      9: LREF(2)
;;     10: UNBOX
;;     11: LOCAL_TAIL_CALL(1)
;;     12: RET
;;    2: RET
I've put comments where it causes performance issue.

The first one, implicit boxing, is because the loop is transformed to letrec and it needs to refer loop itself inside of it. The second one is creating a procedure each time the top procedure (I haven't named it though) is called because it has a free variable pred inside. Sounds reasonable? No, it doesn't at all to me now. If you write a piece of code with named let, then you would expect the calling named procedure would be compiled to just a jump. However this is compiled to a procedure call. Well, on the other hand this is not a tail recursive call so you wouldn't expect to be a jump though. Actually if the code was tail recursive then compiler would compile it to a simple jump without implicit boxing and creating a closure.

Now, then should users always be careful or adjust their code how compiler compiles to maximumised performance instructions? My answer is yes and no. In above case, I expect at least compiler should emit non implicit boxing code. In general, compiler should be smart enough to emit good quality code. Then problem is how? ... that's something I need to think, though.

2014-06-28

SRFI-17の紹介

(LISP Library 365参加エントリ)

SRFI-17は一般化したset!です。発案者はKawaの製作者Per Bothner氏ですね。(他のSRFIでも既にGuileやらLarcenyやらの製作者が出ていたのですが、文量の水増しです。)

CL使ってる方はsetfに近いものだと思ってもらえればいいです。使い方は以下のようになります。
(set! (car a) 'b) ;; -> (set-car! a 'b)
(set! (cdr a) 'c) ;; -> (set-cdr! a 'b)

(set! (vector-ref v 0) 'a) ;; -> (vector-set! v 0 'a)
実際にこのように展開されるかは実装次第なところもあるのですが、SRFI内では以下のようになると書かれています。
(set! (proc args ...) value) 
 ;; -> ((setter proc) args ... value)
例えば、Gaucheでは(set! (car a) 'b))はSRFIのように展開されますが、Sagittariusはset-car!が直接呼ばれるようにコンパイルされます(それがいいかどうかは別の問題ですが)。他の処理系は調べてません。

あまりに文量が少ないので多少余計な情報も。

このSRFIはどうやらかなり不評だったようです。
I have gotten no support for my proposal. While the srfi process allows for srfis that are controversal and don't have consensus, at this stage it seems kind of pointless to pursue it. I have no particular desire to push for something most people dislike, at least in this context. So if anybody out there thinks the generalized set! is a good idea, now is the time to speak up.
http://srfi.schemers.org/srfi-17/mail-archive/msg00049.html
この投稿が投げられる前にあったMatthias Felleisen氏が投げた投稿から始まる議論(罵り合い?)がなかなか面白いです。個人的には便利なSRFIだと思っているのですが、Scheme的ではないかもしれませんね。

今回はSRFI-17を紹介しました。

2014-06-15

How to emulate pthread_cond_wait with SRFI-18

I was implementing multi thread queue like Gauche has but in pure Scheme with SRFI-18. Then I've got confused how to wait condition variable. If it's POSIX then you can use pthread_cond_wait. However it's SRFI-18 and it doesn't provide a procedure to wait condition variable directly. Well, in the end there are some example code which explains how to do it but at that moment I couldn't figure it out. So there may be some people who also have the same issue as me. (It's basically because of my lack of knowledge of multi threading and, well, if there aren't at least this could be my memo to remember...)

The answer was a combination of mutex-unlock! and mutex-lock!. I was always thinking that pthread_cond_wait or similar procedure is the only way to wait for a condition variable. I skipped reading this section (and caused my confusion...)
NOTE: mutex-unlock! is related to the "wait" operation on condition variables available in other thread systems. The main difference is that "wait" automatically locks mutex just after the thread is unblocked. This operation is not performed by mutex-unlock! and so must be done by an explicit call to mutex-lock!. This has the advantages that a different timeout and exception handler can be specified on the mutex-lock! and mutex-unlock! and the location of all the mutex operations is clearly apparent. A typical use with a condition variable is:
(I don't complain this is very confusing but don't you think?) So what I needed to do is instead of searching something that specifically says condition-wait! or something, I needed to use mutex-unlock! and mutex-lock!. Following piece of code is more concrete example;
(import (rnrs) (srfi :18))

(define-record-type (<foo> make-foo foo?)
  (fields (immutable mutex foo-mutex)
          (immutable cv    foo-cv)
          (mutable   count foo-count foo-count-set!))
  (protocol (lambda (p)
              (lambda ()
                (p (make-mutex)
                   (make-condition-variable)
                   0)))))

;; Utilities for above foo record
(define-syntax lock-foo!
  (syntax-rules ()
    ((_ foo) (mutex-lock! (foo-mutex foo)))))
(define-syntax unlock-foo!
  (syntax-rules ()
    ((_ foo) (mutex-unlock! (foo-mutex foo)))))
(define (with-locking-foo foo thunk)
  (dynamic-wind
      (lambda () (lock-foo! foo))
      thunk 
      (lambda () (unlock-foo! foo))))

(define-syntax wait-cv
  (syntax-rules ()
    ((_ foo)
     (let ((r (mutex-unlock! (foo-mutex foo) (foo-cv foo))))
       (display "unlocked! with cv") (newline)
       ;; mutex is not locked so lock it if you need it.
       (mutex-lock! (foo-mutex foo))
       r))))

(define-syntax notify-foo
  (syntax-rules ()
    ((_ foo)
     (condition-variable-broadcast! (foo-cv foo)))))

(let ()
  (define foo (make-foo))

  (define (producer)
    ;; increment count
    (with-locking-foo foo
     (lambda ()
       (display "Increment count!") (newline)
       (foo-count-set! foo 1)
       (notify-foo foo))))

  (define (consumer)
    ;; wait until the count is one
    (with-locking-foo foo 
     (lambda ()
       (let loop ()
         (cond ((zero? (foo-count foo))
                (display "It's zero need to wait!") (newline)
                (if (wait-cv foo)
                    (loop)
                    (error #f "something went wrong")))
               (else (foo-count foo)))))))

  (let ((ct (thread-start! (make-thread consumer)))
        (pt (make-thread producer)))
    ;; consumer is waiting but make sure
    (thread-sleep! 10)
    ;; let producer increment
    (thread-start! pt)
    (thread-join! pt)
    (display (thread-join! ct)) (newline)))

#|
It's zero need to wait!
Increment count!
unlocked! with cv
1
|#
This just tries to emulate pthread_cond_wait using mutex-unlock! and mutex-lock!. The wait-cv is the emulation macro. notify-foo assumes that given foo
's mutex is locked. It took couple of hours to figure out this simple thing for me...
I haven't met any case that this mutex model is convenient but if this how it is I need to get used to it. (though, my case was just the name confusion...)

2014-06-14

SRFI-16の紹介

(LISP Library 365参加エントリ)

SRFI-16は可変長引数を受け取る手続きに対する構文です。要するにcase-lambdaです。R6RSから標準になった構文なので、あまり目新しいものは無いかもしれません。

以下のように使います。
;; because it's very famous I've become a bit lazy
;; so just a bit of taste...
(define foo
  (case-lambda
    ((c key) (foo c key #f))
    ((c key default) (ref c key default))))
上記の(いい加減な)例では、束縛される手続きは2つもしくは3つの引数を受け取ります。2つの場合は3つ目の引数にデフォルトの値として#fを設定して自身を呼びなおします。以下のように書くのとそんなに変わりません。
(define (foo c key . rest)
  (let ((default (if (null? rest) #f (car rest))))
    (ref c key default)))
ちなみに、参照実装だと上記のcase-lambdaは以下のように展開されます。
(define foo
  (lambda args
    (let ((l (length args)))
      (if (= l (length '(c key)))
          (apply (lambda (c key) (foo c key #f)) args)
          (if (= l (length '(c key default)))
              (apply (lambda (c key default) (ref c key default)) args)
              (error #f "Wrong number of arguments to CASE-LAMBDA"))))))
これだと多少手間が減る程度の恩恵しかありません。(もちろん処理系によっては引数のパックとapplyが異常なまでに安い処理系もあるかもしれませんが・・・) また、コンパイル時に手続きの呼び出しを行わない処理系だと、lengthの呼び出しが定義された分だけ呼び出されるので精神衛生上あまり好ましくありません。

さすがにこれはどうかとも思ったらしく、以前syntax-rulesのみを使った多少効率のいいcase-lambdaの実装を作っていました。(ちなみに、Sagittariusではもう少し前に書き直しています。やってることは一緒なのでsyntax-rules版にしてもいいかとは思いますが。)

これならば引数がパックされるだけでapplyを呼び出すことがないので、参照実装よりは多少効率がいいはずです(オプション引数として受け取るのと同等程度)。もう少し進めるのであれば、組み込みの構文にしてしまってコンパイラが受け取る引数をうまいこと解決するということも可能かもしれません。

今回はSRFI-16を紹介しました。RnRS準拠でもっと効率のいい実装があるよ!という方がいらしたらご一報くださいm(_ _)m

2014-05-30

Shibuya.lisp meetupに行ってきた

Lisp コンパイラの評価マークシティの駐車場の方に行ったり、chikuさん(だと思う)に5階まで来てもらったのに入れ違いで17階にたどり着いたりしたのはまぁ誤差の範囲だろう。初見殺しだね、あのビル。

発表の見出しはここ。とりあえずざくっと感想。

ELSの。パリだったんだし行けばよかったなぁというのが真っ先に出てきた感想。 論文は目を通したのもあったけど、見てないのの方が多い。日本からだとトータルで旅費がどれくらいかかったんだろう?と思ったが、僕が質問することでもないなぁと思ったので思っただけにとどめた。(これ出てこなかったということは、会場の人はお金を気にしなくてもいいくらいの身分の人ばかりか、ECLにそこまで興味がないかのどちらかかなぁ、とも思ったり。)

僕の。


メインはサルミアッキで発表はおまけ。最初に行ったけど、詳細と書いてあるけど割りと表面の概要だけ。

CLの最適化。いくつか聞いてて気になった部分はあったけど、質問したのは1個だけ。ちなみに気になったのは以下。
  • プロファイル取らない点
    • コード見て遅いのが判別できるのはあるけど、性能出すのってその先が結構要る場合が多いので。
    • まぁ、でも複数アルゴリズムを試すとかじゃなかったから問題ないのか?
  • レジスタに乗せる方に割いてた点
    • x86だとレジスタに乗らんのでは?と思ったが環境がMacのCore i7だったし、いいのか?
    • SBCLだとx86でもレジスタ6個使うから問題ないかもしれない。
      参照:Lisp コンパイラの評価
    • ちなみに、これは質問したやつ
  • 性能と抽象化の両方を取るけど、性能にかなり傾いてた点
    • pixel(だったかな)型をばらして渡すよりはdynamic-extentでスタックに載せて抽象度を保ったままの方が好みかなと思っただけ。
Picrinの。これのスライドを5枚みたくらいで時間切れ。スライドを見た感じだと面白そうだったので見たかった。(しかし40枚以上あったけど、9時半までに終わったのかな?)

自分のPCにシリアルポートが付いてないとか昨日初めて知ったりしていた(あれが無ければ10分くらい時間節約できたよね、すいません)。開始前に皆席について静かに待っていたので自己紹介もあるしと思い郷にいってしまったが、ここで回っておけばよかった。おかげでほぼ顔とTwitter IDが一致しない・・・Shibuya.lisp in the Netherlandsとか開くべき。まぁ、ELSにもっと日本から人がくればいいだけの話ともいう。(行ってないけどw)

2014-05-28

なんだか日本っぽいと思ったもの

休暇で日本にいるのだが、特にやることもないので地元をぶらぶらとしている(暑いので名古屋に行くのも面倒という・・・)。っで、ふとすごく日本っぽいものを発見した。

まずは以下のような張り紙。
 30km/h制限の道に貼られていたのだが、「みんなが見ています」というのがなんとも日本っぽいなぁという感じである。

次にビル(アパート)名
 住んでいる人は王族か貴族か何かだろうか?10年前とかに見たときは特に気にしなかったような気がするのだが、今はふと気になった。そういえば、ほぼ全てのビルに名前が付いているのも日本っぽい気がする(自分が今住んでいるアパートに名前はない)。名前の付け方はセンスを疑うが、名前があるというのは目印にしやすいし、いいことじゃないかなぁと思う。

見る人が見ると今どこにいるのかバレバレな写真な気もしないでもないが、まぁいいか。

2014-05-12

Want to use SFTP?

I've added SFTP library (rfc sftp) recently so let me introduce it.

If you are a lazy programmer, you would have thought, at least once, that avoiding GUI operations to upload files to SFTP server. (Yes, that was my motivation!) I've been thinking that for already couple of months (or even close to a year). SFTP is a protocol that depending on SSH so first I needed to write the SSH library. From that, for some reason, I didn't have much need to do. (Basically SFTP operation was reduced at my work.) Then a week ago or so, I needed to collect log files from multiple servers and I simply disliked that task. So I wrote it.

Following is the basic usage.
(import (rfc sftp))

(call-with-sftp-connection "hostname" "23"
  (lambda (conn)
    ;; downloading is very straight forward
    (sftp-read conn "a/file" (sftp-file-receiver "a/local/file"))

    ;; uploading is a bit complicated
    ;; firstly, you need to open the file handle
    (let ((handle (sftp-open conn "a/file2" 
                    (bitwise-ior +ssh-fxf-creat+ +ssh-fxf-write+))))
      ;; then you need to use the handle
      ;; uploading uses binary input port as it's input.
      (sftp-write! conn handle 
        (open-bytevector-input-port (string->utf8 "Hello SFTP from Sagittarius")))
      ;; finally close the handle
      (sftp-close conn handle))

    ;; want the contents of a directory?
    ;; (sftp-readdir conn ".") ;; >- this returns a list of sftp-name object
    ;; this returns a list of string representing directory contents (file names)
    (sftp-readdir-as-filenames conn "."))
  :username "user" :password "pass")
sftp-close may not be needed as long as the server can handle it correctly but it's better to do it. You can also write a very thin wrapper for this. (I just didn't have any good API for this so maybe for future.) If your SFTP server doesn't need any authentication then you don't have to specify the keyword arguments (never seen such a server though). For now, the library doesn't support any other authentication method such as login with certificate.

One important note; the underlying SSH library doesn't support key re-exchange properly so if the uploading/downloading file is more than 1 GB in total then it would most likely fail. However I don't need this yet so not sure when this will be fixed. (patches are always welcome!)

This library will be available from 0.5.4 (will be release very soon).

2014-05-11

プロのプログラマ

Twitterで「本物のプロのプログラマは数学に精通している」みたいなのを見かけてもやもやしているのでちょっと吐き出すことにした。

そもそも、プロとアマの差というのはあることに対しての対価をもらっているかいないかだと思うのでそもそもの言葉の定義からおかしいという話もあるのだが、とりあえずそれはおいておくとしよう。個人的にはそれがたとえコピペでプログラムを書いてても、それに対しての対価をもらっているのであればプロのプログラマである。品質に対する気概とか、自分の作ったものに対する何かしらとかはどちらかと言えば職人気質に分類されるべきで、プロとアマの差というのに使われるべきではないと思う。

おそらくTwitterでの意図としては「凄腕のプログラマ」くらいの意味だろう。では何をもって「凄腕のプログラマ」とするのか?これはいろいろ議論があるだろうが、まず第一に挙がるのは「問題解決力」、次に「抽象化能力」じゃないだろうか?仮にこれらを持つプログラマを凄腕のプログラマとするならば、そのプログラマは数学に精通している必要があるのだろうか?例えば暗号分野に特化しているのであれば、三角関数とかはあまり必要ない気がする。(僕の経験上でしかないので本当に必要ないかはちょっと微妙。) ついでに言えば、自分が知っている限りの範囲でしかないが、凄腕と呼べるプログラマは引き出しが多いイメージである。ここでいう引き出しが広いとは、あることに対しての解決策の「名前」を知っている、という意味である。

例えば(自分の分野で申し訳ないのだが)、パスワードの保存をセキュアにしたい、という要望があったとする。そうすると「暗号化」がまず出てくる。どのように暗号化するかといえば、「秘密鍵」が出てくるだろう。「公開鍵」ではセキュアではないからだ*1。どの方式がいいかはまぁ後で調べればいいだろう。次に出てくるのは「ハッシュ」を使う方式だろう。ただし、ハッシュにする場合はパスワードを再送するというのは諦める必要がある。ハッシュ方式は「SHA-256」が一応安全か。

これぐらいの引き出しがあれば「実装の中身」までは特に知らなくてもなんとかなる。(暗号分野では大体自分で実装するっていうのはありえないので。) この程度では凄腕とはいえないが、言いたいこととしてはこれだけあれば調べ方が分かるので解決できる。抽象化はまぁその先にあるのでここで上げるのは多少厳しい。で、数学の話は出てきただろうか?

何がいいたいかといえば、プログラマが必要な知識は問題領域に大きく依存するということである。(数学が必要なところってとりあえずゲーム関連しか知らないのだけど、他にもあるの?) そして、その問題領域に精通していれば「凄腕」になる資質は十分にある。他の分野も知っているにこしたことはないが、「必須」ではない。自分の理想像のみを指して「本物のプロのプログラマ*2」といわれると、世の中のプロのプログラマの門を極端に狭くするのでいかがなものか、という話である*3

*1 暗号化でセキュアとは基本的に総当り以外の解き方がないこと+暗号文に平文の情報がないことの2点である。公開鍵方式は後者が満たされないという話。
*2 そもそも「偽者のプロのプログラマ」というのがあるのか?という話でもあるが・・・プログラム書いて金もらってりゃ本物のプロだよ、という理論の前だとないので・・・
*3 自分が数学に精通してないのでというのがこれ書いた最も大きな理由だわね。一応金もらってやってるプロだし、それなりにプライドもあるのでちょっとムカっとしたのさ・・・

2014-05-09

SRFI-14の紹介

(LISP Library 365参加エントリ)

SRFI-14は文字セットを扱うSRFIです。このSRFIは他のSRFIの下請け的に使われることが念頭にあります(Abstract参照)。実際SRFI-13にはSRFIを使っている部分があります。

基本的な使い方を見てみましょう。
(import (srfi :14))

(char-set-contains? char-set:digit #\a)
;; -> #f

(char-set-contains? char-set:digit #\1)
;; -> #t
基本はこれだけです。一応セットなので以下のようにセットに登録されている文字を扱うことも可能です。
(char-set-map char-upcase char-set:lower-case)
;; -> charset with #\A-#\Z range
ただ、上記のような手続きを日常的に使うということはまずないでしょう。新たに文字セットを構築するのであれば以下の手続きの方がはるかに便利です。
(list->char-set '(#\a ...))
;; -> charset with given chars

(string->char-set "abc..."))
;; -> charset contains given string's characters

(ucs-range->char-set #x100 #x10FFFF)
;; -> charset with range 0x100-0x10FFF
既存の文字セットものから文字を追加もしくは削除ということも可能です。
(char-set-adjoin char-set:letter #\-)
;; -> charset with letters (#\a-#\z #\A-#\Z) + #\-

(char-set-delete char-set:digit #\0)
;; -> charset of #\1-#\9
上記の手続きには破壊的に操作を行うものも用意されています。

これだけだとちょっと寂しいので多少実装にも踏み込んでみます。SRFI-14は参照実装も用意されていますが、参照実装では最大でLatin-1までの範囲しかサポートされていません。実装もかなり素朴で、char-set-containsは256までしかないことを利用したO(1)の手続きです。(ちなみに反復子的な手続きは逆に256回反復させます。)

これはLatin-1までの範囲に絞っているから可能なのであって、Unicodeをサポートするのであれば不可能な実装とも言えます。(ハッシュテーブルのエントリを2^32個作るのは現実的ではないでしょう。) セットが含む文字を全て舐めるO(n)の実装でもまぁ問題はないのかもしれないですが、二分木辺りを使ってO(log(n))くらいで抑えられるといいかもしれません。(疎な配列を使ったO(1)とかも実装によって現実的かも。ただエントリが増えるとハッシュテーブルと同じ問題が起きる?)

今回はSRFI-14を紹介しました。

2014-05-08

R7RSのMLで何が起きたのか

別にたいしたことは起きてないんだけど、ちょっと気になるやり取りが(というほどですらないが)あった。

事の発端はJohn CowanがR7RSで数値塔に関することの多数決を取るためにMLに草案を投げたことにある。草案はこれこれ(改)。 で、どこで線引きするんだ?という議論にPeter BexとJohn Cowanが発展させて、何故かAndy Wingoが噛み付いた。以下がAndyの投稿(意訳)

> 標準ってのは実装者とユーザ間の契約だ。仮定が少なすぎるとユーザが苦しむし
> 多すぎれば実装者が泣きを見ることになる。こいつはGoldilocks問題で、
> 大き過ぎず、小さ過ぎず、ちょうどいいべきだ。
この議論は率直に言ってばかげてる。Dybvig、Flatt、Clingerたちはどこだ?
Feeleyは?俺がもっとも尊敬する実装者たちだよ。
(もちろん、ShiroやPeterがいてくれることには感謝してるよ。けど彼らが今の有権者を
成功に導くことを期待できないよ。) 前述のやつらが興味持ってないなら、なんでまだ
Schemeって呼ぶのさ?
Kent Dybvig(Chezの実装者)、Matthew Flatt(Racketの実装者の一人)、William Clinger(Larcenyの実装者)、Marc Feeley(Gambitの実装者)ですね。Shiroさんは説明する必要すらないでしょうが、Gaucheの開発者です。Peter BexはKawaの開発者。AndyはRatification Voteでこんなコメントをしているので、全面的にR7RSに賛成なShiroさんPeterでは彼が望む姿に持っていけないという意味でしょうか?(ShiroさんとPeterがR7RSに全面的に賛成しているというのは僕の妄想です)
質問ついでに、R7RS-smallの最終稿はどこだよ?なんでr7rs.orgにないのさ?
コアになる部分に強力で質のいい処理系の参加がないんだったら、R7RSは永遠に
端っこのSchemeと大して違わない標準にしかならないね。それに、こいつらが標準に
なるかもってのも、Johnが(勝手に)決めたことだろ?例えばvector-for-each、こいつを
Johnが投票用紙に並べて、多くの人が賛成票を投じた -- ひょっとしたら後者は必要で
すらなかったか、最低投票数とかないわけだし。SRFIプロセスはRnRSになったしな。
large版でこの違いと要求の議論が起きてるのって、なんていうか…なんだろうな。
なんていっていいのかも分からねぇよ。まぁ、頑張れや、けど俺を巻き込むな。
ちと訳に自信がない。現状のプロセスと人がいないことを憂いて去っていく感じですかね?これに対してJohn Cowanの返信
> この議論は率直に言ってばかげてる。Dybvig、Flatt、Clingerたちはどこだ?
> Feeleyは?
Wingoはどこだよ?
Andy WingoはGuileの開発者の一人ですね。 なかなか洒落が効いてて好きです。
(中略)

> large版でこの違いと要求の議論が起きてるのって、なんていうか…なんだろうな。
> なんていっていいのかも分からねぇよ。まぁ、頑張れや、けど俺を巻き込むな。
お仲間がいねぇからって、助けるわけでもなくお前はここを去るのか?高貴な身分だ
な!参加したらどうだよ?ここじゃ実装者視点ってのはすげぇ価値があるんだよ --
そいつが得られるならな。
中略の部分はWorking Groupではscheme-reports.orgの変更はできないし、r7rs.orgは別の人の持ち物だから触れもしないよ、という旨が書いてあります。r7rs.orgは2009年に出来て以来特になんら代わり映えしてないのですが、r7rs.comの方は一応R7RSの最終稿へのポインタがあったりするので、Andyの調査不足が露呈してたりします。

そういえば、上記のAndyが尊敬する処理系実装者のうち、Scheme処理系として開発を継続してる人っているの?RacketはRacket言語としての道を選んだし、ChezとLarcenyは開発とまってるし(ChezはCiscoで内部的に開発されているという噂もある)。Gambitは継続されてるのか、そこそこの頻度でコミットがあるな。

2014-05-07

O記法

CourseraのAlgorithms: Design and Analysis, Part 1の復習。別に毎週書くつもりはないんだけどこの記法しょっちゅう使うくせに厳密な定義を知らなかったので(入力nに対する計算量くらいしか知らなかった)、ちょっとメモ。

形式的な定義: T(n) = O(f(n)) iff there exists constants c*n0 > 0 such that T(n) ≦ c*f(n) for all cn0
ここで問題になるのがc*n0の存在で(実際これにはまった)、具体的には以下のような点n0を指す。

ここはc = 2となりT(n)と2f(n)が交差する点がn0となる。っでO(n)が適用されるのはn0以降になるのが味噌。逆に言うと、n
< n0まではT(n) = O(f(n))ではないということになる。
上記ではまったと書いたのだけど、これではまるのは例えば実際に計算量の増加率が高い順に並べるとかいうのをやった際に、nがかなり巨大にならないと反転しない場合(n0=10^13とか)。

ここからは適当な感想というか、考察というかになるんだけど、例えばO(2^(2^log(n)))なアルゴリズムとO(n^2 * log(n))なアルゴリズムがあった場合(底は10とするw)、nが10^5までは前者で10^6からは(もう少し前からだが)は後者のアルゴリズムを使う必要があるということになる。定義的には前者の方が計算量が少ないことになるんだけど、現実的にはねぇっていう話。

ついでにアルゴリズム的には計算量少なくても使用する空間が大きいと実装した際には速度出ないということはまぁ言わずもがなだわね。divide and conquerって使用する空間のこと考えないと涙出るし。こういうの書くと自分は以下に計算機科学の基礎(とか理論)ができてないのかがわかって凹むが、まぁ凹んだ部分は今からでも埋めればいいかと開き直ることにする。

2014-05-02

SRFI-13の紹介

(LISP Library 365参加エントリ)

SRFI-13は文字列操作を行う手続きを定義したSRFIです。SRFI-1と同様に超有名なSRFIなので知らない人はいないのではないでしょうか?今回はそんなSRFI-13の中から便利だけど意外と知られてなさそうな手続きを紹介します(RnRSに入っているものは除きます)。

string-tabulate
文字列の構築を行う手続きです。SRFI-1のlist-tabulateとほぼ同じですが、受け取る引数の順番が違います(先に手続きを受け取る)。以下のように使います。
;; make string range 0-255
(string-tabulate integer->char 256)
;; "\x0;\x1;\x2;\x3;\x4;\x5;\x6;\a\b\t\n\v\f\r\xE;\xF;\x10;\x11;\x12;\x13;\x14;\x15;\x16;\x17;\x18;\x19;\x1A;\x1B;\x1C;\x1D;\x1E;\x1F; !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7F;\x80;‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ\xFF;"

;; the first argument doesn't have to be integer->string
(string-tabulate (lambda (i) #\a) 256)
;; "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
ランダムな文字列がほしいときとかに使えそうです。

string-take string-take-right
string-drop string-drop-right
SRFI-1にあるtake/dropの文字列版です。使い方を載せるまでもないくらいそのままです。

string-prefix? string-prefix-ci?
string-suffix? string-suffix-ci?
第一引数に与えられた文字列が第二引数に与えられた文字列の接頭文字列・接尾文字列かどうかを判別します。オプションで引数に与えられて文字列の開始位置及び終了位置を指定可能です。-ci?で終わるものは大文字小文字を無視します。
(string-prefix? "http" "http://localhost")
;; #t

(string-prefix? "https" "http://localhost")
;; #f

string-replace
名前のとおり文字列の置き換えを行います。対象文字列、置き換え文字列、開始位置、終了位置を受け取り、新しく文字列を生成します。(破壊的操作ではない)
(string-replace "Hello horrible Scheme" "beautiful" 6 14)
;; "Hello beautiful Scheme"
参照実装ではsubstringしてappendしてるだけだったりします。処理系によっては最適化しているものもあるかもしれません。(ちなみに、Sagittariusは参照実装のままです。組み込みにしてメモリの割付を減らしてもいいかなぁとは思ったりしますが。)

string-tokenize
文字列をトークンに切り出してリストとして返します。オプション引数としてトークンにする文字セットを受け取ることが可能です。 デフォルトの文字セットはchar-set:graphicです。
(string-tokenize "Hello world!!")
;; ("Hello" "world!!")

(string-tokenize "Helloaaaworld!!" (string->char-set "edlowrH"))
;; ("Hello" "world")
文字セットに関する手続きはSRFI-14で定義されています。

その他にもパディングやトリムといった手続きがあるので、興味があれば覗いてみてはどうでしょう?

To good to be true?

No, it wasn't!

I've wrote about implementing Karatsuba multiplication in previous post (sorry it's in Japanese). So I wanted to make all internal bignum multiplication to use it. Now I've modified expt to use it. Then next step is of course benchmark :)

Actually, it didn't make any performance better and it's quite difficult to compare because of the bug. So I've decided to compare with Mosh and Ypsilon, especially Mosh which is using GMP internally. So I wrote this piece of code;
(import (rnrs) (time))
(define-syntax dotimes
  (syntax-rules ()
    ((_ (var i res) . body)
     (do ((limit i)
          (var 0 (+ var 1)))
         ((>= var limit) res)
       . body))
    ((_ (var i) . body)
     (do ((limit i)
          (var 0 (+ var 1)))
         ((>= var limit))
       . body))
    ((_ . other)
     (syntax-violation 'dotimes
                       "malformed dotimes"
                       '(dotimes . other)))))

;; To avoid compile time constant folding
;; Sagittarius' compiler is a bit smarter than
;; the others :)
(define v (make-vector 1 512))

(time (dotimes (i 5000) 
        (let ((e (vector-ref v 0)))
          (expt #x123456789ABCDEF12 e))))

(display 'done) (newline)
And for Mosh, this was also required;
;; file name time.mosh.sls
(library (time)
    (export time)
    (import (mosh)))
Now, it's good to go. It took a bit time to figure out that Sagittarius compiler computes constant variable in compile time even though I implemented it. (That causes benchmark time always 0.00ms, so it was indeed 'too good to be true'. well but true though :-P)

The result was like this;
% ./build/sash.exe num.scm

;;  (dotimes (i 5000) (let ((e (vector-ref v 0))) (expt 20988295479420645138 e)))
;;  3.160898 real    3.2290000915527344 user    0.000000 sys
done

% ypsilon num.scm

;;  4.47794 real    4.524029 user    0.0 sys
done

% mosh num.scm

;;4.463438034057617 real 4.18 user 0.234 sys
done
As usual, I was very surprised with Ypsilon. It doesn't use GMP but the same time as Mosh. Anyway, Sagittarius' expt is faster than any others now. (could be before as well but incorrectly.)