Clojureをインストールするには

「7つの言語 7つの世界」第7章の学習のために、Clojure(クロージャー)言語をインストールします。

7つの言語 7つの世界

  • Ruby
  • Io
  • Prolog
  • Scala
  • Erlang
  • Cloijure
  • Haskell

書籍では、Clojure 1.2 プレリリース版、Leiningen(ライニンゲン)を使っているよ

Clojure公式サイト/Leiningen公式サイト

書籍を学習するだけなら、Leiningenをインストールするだけでいいよ。

Clojure
Leiningen
Leiningen: automating Clojure projects

Windows 10

https://djpowell.github.io/leiningen-win-installer/ から leiningen-win-installer 1.0をダウンロードします。

インストーラを起動します。「Next>」ボタンをクリックします。

インストール先は、%USERPROFILE%\.lein です。そのままで「Next>」ボタンをクリックします。

JDKを選択します。「Next>」ボタンをクリックします。

「Install」ボタンをクリックします。

プログレスバーが進み、

コマンドプロンプトが起動して、もろもろがダウンロードされます。数分間かかりました。ダウンロードが終了すると、コマンドプロンプトは自動で閉じます。

インストールが完了しました。「Run a Clojure REPL」のチェックを付けて、「Finish」ボタンをクリックします。

コマンドプロンプトが起動します。必要ファイルのダウンロードに少し時間がかかりました。

Clojure 1.10.0 でした。「user=>」プロンプトがClojure REPLのプロンプトです。quit またはexitで終了します。

コマンドプロンプトを開いて、学習フォルダに移動します。

C:> cd study_7lang\clojure

leinコマンドで、firstdayプロジェクトを新規作成します。firstdayフォルダへcdします。

C:> lein -version Leiningen 2.9.3 on Java 1.8.0_251 Java HotSpot(TM) 64-Bit Server VM C:> lein new firstday Generating a project called firstday based on the 'default' template. The default template is intended for library projects, not applications. To see other templates (app, plugin, etc), try `lein help new`. C:> cd firstday

lein replclojureを起動します。Ctrl+D または exit または quit で終了します。

C:> lein repl Retrieving org/clojure/clojure/1.10.1/clojure-1.10.1.pom from central Retrieving org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.pom from central Retrieving org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.pom from central Retrieving org/clojure/spec.alpha/0.2.176/spec.alpha-0.2.176.jar from central Retrieving org/clojure/clojure/1.10.1/clojure-1.10.1.jar from central Retrieving org/clojure/core.specs.alpha/0.2.44/core.specs.alpha-0.2.44.jar from central nREPL server started on port 59308 on host 127.0.0.1 - nrepl://127.0.0.1:59308 REPL-y 0.4.4, nREPL 0.6.0 Clojure 1.10.1 Java HotSpot(TM) 64-Bit Server VM 1.8.0_251-b08 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e firstday.core=> quit C:>

macOS(Mac OS X) 10.15.4

homebrewleiningenをインストールします。

$ brew install leiningen Updating Homebrew... ==> Downloading https://homebrew.bintray.com/bottles/leiningen-2.9.3.catalina.bottle.tar.gz ==> Downloading from https://akamai.bintray.com/70/70a6ab2365ebf7efd3d2d84fd89d88b59c6417df9225562784f8b6184653a49b?__gda__=exp=1593 ######################################################################## 100.0% ==> Pouring leiningen-2.9.3.catalina.bottle.tar.gz ==> Caveats Dependencies will be installed to: $HOME/.m2/repository To play around with Clojure run `lein repl` or `lein help`. Bash completion has been installed to: /usr/local/etc/bash_completion.d zsh completions have been installed to: /usr/local/share/zsh/site-functions ==> Summary 🍺 /usr/local/Cellar/leiningen/2.9.3: 9 files, 14MB

leinのバージョンは、2.9.3 でした。

$ which lein /usr/local/bin/lein $ lein -version Leiningen 2.9.3 on Java 1.8.0_111 Java HotSpot(TM) 64-Bit Server VM

leinfirstdayプロジェクトを新規作成して、lein replclojureを起動します。clojureのバージョンは1.10.1 でした。

Ctrl+D または exit または quit で終了します。

$ lein new firstday Generating a project called firstday based on the 'default' template. The default template is intended for library projects, not applications. To see other templates (app, plugin, etc), try `lein help new`. $ cd firstday $ lein repl Retrieving nrepl/nrepl/0.6.0/nrepl-0.6.0.pom from clojars Retrieving clojure-complete/clojure-complete/0.2.5/clojure-complete-0.2.5.pom from clojars Retrieving clojure-complete/clojure-complete/0.2.5/clojure-complete-0.2.5.jar from clojars Retrieving nrepl/nrepl/0.6.0/nrepl-0.6.0.jar from clojars nREPL server started on port 62742 on host 127.0.0.1 - nrepl://127.0.0.1:62742 REPL-y 0.4.4, nREPL 0.6.0 Clojure 1.10.1 Java HotSpot(TM) 64-Bit Server VM 1.8.0_111-b14 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e firstday.core=>

Ubuntu 18.04

aptleiningenをインストールします。

$ sudo apt install leiningen

leinのバージョンは、2.8.1 でした。

$ which lein /usr/bin/lein $ lein -version Leiningen 2.8.1 on Java 1.8.0_252 OpenJDK 64-Bit Server VM

leinfirstdayプロジェクトを新規作成して、lein replclojureを起動します。clojureのバージョンは1.8.0 でした。

$ lein new firstday Generating a project called firstday based on the 'default' template. The default template is intended for library projects, not applications. To see other templates (app, plugin, etc), try `lein help new`. $ cd firstday $ lein repl nREPL server started on port 34345 on host 127.0.0.1 - nrepl://127.0.0.1:34345 WARNING: cat already refers to: #'clojure.core/cat in namespace: net.cgrand.regex, being replaced by: #'net.cgrand.regex/cat REPL-y 0.3.7, nREPL 0.2.12 Clojure 1.8.0 OpenJDK 64-Bit Server VM 1.8.0_252-8u252-b09-1~18.04-b09 Docs: (doc function-name-here) (find-doc "part-of-name-here") Source: (source function-name-here) Javadoc: (javadoc java-object-or-class-here) Exit: Control+D or (exit) or (quit) Results: Stored in vars *1, *2, *3, an exception in *e

セルフスタディ

1日目

• 文字列 stn 文字より長い場合に真を返す関数 (big st n) を実装してみよ.

(defn big [st n] (> (count st) n))
user=> (defn big [st n] #_=> (> (count st) n)) #'user/big user=> (big "aaaa" 1) true user=> (big "aaaa" 4) false user=> (big "" 0) false user=> (big "" -1) true

col コ レ ク シ ョ ン の 型 に 応 じ て , :list , :map,:vector を返す関 数(collection-type col) を書け.

class関数で、クラス名を取得できます。

user=> (class (list 1 2)) clojure.lang.PersistentList user=> (class {:k :v}) clojure.lang.PersistentArrayMap user=> (class [1 2]) clojure.lang.PersistentVector

返ってきた clojure.lang.PersistentList は、文字列ではありませんでした。

user=> (= (class (list 1 2)) "clojure.lang.PersistentList") false user=> (= (class (list 1 2)) clojure.lang.PersistentList) true

次のようなマップを用意しました。class関数の戻り値で、マップから取り出すことができました。

user=> (def mymap {clojure.lang.PersistentList :list, #_=> clojure.lang.PersistentArrayMap :map, #_=> clojure.lang.PersistentVector :vector}) #'user/mymap user=> (mymap (class (list 1 2))) :list user=> (mymap (class {:k :v})) :map user=> (mymap (class [0 1])) :vector

1つの関数にまとめます。

(defn collection-type [col] ({clojure.lang.PersistentList :list, clojure.lang.PersistentArrayMap :map, clojure.lang.PersistentVector :vector} (class col)))
user=> (defn collection-type [col] #_=> ({clojure.lang.PersistentList :list, #_=> clojure.lang.PersistentArrayMap :map, #_=> clojure.lang.PersistentVector :vector} (class col))) #'user/collection-type user=> (collection-type '(1 2)) :list user=> (collection-type {:k :v}) :map user=> (collection-type [0 1]) :vector

2日目

• マクロを使って else 付きの unless 構文を実装してみよ.

引数が3個のバージョン

(defmacro unless [condition ifbody elsebody]   (list 'if (list 'not condition) ifbody elsebody))

引数が2個または3個のバージョン

(defmacro unless ([condition ifbody] (list 'if (list 'not condition) ifbody)) ([condition ifbody elsebody] (list 'if (list 'not condition) ifbody elsebody)))
user=> (unless true (println "A") (println "B")) B nil user=> (unless false (println "A") (println "B")) A nil user=> (unless false (println "A")) A nil user=> (unless true (println "A")) nil

• defrecord を使って,プロトコルを実装する型を書け

まだ、defprotocol、defrecordを理解していないよ。

(defprotocol Person (get_name [p]) (get_age [p])) (defrecord SimplePerson [name, age] Person (get_name [_] name) (get_age [_] age) Object (toString [this] (str "[" (get_name this) ", " (get_age this) "]")) )
user=> (def taro (SimplePerson. "taro" 21)) #'user/taro user=> (get_name taro) "taro" user=> (get_age taro) 21 user=> (str taro) "[taro, 21]" user=> (def hanako (SimplePerson. "hanako", 20)) #'user/hanako user=> (get_name hanako) "hanako" user=> (get_age hanako) 20 user=> (str hanako) "[hanako, 20]"

3日目

• 参照を使ってメモリ内に口座を表すベクタを作成せよ.口座の残高を変更するdebit 関数と credit 関数を作成せよ.

ベクターで何を表現すればいいのかな?

口座番号と残高はどう?

taroさんの口座番号123、残高100円を、[123, 100] と表すことにします。

(defn account_create [num v] (ref [num v])) (defn account_credit [ac v] (dosync (ref-set taro_ac [(taro_ac 0) (+ (taro_ac 1) v)]))) (defn account_debit [ac v] (dosync (ref-set taro_ac [(taro_ac 0) (- (taro_ac 1) v)])))
user=> (def taro_ac (account_create 123 100)) #'user/taro_ac user=> taro_ac #object[clojure.lang.Ref 0x1ac24867 {:status :ready, :val [123 100]}] user=> @taro_ac [123 100] user=> (account_credit taro_ac 1000) [123 1100] user=> taro_ac #object[clojure.lang.Ref 0x1ac24867 {:status :ready, :val [123 1100]}] user=> @taro_ac [123 1100] user=> (account_debit taro_ac 500) [123 600] user=> taro_ac #object[clojure.lang.Ref 0x1ac24867 {:status :ready, :val [123 600]}] user=> @taro_ac [123 600]

口座0が1000円、口座1が2000円、口座2が3000円を [1000 2000 3000]と表現する解答例を見つけたよ

7つの言語7つの世界 Clojure 3日目 セルフスタディやってみた
キューが空のときブロックして,キューに新しい項目が登録されるのを待つキュー の実装 Java の BlockingQueue → Clojure の seque →...

ここでは「居眠り床屋」問題について概説する.この問題は 1965 年に Edsger Dijkstraによって作成された.内容は次のとおりだ.

  • 営業中の床屋がある.
  • 客は, 10 〜 30 ミリ秒の任意の間隔でやってくる.
  • 床屋の待合室には 3 つの椅子がある.
  • 理容師は一人で,理容椅子は一脚用意されている.
  • 理容椅子が空であれば,客が座り,理容師を起こして,髪を切ってもらう.
  • 理容師が他の客の髪を切っていたら,待合室の椅子に座る.ただし待合室に空きがなければ,客は髪を切らずに黙って帰る.
  • 髪を切るには 20 ミリ秒かかる.
  • 髪を切ってもらった客は,立ち上がって店から出て行く.

この理髪店で, 10 秒間にさばける客の数を決定するマルチスレッドプログラムを書け.

visit_counterやcut_counterをagentにしたバージョン。

(def rnd (new java.util.Random)) (defn myrnd [a b] (+ a (. rnd nextInt (- b a)))) (defn is_on_time [start_millis] (< (- (System/currentTimeMillis) start_millis) 10000)) (defn visit_run [visit_counter start_millis visit_queue] (do ;(println "visit_counter:" visit_counter) ;(println "visit wait start") (Thread/sleep (myrnd 10 30)) ;(println "visit wait end") (if (is_on_time start_millis) (do (. visit_queue offer 1) (recur (inc visit_counter) start_millis visit_queue)) visit_counter))) (defn cut_run [cut_counter start_millis visit_queue] (do ;(println "cut_counter:" cut_counter) (. visit_queue take) ;(println "cut start") (Thread/sleep 20) ;(println "cut end") (if (is_on_time start_millis) (recur (inc cut_counter) start_millis visit_queue) cut_counter ))) (defn sleeping_barber [] (let [visit_queue (new java.util.concurrent.ArrayBlockingQueue 3) visit_counter (agent 0) cut_counter (agent 0) start_millis (System/currentTimeMillis)] (send visit_counter visit_run start_millis visit_queue) (send cut_counter cut_run start_millis visit_queue) (Thread/sleep 3000) (println "#millis:" (- (System/currentTimeMillis) start_millis)) (println "#visit_counter:" @visit_counter) (println "#cut_counter:" @cut_counter) (Thread/sleep 3000) (println "#millis:" (- (System/currentTimeMillis) start_millis)) (println "#visit_counter:" @visit_counter) (println "#cut_counter:" @cut_counter) (await visit_counter cut_counter) (println "#millis:" (- (System/currentTimeMillis) start_millis)) (println "#visit_counter:" @visit_counter) (println "#cut_counter:" @cut_counter) ))

10秒経過すると、visit_counterやcut_counterが更新されます。メインスレッドで、visit_counterやcut_counterの途中経過を表示しても、10秒経過するまでは、0のままです。

user=> (sleeping_barber) #millis: 3000 #visit_counter: 0 #cut_counter: 0 #millis: 6001 #visit_counter: 0 #cut_counter: 0 #millis: 10004 #visit_counter: 518 #cut_counter: 494 nil

訪問者数 visit_counteragentにすると、スレッドが終わるまで、値が更新されないんだね。

客が訪問するたびに visit_counterを更新するバージョン

(def rnd (new java.util.Random)) (defn myrnd [a b] (+ a (. rnd nextInt (- b a)))) (defn is_on_time [start_millis] (< (- (System/currentTimeMillis) start_millis) 10000)) (defn visit_run [_ start_millis visit_queue visit_counter] (do ;(println "visit wait start") (Thread/sleep (myrnd 10 30)) ;(println "visit wait end") (if (is_on_time start_millis) (do (. visit_queue offer 1) (swap! visit_counter inc) (recur :dummy start_millis visit_queue visit_counter))))) (defn cut_run [_ start_millis visit_queue cut_counter] (do (. visit_queue take) ;(println "cut start") (Thread/sleep 20) ;(println "cut end") (if (is_on_time start_millis) (do (swap! cut_counter inc) (recur :dummy start_millis visit_queue cut_counter))))) (defn sleeping_barber [] (let [visit_queue (new java.util.concurrent.ArrayBlockingQueue 3) visit_agent (agent 0) visit_counter (atom 0) cut_agent (agent 0) cut_counter (atom 0) start_millis (System/currentTimeMillis)] (send visit_agent visit_run start_millis visit_queue visit_counter) (send cut_agent cut_run start_millis visit_queue cut_counter) (Thread/sleep 3000) (println "#millis:" (- (System/currentTimeMillis) start_millis)) (println "#visit_counter:" @visit_counter) (println "#cut_counter:" @cut_counter) (Thread/sleep 3000) (println "#millis:" (- (System/currentTimeMillis) start_millis)) (println "#visit_counter:" @visit_counter) (println "#cut_counter:" @cut_counter) (await visit_agent cut_agent) (println "#millis:" (- (System/currentTimeMillis) start_millis)) (println "#visit_counter:" @visit_counter) (println "#cut_counter:" @cut_counter) ))

incするとすぐに、visit_counterやcut_counterが更新されます。メインスレッドで、visit_counterやcut_counterの途中経過を表示すると、値が更新されています。

user=> (sleeping_barber) #millis: 3000 #visit_counter: 150 #cut_counter: 145 #millis: 6001 #visit_counter: 304 #cut_counter: 294 #millis: 10011 #visit_counter: 508 #cut_counter: 492 nil

visit_agentやcut_agentの値を更新も表示もしていないよ?

そうね、スレッドを作るためだけのagentね。

agentが、JavaのFutureTask
agentに投げる関数が、JavaのCallable
って感じかしら?

5回実行したところ、訪問客数は494〜506、髪を切った客数は487〜492でした。

参考記事

おすすめ書籍

ハロウェイ,ステュアート(著) ベドラ,アーロン(著) オーム社 2013
ニコラ・モドリック (著), 安部 重成 (著) 技術評論社 2013
登尾 徳誠 (著) 工学社 2014
タイトルとURLをコピーしました