treeコマンドを作る
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]))