LoginSignup
2
1

More than 1 year has passed since last update.

Clojure入門メモ (Python & Common Lisp経験者)

Last updated at Posted at 2023-02-21

Clojrueに入門したのでメモ。私自身は、PythonとCommon Lispに触れたことがあります。

気軽に走らせるならclojure-cli

Clojureを動かそうと思って調べると、Leiningen (lein)、boot、clojure-cli (clj)の3つが出てくる。
100行もないようなコードを書いて動かすのであれば、clojure-cliを利用するべき。他の2つは小さなコードを書く場合でも、プロジェクトフォルダを要求してくる。

ライブラリ・パッケージは deps.edn + require

Pythonでのpip install xxx+import xxx、Common Lispでの(ql:quickload xxx)に相当する操作は、Leiningenとclojure-cliで異なる。
clojure-cliでは~/.clojure/deps.ednに欲しいライブラリを書いておいて (Pythonのpip install相当)、

;; ~/.clojure/deps.edn
;; 動かしたいコードが書いてあるファイルと同じフォルダに置いても良い。
{
  :deps {
    org.clojure/data.csv  {:mvn/version "1.0.1"}
    metasoarous/oz {:mvn/version "2.0.0-alpha5"}
  }
}

動かしたコード内でrequireをする (Pythonのimport相当)。Python等とは異なり、ライブラリのバージョンを指定する必要がありそう。

;; 動かしたいファイルの先頭に下記を記載
(ns user
  (:require [clojure.data.csv :as csv]
            [oz.core :as oz]))

ライブラリによってはインストールにそこそこ時間がかかる。clj -Sverboseをターミナルで実行すれば、①インストール状況と、②前述のdeps.ednが反映されているかどうかが確認できる。
Leiningenではdeps.ednの変わりに、プロジェクトフォルダのルートフォルダにproject.cljを置いて、下記のように記載をする。

(defproject ...
  :dependencies [[org.clojure/clojure "1.11.1"]
                 [org.clojure/data.csv "1.0.1"]
                 [metasoarous/oz "2.0.0-alpha5"]]
  ...)

bootは触っていないが、多分Leiningenと同じ。

Emacsならcider-mode

EmacsでClojureを触るなら、cider-modeがデファクトスタンダード。Common LispのSlimeであれば(slime)で動くが、Clojureでは(cider-jack-in)を使う。clojure-cli/Leiningenのインストール状況次第では、(setq cider-jack-in-default 'lein)等をしておく必要があるかもしれない
init.elcider-jack-inを何かのキーに割り当てる際、(cider-jace-in)だけでは動かない。(cider-jack-in '(:jack-in-type clj :project-type clojure-cli :edit-project-dir t))のようにする必要がある (cider.elcider-jack-in-universal-options参照すると良い)。
M-x cider-eval-bufferでバッファ全体を評価できるが、毎回ファイルをセーブするか聞いてくる。これを防ぐため、init.el(setq cider-save-file-on-load t)を書いておくと良い。

Vimならfireplace

Vimの場合、fireplaceというプラグインがスター数1.7kで人気。日本人開発のvim-icedもあるが、スター数が500前後とfireplaceの後を追っている状況。他にもConjureというプラグインのスター数が1.2k。
vim-iced/Conjureには触れていないので分からないが、fireplaceの場合だと、Vimとは別にターミナルを立ち上げてclj -Sdeps '{:deps {cider/cider-nrepl {:mvn/version "0.30.0"} }}' -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]" を走らせておく必要がある (Emacsと違い、エディタがそこまで面倒を見てくれない)。またEmacsのcider-modeと異なり、出力結果はエコー領域に出るのみ (cider-modeだと出力はREPLに書き込まれていく) のためprintデバッグがやりにくい。
(追記)Conjureの方が簡単そうです。

Clojureのデータ型

恐らく正確ではないが、概要は下記の通り。

フォーム

フォーム ≒ 一般的に言うデータ型。

  • 文字: Common Lispとほとんど同じ。\aで表される。
  • 文字列: Common Lisp / Pythonとほとんど同じ。
  • 数値: Common Lisp / Pythonとほとんど同じ。ただしfloat/int/ratioの区別あり ((= 2 2.0) ; falseかつ(= 0.5 (/ 1 2)) ; false)。
  • シンボル: Common Lispのシンボルと同じだが、大文字小文字の区別あり ((= 'hoge 'HOGE) ; false)。
  • ブール値: truefalse。Common Lispのt/nilとほとんど同じ。
  • Nil: ただしnilも存在する。関数の返すべき値がないときに、nilが返される印象。空リストではない ((= nil '()) ; false) が、ifではfalse扱いされる ((if nil true false) ; false)。
  • キーワード: コロンに続く文字列:keyで表される。後述のマップで良く使われるイメージ。マップでなぜアトムを利用しないのか、分かっていない。
  • リスト: '()。 Common Lispのリストとほとんど同じ。もっぱら関数呼び出しのために使われて、データの入れ物としては使わない。空リストはfalseではない ((if '() true false) ; true)。
  • ベクタ: [x y z]。Pythonの配列とほとんど同じ。ただし、あまり二次元以上のものは見ない。
  • マップ: {:key val}。Pythonの辞書とほとんど同じ。
  • セット: #{:a :b :c}。Pythonの集合とほとんど同じ。

コレクション

コレクションは、リスト・ベクタ・マップ・セットを引っくるめた総称。関数を調べていると、collという引数名で良く出てくる。他言語でも似た状況が見られるように、コレクション関数は上記4つのデータ型どれにも適用可能で便利な一方、遅いことも多い。例えばコレクションの最初の要素を返すfirstは、上記のデータ構造全てに適用できる。

(first '(1 2 3)) ; 1
(first [1 2 3]) ; 1
(first {:a 1 :b 2}) ; [:a 1]
(first #{1 2 3}) ; 1

コレクションの中でも、リストとベクタは順番が意味を持つデータ構造なのでシーケンス (= 順序)と呼ばれる。関数を調べていると、seqという引数名で良く見かける (分かっていないが、aseqというのも見かける)。ただ入門者の身からすると、シーケンスであろうとなかろうと使える関数にほとんど差がないため、コレクション ≒ シーケンスの認識でも問題はなさそうに思える。

変数を利用した状態の管理

Clojureでは他言語のように、変数に次々と値を入れて状態を管理するというやり方が推奨されていない。例えば、while文は存在していない。基本的には、数多く用意された組み込みのコレクション操作関数を駆使して全部解こうとする (Pythonでいう内包表記の変形版を利用するイメージ)。
どうしても状態の管理が必要なときは、refatomagentvarを利用する (状態の同期が必要かどうか等で利用する関数が変わる)。例えばイベントの受付で、来場者 (誰が来るかは分からない) の名前を記録する (= 入場者の状態を管理する) ことを考える。Python / Clojureの書き方は、それぞれ下記のようになる。

# python
visitors = []
while True:
    visitors += [input()]
;; clojure
(def visitors (atom []))
(loop []
  (swap! visitors conj (read-line))
  (recur))

;; こうは書かない
(def visitors [])
(loop []
  (def visitors (conj vistors (read-line)))
  (recur))

;; これは推奨されない (状態を再帰で管理している)
(loop [visitors []]
  (recur (conj visitors (read-line)))

clojureの方が (atomを利用する) ひと手間多いところがミソで、①これで状態の同期・管理を明示的にするとともに、②状態を管理することに対して (精神的な) 税金をかけている1

Common LispとClojureの異なる点

  • car系の関数はない。
  • 末尾再帰は最適化されないので使わない。loop/recurという局所的な再帰構造を作る関数はあるが、まずはコレクション操作関数で頑張ることが推奨されている。
    • Common Lispは何でも再帰 (状態が入る余地がある) + マクロで抽象化しようとするが、Clojureは何でもコレクション操作関数 (状態が入らない) で抽象化しようとする。
  • 関数の呼び出しは丸括弧で統一。(xxx ...)という形を見たら、それは必ず関数呼び出し (リストではない)。
  • Schemeのように関数名/変数名の名前空間が一緒なので、mapする際に#'は必要ない ((map inc [1 2 3]) ; (2 3 4))。
  • ベクタの中身が評価される。Common Lispだと(list (something))とか`(,something)だが、Clojureだと[(something)]
  • ベクタを関数のように利用できる (([1 2 3] 2) ; 3)。
  • マップを関数のように利用できる (({:a 1 :b 2} :a) ; 1)。マップなら逆も可 ((:a {:a 1 :b 2}) ; 1)。
  • (doc 関数名)で、REPLで関数定義を確認できる。関数定義の記載もシンプルで良い。
  • Clojurianという言葉があるように、言語の設計として特定のスタイルを推奨している。PerlよりもPython的。
    • Common Lispはアイデアをそのままコードに表現できるようになっている2けれど、柔軟すぎるきらいがある。Clojureはアイデアをコードにする前に、より関数的に洗練させることを強要する3
  • Clojureのコードは暗号のように見えがち。PythonよりもPerl的。
    • 基本的な発想は、シェルがパイプでデータを流していくように、Clojureはコレクションでデータを流していくというもの。恐らく、絶対に必要な箇所以外でlet/def(setq)を利用するのはClojureらしくない。
    • システム外部の状態は本質的だけど、システム内部の状態は無駄という発想
  1. 『関数的プログラミングを理想とするというのは,プログラムが決して副作用を使ってはいけないということではない.ただ必要以上に使うべきでないということだ.この習慣を育てるには時間がかかるかもしれない.一つの方法は,以下のオペレータは税金がかかっているつもりで扱うことだ:set setq setf ...』, On Lisp (野田開 訳), ポール・グレアム, http://www.asahi-net.or.jp/~kc7k-nd/onlispjhtml/functionalProgramming.html

  2. Common Lispは、プログラマにとって (Simpleではなく) Easyであることを善としているとも考えられる。コードは、抽象化の層が重なることになる → 『本当の非効率性とは、マシンの時間を無駄にすることではなく、プログラマの時間を無駄にすることだ』, 百年の言語, ポール・グレアム (川合史朗 訳), http://practical-scheme.net/trans/hundred-j.html

  3. 昔は計算資源を最適化するために人間の時間が取られていたが、Clojureは問題設定を最適化することに人間の時間を使おうとしているように思われる。ポール・グレアムは良いコードの条件を「解くべき問題が簡潔に表現されていること」とした一方で、リッチ・ヒッキーは「解くべき問題が簡潔になっていること」としているように思われる。 → 『私が自分のプログラムにパターンを見付けたら、それはどこかがおかしいというサインだ。プログラムの形は、それが解くべき問題のみを反映すべきだ』, 技術野郎の復讐, ポール・グレアム (川合史朗 訳), http://practical-scheme.net/trans/icad-j.html

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1