「7つの言語 7つの世界」第7章の学習のために、Clojure(クロージャー)言語をインストールします。
7つの言語 7つの世界
- Ruby
- Io
- Prolog
- Scala
- Erlang
- Cloijure
- Haskell
書籍では、Clojure 1.2 プレリリース版、Leiningen(ライニンゲン)を使っているよ
Clojure公式サイト/Leiningen公式サイト
書籍を学習するだけなら、Leiningenをインストールするだけでいいよ。
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
Code language: plaintext (plaintext)
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
Code language: plaintext (plaintext)
lein repl
でclojure
を起動します。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:>
Code language: plaintext (plaintext)
macOS(Mac OS X) 10.15.4
homebrew
でleiningen
をインストールします。
$ 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
Code language: Bash (bash)
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
Code language: Bash (bash)
lein
でfirstday
プロジェクトを新規作成して、lein repl
でclojure
を起動します。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=>
Code language: Bash (bash)
Ubuntu 18.04
apt
でleiningen
をインストールします。
$ sudo apt install leiningen
Code language: Bash (bash)
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
Code language: Bash (bash)
lein
でfirstday
プロジェクトを新規作成して、lein repl
でclojure
を起動します。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
Code language: Bash (bash)
セルフスタディ
1日目
• 文字列 st
が n
文字より長い場合に真を返す関数 (big st n)
を実装してみよ.
(defn big [st n]
(> (count st) n))
Code language: Clojure (clojure)
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
Code language: Clojure REPL (clojure-repl)
• 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
Code language: Clojure REPL (clojure-repl)
返ってきた clojure.lang.PersistentList
は、文字列ではありませんでした。
user=> (= (class (list 1 2)) "clojure.lang.PersistentList")
false
user=> (= (class (list 1 2)) clojure.lang.PersistentList)
true
Code language: Clojure REPL (clojure-repl)
次のようなマップを用意しました。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
Code language: Clojure (clojure)
1つの関数にまとめます。
(defn collection-type [col]
({clojure.lang.PersistentList :list,
clojure.lang.PersistentArrayMap :map,
clojure.lang.PersistentVector :vector} (class col)))
Code language: Clojure (clojure)
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
Code language: Clojure REPL (clojure-repl)
2日目
• マクロを使って else 付きの unless 構文を実装してみよ.
引数が3個のバージョン
(defmacro unless [condition ifbody elsebody]
(list 'if (list 'not condition) ifbody elsebody))
Code language: Clojure (clojure)
引数が2個または3個のバージョン
(defmacro unless
([condition ifbody]
(list 'if (list 'not condition) ifbody))
([condition ifbody elsebody]
(list 'if (list 'not condition) ifbody elsebody)))
Code language: Clojure (clojure)
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
Code language: Clojure REPL (clojure-repl)
• 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) "]"))
)
Code language: Clojure (clojure)
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]"
Code language: Clojure REPL (clojure-repl)
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)])))
Code language: Clojure (clojure)
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]
Code language: Clojure REPL (clojure-repl)
口座0が1000円、口座1が2000円、口座2が3000円を [1000 2000 3000]
と表現する解答例を見つけたよ
ここでは「居眠り床屋」問題について概説する.この問題は 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)
))
Code language: Clojure (clojure)
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
Code language: Clojure REPL (clojure-repl)
訪問者数 visit_counter
をagent
にすると、スレッドが終わるまで、値が更新されないんだね。
客が訪問するたびに 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)
))
Code language: Clojure (clojure)
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
Code language: Clojure REPL (clojure-repl)
visit_agentやcut_agentの値を更新も表示もしていないよ?
そうね、スレッドを作るためだけのagentね。
agent
が、JavaのFutureTask
、agent
に投げる関数が、JavaのCallable
って感じかしら?
5回実行したところ、訪問客数は494〜506、髪を切った客数は487〜492でした。