Clojureの練習として、treeコマンドを作って・・・みようとしたのですが、
最初いきなりClojureで作ろうとして混乱してしまいました。
そこで、まず自分が使いなれたPythonでtreeを作ってみました。
実行例:
$> python tree.py hello-clojure P:\\hello-clojure
--build.xml |
--COPYING |
--html |
`--index.html |
Pythonのコード:
#encoding:shift-jis #tree.py from __future__ import division, print_function, unicode_literals import os import sys from os.path import * from operator import methodcaller def add_prefix_to_strs(prefix, strs): return [prefix + s for s in strs] def tree_lines_(path, is_last=True): path = abspath(path) if is_last: branch_str = "`--" indent_str = " " else: branch_str = "|--" indent_str = "| " lines = [branch_str + basename(path)] if isdir(path): entries = os.listdir(path) entries.sort(key=methodcaller("lower")) for i, entry in enumerate(entries): is_last_entry = i == len(entries) - 1 entry_lines = tree_lines_(join(path, entry), is_last_entry) entry_lines = add_prefix_to_strs(indent_str, entry_lines) lines.extend(entry_lines) return lines def tree_lines(path): path = abspath(path) lines = [] lines.append(path) lines.append("|") entries = os.listdir(path) entries.sort(key=methodcaller("lower")) for i, entry in enumerate(entries): is_last_entry = i == len(entries) - 1 entry_lines = tree_lines_(join(path, entry), is_last_entry) lines.extend(entry_lines) return lines def print_lines(lines): for line in lines: print(line) def main(): if 2 <= len(sys.argv): if isdir(sys.argv[1]): root = sys.argv[1] else: root = dirname(sys.argv[1]) else: root = os.getcwd() print() print_lines(tree_lines(root)) if "__main__" == __name__: main()
tree最低限の機能しか無いのですが、これをClojureに翻訳してみます。
Clojureのコード:
;tree.clj (ns tree (:import java.io.File) (:use clojure.contrib.str-utils)) (defn add-prefix-to-strs [prefix strs] (for [s strs] (.concat prefix s))) (defn sub-entries [dir] (for [entry (.list dir)] (File. dir entry))) (defn with-is-last [seq] (partition 2 (concat (interleave (repeat false) (drop-last seq)) (list true (last seq))))) (defn tree-lines- [file, last?] (let [[prefix prefix-sub] (if last? ["`--" " "] ["|--" "| "])] (concat [(.concat prefix (.getName file))] (when (.isDirectory file) (let [entries (with-is-last (sort (sub-entries file)))] (for [[last-entry? entry] entries line (add-prefix-to-strs prefix-sub (tree-lines- entry last-entry?))] line)))))) (defn tree-lines [root-dir] (concat [(.getAbsolutePath root-dir) "|"] (let [entries (with-is-last (sub-entries root-dir))] (for [[last-entry? entry] entries line (tree-lines- entry last-entry?)] line)))) (defn print-lines [lines] (doseq [line lines] (println line))) (defn get-dir [file] (if (.isDirectory file) file (.getParentFile file))) (let [root (if (nil? *command-line-args*) (File. ".") (get-dir (File. (first *command-line-args*))))] (print \newline) (print-lines (tree-lines root)))
気づいた点など
- パスやディレクトリ構造はjava.io.Fileをimportして使う(Clojure var.1.2 以降は(:use clojure.java.io))
Clojureにはシーケンスの長さを取得する関数が無い?ウソです、countで取得できます。- ClojureにはPythonのzip関数が無い?
- Python版28行目のような「リストの最後で」という条件式を、Clojureに直訳することはできない?
- 取り合えず、Clojureでは「リストの最後で」はwith-is-last関数として独立させる必要がある(Pythonのようにコピペすることは出来なさそう)
- Python版16行目〜21行目のようなifの中で複数の変数に代入するパターンは、Clojureに翻訳するとletとifがネストする。
- 全体的にClojureはカッコのネストが深くなりがち
- ネストが深くなると、コードを理解するのも、カッコの対応を保つのも難しくなるの
- ネストを避けるには短い関数に分割せざるを得ない
結論
実はClojure(Lisp)はプログラマに短い関数を書くよう強制する言語であった!
GAEjでcompojureを使う場合、cljの先頭で
(ns servlet (:gen-class :extends javax.servlet.http.HttpServlet) (:use compojure.html))
としてはいけません!!
もし、compojure.htmlを使おうとすると・・・
HTTP ERROR 500 Problem accessing /. Reason: java.rmi.server.UID is a restricted class. Please see the Google App Engine developer's guide for more details. Caused by: java.lang.NoClassDefFoundError: java.rmi.server.UID is a restricted class. Please see the Google App Engine developer's guide for more details. at com.google.appengine.tools.development.agent.runtime.Runtime.reject(Runtime.java:51) at org.apache.commons.fileupload.disk.DiskFileItem.(DiskFileItem.java:103) at java.lang.Class.forName0(Native Method) (以下略)
compojure.htmlのいづれかのモジュールがjava.rmi.server.UIDを要求していると思われます。具体的にどれなのかは知りません。
必要なものだけuseするようにしましょう。
(ns servlet (:gen-class :extends javax.servlet.http.HttpServlet) (:use [compojure.http servlet routes]))