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\clojureCode 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 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:>Code language: plaintext (plaintext)

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
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 VMCode language: Bash (bash)

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=> 
Code language: Bash (bash)

Ubuntu 18.04

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

$ sudo apt install leiningenCode 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 VMCode language: Bash (bash)

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

Code language: Bash (bash)

セルフスタディ

1日目

• 文字列 stn 文字より長い場合に真を返す関数 (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.PersistentVectorCode 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]と表現する解答例を見つけたよ

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)
    ))
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_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)
    ))
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でした。

参考記事

おすすめ書籍

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