repl-serverを立ち上げてclojureの起動時間を短縮

はじめに

clojure,scala,jrubyとかjvmの上に乗った言語は起動がとても遅い。*1
そんなわけで、これをどうにかしたいと思っていたのでした。
emacsclientのようにserverを立ち上げてそれにアクセスするという形にすれば起動は一回で済みそうです。
まだまだ改善の余地がたくさんあるけれど。途中経過。

todo

  • いろいろなオプションに対応していない。
  • #
    • こんな書式に対応していない*2
  • portとipアドレスが分かれば他人でも実行できてしまう。
  • sandbox化した方が安全かも?
    • with-temp-nsを使うと楽(contrib)
    • contribのclasspathを実行時に設定しないとダメなのか。*3
    • (このあたりもう少し楽にできる方法がないか考え中)

how to use

clojure nrepl.clj 12345 #server
gosh nrepl-client.scm -e "(+ 10 20 30)"  #client

###
# time clojure -e "(+ 10 20 40)"
#  1.06s user 0.05s system 117% cpu 0.946 total
# time gosh nrepl-client.scm -e "(+ 10 20 40)"
#  0.04s user 0.01s system 115% cpu 0.043 total
# 

一応、20倍くらいはやくなっている。

server

(import '(java.net ServerSocket Socket SocketException)
        '(java.io InputStreamReader PrintWriter)
        '(clojure.lang LineNumberingPushbackReader))
 
(defn nrepl [s]
  (let [sin (-> (. s getInputStream) (InputStreamReader.) (LineNumberingPushbackReader.))
	sout (-> (. s getOutputStream) (PrintWriter. true))]
    (binding [*in* sin, *out* sout, *err* sout]
      (clojure.main/repl 
       :print (comp (fn [_] (prn 'end-of-output)) prn) ;end-of-output is terminator
       :prompt (fn [])))))

(defn- on-thread [fun]
  (doto (Thread. fun) (.start)))

(defn start-server [port]
  (printf "start with port=%d\n" port)
  (let [ss (ServerSocket. port 1)]
    (on-thread #(try
		 (while true (nrepl (.accept ss)))
		 (catch SocketException e)))))

(defn- main [args]
  (if-let [[port & rest] args]
    (start-server (Integer/parseInt port))
    (println "<program> <port-number>")))

(main *command-line-args*)

client

'end-of-outputまで読み込むという仕様は綺麗じゃないかも?

#!/usr/bin/env gosh
(use gauche.parseopt)
(use gauche.net)

(define (send data port) 
  (let1 s (make-client-socket 'inet "localhost" port) 
  (call-with-client-socket s
    (lambda (in out)
      (write data out)
      (newline out)
      (flush out)
      (let loop ()
	(let1 line (read in)
	  (cond [(eq? 'end-of-output line) (flush)]
		(else (write line) (newline) (loop)))))))))

(define (main args)
  (let-args (cdr args) 
      [(port "p|port=i" 12345)
       (expr "-e|exp=s")]
    (let1 data (if expr (read-from-string expr) (read))
      (send data port)))
  0)

*1:実行は結構速いけれど

*2:gaucheのreadを使って手抜きをしているせいです

*3:そんなこともあってclojure.contrib.command-lineを使っていない