Clojureの概要

  • link:/category/programming/ [プログラミング]

1. 前書き

https://clojure.org/[Clojure]は、ScalaやKotlinと同様に、Java仮想マシン上で完全に実行される機能的なプログラミング言語です。 * ClojureはLisp派生物であると見なされており、他のLisp言語の経験がある人なら誰でも知っているでしょう。
このチュートリアルでは、Clojure言語の概要、Clojure言語の使用方法、およびそれが機能する主要な概念の一部を紹介します。

*2. Clojure *のインストール

  • Clojureは、LinuxおよびmacOS *で使用するためのインストーラーおよび便利なスクリプトとして利用可能です。 残念ながら、この段階では、Windowsにはそのようなインストーラーがありません。

    ただし、LinuxスクリプトはCygwinやWindows Bashなどで動作する場合があります。 *言語のテストに使用できるオンラインサービス*もあり、古いバージョンにはスタンドアロンバージョンがあります。

* 2.1。 スタンドアロンダウンロード*

スタンドアロンJARファイルはhttps://repo1.maven.org/maven2/org/clojure/clojure/1.8.0/clojure-1.8.0.jar[Maven Central]からダウンロードできます。 残念ながら、JARファイルが小さなモジュールに分割されているため、1.8.0より新しいバージョンはこの方法で簡単に動作しなくなりました。
このJARファイルがダウンロードされると、実行可能なJARとして扱うだけで、インタラクティブなREPLとして使用できます。
$ java -jar clojure-1.8.0.jar
Clojure 1.8.0
user=>

* 2.2。 REPL *へのWebインターフェイス

Clojure REPLのWebインターフェースは、https://repl.it/languages/clojureで入手できます。何もダウンロードする必要なく試してみることができます。 現在、これはClojure 1.8.0のみをサポートし、新しいリリースはサポートしていません。

* 2.3。 MacOS *上のインストーラー

macOSを使用してHomebrewをインストールしている場合、Clojureの最新リリースを簡単にインストールできます。
$ brew install clojure
これは、Clojureの最新バージョン–執筆時点で1.10.0をサポートします。 インストールしたら、_clojure_または_clj_コマンドを使用するだけでREPLをロードできます。
$ clj
Clojure 1.10.0
user=>

* 2.4。 Linux *のインストーラー

Linuxにツールをインストールするための自己インストールシェルスクリプトが用意されています。
$ curl -O https://download.clojure.org/install/linux-install-1.10.0.411.sh
$ chmod +x linux-install-1.10.0.411.sh
$ sudo ./linux-install-1.10.0.411.sh
macOSインストーラーと同様に、これらはClojureの最新リリースで利用でき、_clojure_または_clj_コマンドを使用して実行できます。

3. Clojure REPLの概要

上記のすべてのオプションにより、Clojure REPLにアクセスできます。 これは、ClojureのJava 9以降のlink:/java-9-repl[JShellツール]に相当するもので、Clojureコードを入力してすぐに結果を確認できます。 *これは、特定の言語機能がどのように機能するかを実験して発見する素晴らしい方法です。*
REPLがロードされると、標準のClojureコードを入力してすぐに実行できるプロンプトが表示されます。 これには、単純なClojureコンストラクトと、他のJavaライブラリとの相互作用が含まれますが、ロードするクラスパスで利用できる必要があります。
REPLのプロンプトは、作業中の現在のネームスペースを示しています。 大部分の作業では、これは_user_名前空間であるため、プロンプトは次のようになります。
user=>
*この記事の残りの部分はすべて、Clojure REPLにアクセスできることを前提としており、そのようなツールですべて直接動作します。*

4. 言語の基礎

Clojure言語は、他の多くのJVMベースの言語とは非常に異なって見えるため、最初は非常に珍しいようです。 これはhttps://en.wikipedia.org/wiki/Lisp_(programming_language)[Lisp]の方言であると考えられており、他のLisp言語と非常によく似た構文と機能を持っています。
  • Clojureで記述するコードの多く(他のLisp方言と同様)は、リストの形式で表現されます*。 その後、リストを評価して、より多くのリストまたは単純な値の形式で結果を生成できます。

    例えば:
(+ 1 2) ; = 3
これは、3つの要素で構成されるリストです。 「â」記号は、この呼び出しを実行していることを示します。 残りの要素は、この呼び出しで使用されます。 したがって、これは「1 2」と評価されます。
*ここでリスト構文を使用すると、これを簡単に拡張できます*。 たとえば、次のことができます。
(+ 1 2 3 4 5) ; = 15
そして、これは「1 2 3 4 5」と評価されます。
セミコロン文字にも注意してください。 これはClojureでコメントを示すために使用され、Javaで見られるような式の終わりではありません。

* 4.1。シンプル*タイプ

  • ClojureはJVMの上に構築されているため、他のJavaアプリケーションと同じ標準タイプにアクセスできます*。 通常、型は自動的に推測されるため、明示的に指定する必要はありません。

    例えば:
123 ; Long
1.23 ; Double
"Hello" ; String
true ; Boolean
特別な接頭辞または接尾辞を使用して、さらに複雑な型を指定することもできます。
42N ; clojure.lang.BigInt
3.14159M ; java.math.BigDecimal
1/3 ; clojure.lang.Ratio
#"[A-Za-z]+" ; java.util.regex.Pattern
_java.math.BigInteger_の代わりに_clojure.lang.BigInt_タイプが使用されることに注意してください。 これは、Clojureタイプにいくつかのマイナーな最適化と修正があるためです。

* 4.2。 キーワードと記号*

  • Clojureは、キーワードと記号の両方の概念を提供します*。 キーワードは自分自身のみを参照し、多くの場合、マップキーなどに使用されます。 一方、シンボルは、他のものを参照するために使用される名前です。 たとえば、変数の定義と関数名はシンボルです。

    コロンを前に付けた名前を使用して、キーワードを作成できます。
user=> :kw
:kw
user=> :a
:a
*キーワードはそれ自身と直接同等であり、他のものとは同等ではありません:*
user=> (= :a :a)
true
user=> (= :a :b)
false
user=> (= :a "a")
false
Clojureの他のほとんどの単純な値ではないものは、シンボルと見なされます。 *これらは参照するものすべてに評価されますが*、キーワードは常にそれ自体に評価されます:
user=> (def a 1)
#'user/a
user=> :a
:a
user=> a
1

* 4.3。 名前空間*

Clojure言語には、コードを整理するための名前空間の概念があります。 *記述するすべてのコードはネームスペースに存在します。*
デフォルトでは、REPLは_user_名前空間で実行されます(「user =>」を示すプロンプトに表示されます)。
  • _ns_キーワードを使用して名前空間を作成および変更できます*:

user=> (ns new.ns)
nil
new.ns=>
名前空間を変更すると、古い名前空間で定義されたものはすべて使用できなくなり、新しい名前空間で定義されたものはすべて使用できるようになります。
*名前空間を完全に修飾することで、名前空間の定義にアクセスできます*。 たとえば、名前空間_clojure.string_は、関数_upper-case_を定義します。
_clojure.string_名前空間にいる場合は、直接アクセスできます。 そうでない場合は、_clojure.string / upper-case_として修飾する必要があります。
user=> (clojure.string/upper-case "hello")
"HELLO"
user=> (upper-case "hello") ; This is not visible in the "user" namespace
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: upper-case in this context
user=> (ns clojure.string)
nil
clojure.string=> (upper-case "hello") ; This is visible because we're now in the "clojure.string" namespace
"HELLO"
  • require キーワードを使用して、別のネームスペースの定義に簡単にアクセスすることもできます。 これを使用して、より使いやすいように短い名前のネームスペースを定義する方法と、接頭辞なしで別のネームスペースの定義に直接アクセスする方法の2つがあります。

clojure.string=> (require '[clojure.string :as str])
nil
clojure.string=> (str/upper-case "Hello")
"HELLO"

user=> (require '[clojure.string :as str :refer [upper-case]])
nil
user=> (upper-case "Hello")
"HELLO"
これらは両方とも現在のネームスペースにのみ影響するため、別のネームスペースに変更するには新しい__requiresが必要です。 __これにより、名前空間をよりクリーンに保ち、必要なものだけにアクセスできるようになります。

* 4.4。 変数*

*単純な値の定義方法がわかったら、それらを変数に割り当てることができます。*キーワード_def_を使用してこれを行うことができます。
user=> (def a 123)
#'user/a
*一度これを行うと、シンボル_a_ *を使用できます*この値を表現したい場所ならどこでも:*
user=> a
123
変数の定義は、必要に応じて単純にすることも複雑にすることもできます。
たとえば、変数を数値の合計として定義するには、次のようにします。
user=> (def b (+ 1 2 3 4 5))
#'user/b
user=> b
15
変数を宣言したり、変数のタイプを示す必要はありません。 Clojureは、これらすべてを自動的に決定します。
定義されていない変数を使用しようとすると、代わりにエラーが発生します。
user=> unknown
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: unknown in this context
user=> (def c (+ 1 unknown))
Syntax error compiling at (REPL:1:8).
Unable to resolve symbol: unknown in this context
_def_関数の出力は、入力とわずかに異なることに注意してください。 変数_a_を定義すると、_â€〜user / a_の文字列が返されます。 これは、結果がシンボルであり、このシンボルが現在のネームスペースで定義されているためです。

* 4.5。 機能*

  • Clojureで関数を呼び出す方法の例は既にいくつか見ました*。 呼び出される関数で始まるリストを作成し、次にすべてのパラメーターを作成します。

    このリストが評価されると、関数から戻り値を取得します。 例えば:
user=> (java.time.Instant/now)
#object[java.time.Instant 0x4b6690c0 "2019-01-15T07:54:01.516Z"]
user=> (java.time.Instant/parse "2019-01-15T07:55:00Z")
#object[java.time.Instant 0x6b8d96d9 "2019-01-15T07:55:00Z"]
user=> (java.time.OffsetDateTime/of 2019 01 15 7 56 0 0 java.time.ZoneOffset/UTC)
#object[java.time.OffsetDateTime 0xf80945f "2019-01-15T07:56Z"]
1つの関数呼び出しの出力をパラメーターとして別の関数呼び出しに渡したい場合のために、関数呼び出しをネストすることもできます。
user=> (java.time.OffsetDateTime/of 2018 01 15 7 57 0 0 (java.time.ZoneOffset/ofHours -5))
#object[java.time.OffsetDateTime 0x1cdc4c27 "2018-01-15T07:57-05:00"]
また、必要に応じて*関数を定義することもできます*。 *関数は_fn_コマンドを使用して作成されます*:
user=> (fn [a b]
  (println "Adding numbers" a "and" b)
  (+ a b)
)
#object[user$eval165$fn__166 0x5644dc81 "[email protected]"]
残念ながら、*これは関数に使用可能な名前を与えません*。 代わりに、変数で見たように、__def、__exactlyを使用してこの関数を表すシンボルを定義できます。
user=> (def add
  (fn [a b]
    (println "Adding numbers" a "and" b)
    (+ a b)
  )
)
#'user/add
この関数を定義したので、他の関数と同じように呼び出すことができます。
user=> (add 1 2)
Adding numbers 1 and 2
3
便利なように、* Clojureでは、_defn_を使用して、1つのgo *で名前を持つ関数を定義することもできます。
例えば:
user=> (defn sub [a b]
  (println "Subtracting" b "from" a)
  (- a b)
)
#'user/sub
user=> (sub 5 2)
Subtracting 2 from 5
3

* 4.6。 Letおよびローカル変数*

  • _def_呼び出しは、現在のネームスペースに対してグローバルなシンボルを定義します*。 これは通常、コードを実行するときに望ましいことではありません。 代わりに、* Clojureは_let_呼び出しを提供して、ブロックにローカルな変数を定義します*。 これは、変数を関数の外部にリークさせたくない関数内で使用する場合に特に便利です。

    たとえば、sub関数を定義できます。
user=> (defn sub [a b]
  (def result (- a b))
  (println "Result: " result)
  result
)
#'user/sub
ただし、これを使用すると、次の予期しない副作用があります。
user=> (sub 1 2)
Result:  -1
-1
user=> result ; Still visible outside of the function
-1
代わりに、_let_を使用して書き直しましょう。
user=> (defn sub [a b]
  (let [result (- a b)]
    (println "Result: " result)
    result
  )
)
#'user/sub
user=> (sub 1 2)
Result:  -1
-1
user=> result
Syntax error compiling at (REPL:0:0).
Unable to resolve symbol: result in this context
今回は、_result_シンボルは関数の外部からは見えません。 または、実際に、それが使用された_let_ブロックの外側。

5. コレクション

これまで、私たちは主に単純な値とやり取りしてきました。 リストも見ましたが、それ以上のものはありません。 * Clojureには使用可能なコレクションの完全なセットがありますが、リスト、ベクター、マップ、セットで構成されています*:
  • ベクトルは、値の順序付きリストです–任意の値を入れることができます
    他のコレクションを含むベクトルに変換します。

  • セットは、順序付けられていない値のコレクションであり、
    同じ値が複数回。

  • マップは、キーと値のペアの単純なセットです。 使用することは非常に一般的です
    キーワードをマップのキーとして使用しますが、他のコレクションを含め、好きな値を使用できます。

  • リストはベクターに非常に似ています。 違いはそれに似ています
    JavaのanArrayListとaLinkedListの間。 通常、ベクトルが優先されますが、要素を先頭に追加する場合、または要素に順番にのみアクセスする場合は、リストの方が適しています。

* 5.1。 コレクションの構築*

これらのそれぞれを作成するには、簡略表記または関数呼び出しを使用します。
; Vector
user=> [1 2 3]
[1 2 3]
user=> (vector 1 2 3)
[1 2 3]

; List
user=> '(1 2 3)
(1 2 3)
user=> (list 1 2 3)
(1 2 3)

; Set
user=> #{1 2 3}
#{1 3 2}
user=> (hash-set 1 2 3)
#{1 3 2}

; Map
user=> {:a 1 :b 2}
{:a 1, :b 2}
user=> (hash-map :a 1 :b 2)
{:b 2, :a 1}
_Set_と_Map_の例は、同じ順序で値を返さないことに注意してください。 これは、これらのコレクションは本質的に順序付けられておらず、表示される内容はメモリ内での表示方法に依存するためです。
リストを作成するための構文は、式の標準的なClojure構文に非常に似ていることもわかります。 *実際、Clojure式は評価されるリストです*。ここのアポストロフィ文字は、評価する代わりに実際の値のリストが必要であることを示しています。
*もちろん、他の値と同じ方法でコレクションを変数に割り当てることができます*。 1つのコレクションを別のコレクション内のキーまたは値として使用することもできます。
リストはa _seq_と見なされます。 これは、クラスが_ISeq_インターフェイスを実装することを意味します。 他のすべてのコレクションは、_seq_関数を使用してa _seq_に変換できます。
user=> (seq [1 2 3])
(1 2 3)
user=> (seq #{1 2 3})
(1 3 2)
user=> (seq {:a 1 2 3})
([:a 1] [2 3])

* 5.2。 コレクションへのアクセス*

コレクションを取得したら、それとやり取りして値を再び取得できます。 それぞれのセマンティクスが異なるため、これを行う方法は、問題のコレクションにわずかに依存します。
*ベクターは、インデックスによって任意の値を取得できる唯一のコレクションです*。 これは、ベクトルとインデックスを式として評価することにより行われます。
user=> (my-vector 2) ; [1 2 3]
3
*同じ構文を使用して、マップに対しても同じことができます*:
user=> (my-map :b)
2
*ベクトルとリストにアクセスして、最初の値、最後の値、リストの残りを取得する関数もあります*
user=> (first my-vector)
1
user=> (last my-list)
3
user=> (next my-vector)
(2 3)
*マップには、キーと値のリスト全体を取得するための追加機能があります:*
user=> (keys my-map)
(:a :b)
user=> (vals my-map)
(1 2)
*設定する必要がある唯一の実際のアクセスは、特定の要素がメンバーであるかどうかを確認することです*。
これは、他のコレクションへのアクセスと非常によく似ています。
user=> (my-set 1)
1
user=> (my-set 5)
nil

* 5.3。 コレクションの特定*

コレクションにアクセスする方法は、コレクションの種類によって異なることがわかりました。 *特定のより一般的な方法で、これを決定するために使用できる関数のセットがあります。*
各コレクションには、指定された値がリストの場合は_list?_、セットの場合は_set?_などのタイプであるかどうかを判断する特定の機能があります。 さらに、特定の値が任意の種類のa__seq_であるかどうかを判断するための_seq?_と、特定の値が任意の種類の連想アクセスを許可するかどうかを判断するための_associative?_があります。
user=> (vector? [1 2 3]) ; A vector is a vector
true
user=> (vector? #{1 2 3}) ; A set is not a vector
false
user=> (list? '(1 2 3)) ; A list is a list
true
user=> (list? [1 2 3]) ; A vector is not a list
false
user=> (map? {:a 1 :b 2}) ; A map is a map
true
user=> (map? #{1 2 3}) ; A set is not a map
false
user=> (seq? '(1 2 3)) ; A list is a seq
true
user=> (seq? [1 2 3]) ; A vector is not a seq
false
user=> (seq? (seq [1 2 3])) ; A vector can be converted into a seq
true
user=> (associative? {:a 1 :b 2}) ; A map is associative
true
user=> (associative? [1 2 3]) ; A vector is associative
true
user=> (associative? '(1 2 3)) ; A list is not associative
false

* 5.4。 コレクションの変更*

  • Clojureでは、ほとんどの機能言語と同様に、すべてのコレクションは不変です*。 コレクションを変更するために行うことはすべて、変更を表すために作成される新しいコレクションになります。 これにより、効率が大幅に向上し、偶発的な副作用のリスクがなくなります。

    ただし、これを理解することにも注意する必要があります。そうしないと、コレクションに予期される変更が行われません。
    *ベクター、リスト、またはセットに新しい要素を追加するには、_conj_ *を使用します。 これはこれらの各ケースで異なる動作をしますが、基本的な意図は同じです:
user=> (conj [1 2 3] 4) ; Adds to the end
[1 2 3 4]
user=> (conj '(1 2 3) 4) ; Adds to the beginning
(4 1 2 3)
user=> (conj #{1 2 3} 4) ; Unordered
#{1 4 3 2}
user=> (conj #{1 2 3} 3) ; Adding an already present entry does nothing
#{1 3 2}
  • disj *を使用して、セットからエントリを削除することもできます。 これらは厳密に順序付けられているため、リストまたはベクターでは機能しないことに注意してください。

user=> (disj #{1 2 3} 2) ; Removes the entry
#{1 3}
user=> (disj #{1 2 3} 4) ; Does nothing because the entry wasn't present
#{1 3 2}
*マップへの新しい要素の追加は、_assoc_を使用して行われます。 _dissoc:_ *を使用して、マップからエントリを削除することもできます
user=> (assoc {:a 1 :b 2} :c 3) ; Adds a new key
{:a 1, :b 2, :c 3}
user=> (assoc {:a 1 :b 2} :b 3) ; Updates an existing key
{:a 1, :b 3}
user=> (dissoc {:a 1 :b 2} :b) ; Removes an existing key
{:a 1}
user=> (dissoc {:a 1 :b 2} :c) ; Does nothing because the key wasn't present
{:a 1, :b 2}

* 5.5。 関数型プログラミングの構築物*

Clojureは、本質的に関数型プログラミング言語です。 これは、**** __ map、filter、__ * and * __ * reduceなど、従来の多くの関数型プログラミングの概念にアクセスできることを意味します。 * __ **これらは一般に他の言語と同じように機能します**。 ただし、正確な構文は若干異なる場合があります。
具体的には、これらの関数は通常、最初の引数として適用する関数と、2番目の引数として適用するコレクションを受け取ります。
user=> (map inc [1 2 3]) ; Increment every value in the vector
(2 3 4)
user=> (map inc #{1 2 3}) ; Increment every value in the set
(2 4 3)

user=> (filter odd? [1 2 3 4 5]) ; Only return odd values
(1 3 5)
user=> (remove odd? [1 2 3 4 5]) ; Only return non-odd values
(2 4)

user=> (reduce + [1 2 3 4 5]) ; Add all of the values together, returning the sum
15

6. 制御構造

*すべての汎用言語と同様に、Clojureの機能では、条件やループなどの標準的な制御構造が必要です。*

* 6.1。 条件付き*

*条件は_if_ステートメントによって処理されます*。 これには、テスト、テストが_true_の場合に実行するブロック、テストが_false_の場合に実行するブロックの3つのパラメーターが必要です。 これらはそれぞれ、必要に応じて評価される単純な値または標準リストにすることができます。
user=> (if true 1 2)
1
user=> (if false 1 2)
2
テストは、必要なものであれば何でも構いません。つまり、_true / false_値である必要はありません。 また、必要な値を提供するために評価されるブロックにすることもできます。
user=> (if (> 1 2) "True" "False")
"False"
ここでは、_ =、>、_、_ <_を含むすべての標準チェックを使用できます。 また、他のさまざまな理由で使用できる一連の述語もあります–たとえば、コレクションを見ると、すでにいくつかを見ています。
user=> (if (odd? 1) "1 is odd" "1 is even")
"1 is odd"
このテストでは、任意の値を返すことができます。_true_または_false_である必要はありません。 ただし、値が_false_または_nil_以外の場合、__ true_と見なされます。 これは、「true」ではなく「true」であると見なされる値の大きなセットがあるJavaScriptの動作とは異なります。
user=> (if 0 "True" "False")
"True"
user=> (if [] "True" "False")
"True"
user=> (if nil "True" "False")
"False"

* 6.2。 ループ*

*コレクションでの機能サポートは、ループ作業の大部分を処理します* –コレクションに対するループを作成する代わりに、標準関数を使用して言語に反復処理をさせます。
*これ以外では、ループは完全に再帰を使用して行われます*。 再帰関数を作成するか、__loop __and __recur ___keywordsを使用して再帰スタイルループを作成できます。
user=> (loop [accum [] i 0]
  (if (= i 10)
    accum
    (recur (conj accum i) (inc i))
  ))
[0 1 2 3 4 5 6 7 8 9]
_loop_呼び出しは、反復ごとに実行される内部ブロックを開始し、いくつかの初期パラメーターを設定することから開始します。 次に、_recur_呼び出しはループにコールバックし、反復に使用する次のパラメーターを提供します。 _recur_が呼び出されない場合、ループは終了します。
この場合、__i __valueが10に等しくなくなるたびにループし、10に等しくなるとすぐに、代わりに累積された数値のベクトルを返します。

7. 概要

*この記事では、Clojureプログラミング言語の概要を説明し、構文がどのように機能するか、およびそれを使用して実行できることのいくつかを示します。言語で行われます。
しかし、それを手に取り、試してみて、あなたがそれで何ができるかを見てみましょう。