ClojureScript

Clojureとの違い

理論的根拠

ClojureScriptの理論的根拠は、プラットフォームとしての役割をJavaScriptが担っていることを除けば、Clojureとほぼ同じです。また、JSのリーチが明らかにプラットフォームほど豊富ではないため、JSのリーチにも重点が置かれています。

ClojureScriptの理論的根拠に関するより深い議論は、このサイトの別の場所にあります。

状態と同一性

Clojureと同じです。Clojureの同一性モデルは、シングルスレッド環境であっても、可変状態よりもシンプルで堅牢です。

動的開発

Clojureと同様に、ClojureScriptはREPL駆動開発をサポートしており、さまざまなJavaScript環境で簡単に起動できるREPLを提供しています。詳細はクイックスタートを参照してください。

さらに、ClojureScriptのセルフホスティング機能は、サードパーティのREPLやその他の動的機能を作成できる純粋なJavaScript環境に動的な性質を拡張することをサポートしています。

関数型プログラミング

ClojureScriptは、JVM上のClojureと同じ不変の永続コレクションを持っています。

Lisp

Clojureとは異なり、ClojureScriptのマクロ定義とその使用は、同じコンパイルステージで混在させることはできません。以下のマクロのセクションを参照してください。

実行時ポリモーフィズム

  • ClojureScriptのプロトコルは、Clojureプロトコルと同じセマンティクスを持っています。

並行プログラミング

Clojureの値、状態、同一性、時間のモデルは、シングルスレッド環境でも貴重です。

  • アトムはClojureと同じように動作します

  • RefsとSTMはありません

  • bindingのユーザーエクスペリエンスはClojureと似ています

    • Vars

      • 実行時には具象化されません

      • 具象化の多くの開発時の用途は、アナライザーを介したClojureデータ構造へのアクセスによって不要になります

    • defは通常のJS変数を生成します

  • エージェントは現在実装されていません

JVM上でのホスティング

  • ClojureScriptはJavaScript VM上でホストされています

  • オプションで、最適化のためにGoogleのClosureコンパイラを使用できます

  • GoogleのClosureライブラリを活用するように設計されており、その依存関係/require/provideメカニズムに参加します

はじめに

クイックスタートを参照してください

リーダー

  • 数値

    • ClojureScriptは現在、JavaScriptプリミティブにマップする整数と浮動小数点リテラルのみをサポートしています

      • Ratio、BigDecimal、BigIntegerリテラルは現在サポートされていません

      • 数値の等価性は、ClojureではなくJavaScriptのように機能します。 (= 0.0 0) ⇒ true

  • 文字

    • ClojureScriptには文字リテラルがありません。代わりに、文字はJavaScriptと同じです(つまり、単一文字の文字列です)

  • リスト、ベクター、マップ、およびセットリテラルはClojureと同じです

  • マクロ文字

    • ClojureScriptには文字型がないため、\は単一文字の文字列を生成します。

  • read

    • read関数とread-string関数は、cljs.reader名前空間にあります

  • タグ付きリテラル

    • Clojureタグ付きリテラルと同じですが、コンパイルステージで使用されるリーダー関数はClojurescriptマクロに似ており、Clojurescriptコードフォーム(または文字列や数値などのリテラル)を返す必要があります。

    • Clojureコンパイラは、data_readers.clj/cで参照されるリーダー関数を自動的に必要としませんが、Clojurescriptコンパイラは必要とします。

    • 詳細はリーダーを参照してください

REPLとmain

  • ClojureScript REPLの使用方法については、クイックスタートを参照してください。

  • 標準のClojureScript REPLは、Clojureのmainパターンをサポートしています。

評価

  • ClojureScriptはClojureと同じ評価規則を持っています

  • loadは存在しますが、REPL特殊関数としてのみ存在します

  • load-fileは存在しますが、REPL特殊関数としてのみ存在します

  • Clojureはローカルクリアを実行しますが、ClojureScriptは実行しません

特殊形式

次のClojureScript特殊形式は、Clojureの対応するものと同じです。ifdoletletfnquotelooprecurthrow、およびtry

  • varに関する注意事項

    • Varsは実行時には具象化されません。コンパイラがvar特殊形式に遭遇すると、**コンパイル時**メタデータを反映したVarインスタンスを発行します。(これは多くの一般的な**静的**ユースケースを満たします。)

  • defに関する注意事項

    • defは通常のJS変数を生成します

    • :privateメタデータはコンパイラによって強制されません

      • プライベート変数アクセスは分析警告をトリガーします

    • :constメタデータ

      • コンパイル時静的EDN値のインライン化を行います

      • caseテスト定数(^:const Varsに解決されるシンボル)がそれらの値でインライン化されるようになります

    • defフォームは、:def-emits-varコンパイラオプションが設定されていない限り(REPLではデフォルトでtrue)、varではなくinitフォームの値に評価されます

  • ifに関する注意事項

    • Javaのブールボックスに関するセクションは、ClojureScriptには関係ありません

  • fnに関する注意事項

    • 現在、fnを呼び出す際のarityの実行時強制はありません

  • monitor-entermonitor-exit、およびlockingは実装されていません

マクロ

ClojureScriptのマクロは、それらが使用されるものとは異なる*コンパイルステージ*で定義する必要があります。これを達成する1つの方法は、それらを1つの名前空間で定義し、別の名前空間から使用することです。

マクロは、名前空間宣言の:require-macrosキーワードを介して参照されます

(ns my.namespace
  (:require-macros [my.macros :as my]))

:require-macrosプリミティブを使用する代わりに、Sugaredやその他のnsバリアントを使用できます。詳細は以下の名前空間を参照してください。

マクロは*.cljまたは*.cljcファイルに記述され、通常のClojureScriptを使用する場合はClojureとして、ブートストラップ/セルフホストClojureScriptを使用する場合はClojureScriptとしてコンパイルされます。1つの注意点として、ClojureベースのClojureScriptマクロによって生成されるコードは、ClojureScriptの機能をターゲットにする必要があるということです。

ClojureScript名前空間は、異なるコンパイルステージに保持されている限り、同じ名前空間からマクロを必要とする*こと*ができます。そのため、たとえば、foo.cljsまたはfoo.cljcファイルは、マクロ用にfoo.cljcまたはfoo.cljファイルを使用できます。

Clojureとは異なり、ClojureScriptでは、マクロと関数は同じ名前を持つことができます(たとえば、cljs.core/+マクロとcljs.core/+関数は共存できます)。

「もしそうなら、どちらを取得するのですか?」と疑問に思うかもしれません。ClojureScript(Clojureとは異なり)には、相互作用しない2つの個別の名前空間を使用する2つの異なるステージがあります。マクロ展開が最初に発生するため、(+ 1 1)のようなフォームは最初にcljs.core/+マクロを呼び出します。一方、(reduce + [1 1])のようなフォームでは、+記号は演算子位置になく、マクロ展開を介して分析/コンパイルに渡され、そこでcljs.core/+関数として解決されます。

その他の関数

  • 印刷

    • *out**err*は現在実装されていません

  • 正規表現のサポート

  • アサーション

    • JVM ClojureScriptでは、実行時に*assert*を動的にfalseに設定することはできません。代わりに、:elide-assertsコンパイラオプションを使用して省略を実行する必要があります。(一方、セルフホストClojureScriptでは、*assert*はClojureと同様に動作します。)

データ構造

  • nil

    • Clojureでは、nilはJavaのnullと同じですが、ClojureScriptでは、nilはJavaScriptのnullundefinedに相当します。

  • 数値

    • 現在、ClojureScriptの数値はJavaScriptの数値です

  • 現在、強制する型がないため、強制は実装されていません

  • 文字

    • JavaScriptには文字型がありません。Clojure文字は内部で単一文字の文字列として表されます

  • キーワード

    • ClojureScriptキーワードはidentical?であることが保証されていないため、高速な等価性テストにはkeyword-identical?を使用してください

  • コレクション

    • 永続コレクションが利用可能です

      • Clojureの実装の移植

    • 永続ベクター、ハッシュマップ、ハッシュセットの過渡的サポートが導入されています

    • ほとんどすべてのコレクション関数が実装されていますが、すべてではありません

  • StructMaps

    • ClojureScriptは、defstructcreate-structstruct-mapstruct、またはaccessorを実装していません。

Seqs

  • SeqsはClojureと同じセマンティクスを持ち、ほとんどすべてのSeqライブラリ関数はClojureScriptで使用できます。

プロトコル

  • defprotocoldeftypeextend-typeextend-protocolはClojureと同じように動作します

  • プロトコルはClojureのように具象化されておらず、ランタイムプロトコルオブジェクトはありません

  • 一部の反射機能(satisfies?)はClojureと同じように動作します

    • satisfies?はマクロであり、プロトコル名を渡す必要があります

  • extendは現在実装されていません

  • specify、不変の値をプロトコルに拡張します - ラッパーのないインスタンスレベルのextend-type

メタデータ

Clojureと同じように動作します。

名前空間

ClojureScriptの名前空間は、入れ子になったJavaScriptオブジェクトとして表されるGoogle Closure名前空間にコンパイルされます。重要なことは、これは名前空間と変数が衝突する可能性があることを意味しますが、コンパイラはこれらの問題のあるケースを検出し、発生した場合に警告を発します。

  • 現在、以下の注意事項に従ってのみnsフォームを使用する必要があります

    • :use:onlyフォームを使用する必要があります

    • :requireは、:as:refer、および:renameをサポートしています

      • :refer :allはサポートされていません

      • すべてのオプションをスキップできます

      • この場合、シンボルをlibspecとして直接使用できます

        • つまり、(:require lib.foo)(:require [lib.foo])はどちらもサポートされており、同じ意味です

      • :renameは、参照される変数名から異なるシンボルへのマップを指定します(衝突を防ぐために使用できます)

      • プレフィックスリストはサポートされていません

    • :refer-clojure で使用できるオプションは :exclude:rename のみです。

    • :import は Google Closure クラスのインポートにのみ使用できます。

      • ClojureScript の型とレコードは、:import ではなく、:use または :require :refer を使用して取り込む必要があります。

  • マクロは、それらが使用されるコンパイルステージとは異なる*コンパイルステージ*で定義する必要があります。 これを実現する1つの方法は、1つの名前空間でマクロを定義し、別の名前空間からそれらを使用することです。 マクロは、ns:require-macros / :use-macros オプションを介して参照されます。

    • :require-macros:use-macros は、:require:use と同じ形式をサポートします。

***暗黙的なマクロのロード***: 名前空間が require または use され、その名前空間自体が自身の名前空間からマクロを require または use する場合、マクロは同じ仕様を使用して暗黙的に require または use されます。 さらに、この場合、マクロ var は :refer または :only 仕様に含めることができます。 これにより、ライブラリの使用が簡素化され、使用する名前空間が特定の var が関数かマクロかを明示的に区別する必要がなくなることがよくあります。 例:

(ns testme.core (:require [cljs.test :as test :refer [test-var deftest]]))

は、test/is が正しく解決され、test-var 関数と deftest マクロが修飾なしで使用可能になります。

***インラインマクロ指定***: 便宜上、:require には :include-macros true または :refer-macros [syms…​] を指定できます。 どちらも、マクロを含む対応する Clojure ファイルを明示的にロードする形式に糖衣構文変換されます。 (これは、require されている名前空間が内部的に自身のマクロを require または use するかどうかに関係なく機能します。) 例:

(ns testme.core
  (:require [foo.core :as foo :refer [foo-fn] :include-macros true]
            [woz.core :as woz :refer [woz-fn] :refer-macros [apple jax]]))

は、以下の糖衣構文です。

(ns testme.core
  (:require [foo.core :as foo :refer [foo-fn]]
            [woz.core :as woz :refer [woz-fn]])
  (:require-macros [foo.core :as foo]
                   [woz.core :as woz :refer [apple jax]]))

***Clojure 名前空間の自動エイリアシング***: 存在しない clojure.* 名前空間が require または use され、対応する cljs.* 名前空間が存在する場合、cljs.* 名前空間がロードされ、clojure.* 名前空間から cljs.* 名前空間へのエイリアスが自動的に確立されます。 例:

(ns testme.core (:require [clojure.test]))

は、自動的に以下に変換されます。

(ns testme.core (:require [cljs.test :as clojure.test]))

ライブラリ

既存の Clojure ライブラリは、ClojureScript で動作するためには、ClojureScript のサブセットに準拠する必要があります。

さらに、Clojure ライブラリのマクロは、cljs.js/*load-fn* 機能を介してセルフホスト/ブートストラップされた ClojureScript で使用できるように、ClojureScript としてコンパイル可能である必要があります。

Vars とグローバル環境

  • defbinding は Clojure と同様に動作します。

    • ただし、通常の js 変数に対してです。

    • Clojure は未束縛の var を表現できます。 ClojureScript では、(def x)(nil? x)true になることを意味します。 (この場合、(identical? nil x)false ですが、(identical? js/undefined x)true です。)

    • Clojure では、def は *var 自体* を返します。 ClojureScript では、REPL オプション :def-emits-var が設定されていない限り、def は *値* を返します(REPL のデフォルトは true です)。

  • アトムはClojureと同じように動作します

  • Refs と Agents は現在実装されていません。

  • バリデータは Clojure と同様に動作します。

  • intern は実装されていません - 再化された Vars はありません。

Refs とトランザクション

Refs とトランザクションは現在サポートされていません。

Agents

Agents は現在サポートされていません。

Atoms

Atoms は Clojure と同様に動作します。

ホストとの相互運用

ホスト言語の相互運用機能(new/. など)は、可能な限り Clojure と同様に動作します。例:

goog/LOCALE
=> "en"

(let [sb (goog.string.StringBuffer. "hello, ")]
 (.append sb "world")
 (.toString sb))
    => "hello, world"

ClojureScript では、Foo/bar は常に Foo が名前空間であることを意味します。 JavaScript にはこれを判断するためのリフレクション情報がないため、Clojure で一般的な Java 静的フィールドアクセス パターンには使用できません。

特別な名前空間 js は、グローバルプロパティへのアクセスを提供します。

js/Infinity
=> Infinity

オブジェクトプロパティ(実行するのではなく値として必要な関数を含む)にアクセスするには、先頭にハイフンを使用します。

(.-NEGATIVE_INFINITY js/Number)
=> -Infinity

ヒント

^long^double は、関数パラメータで使用される場合、Clojure では型 *宣言* ですが、ClojureScript では型 *ヒント* です。

型ヒントは、主に Clojure でのリフレクションを回避するために使用されます。 ClojureScript では、重要な型ヒントは ^boolean 型ヒントのみです。 チェックされた if 評価を回避するために使用されます(たとえば、0"" は JavaScript では false ですが、ClojureScript では true であるという事実に対処します)。

コンパイルとクラス生成

コンパイルは Clojure とは異なります。

  • すべての ClojureScript プログラムは、(オプションで最適化された)JavaScript にコンパイルされます。

  • 個々のファイルは、出力の分析のために個々の JS ファイルにコンパイルできます。

  • 本番コンパイルは、Google Closure コンパイラを介したプログラム全体のコンパイルです。

  • gen-classgen-interface などは、ClojureScript では不要であり、実装されていません。

その他のライブラリ

ClojureScript には現在、Clojure から移植された次の非コア名前空間が含まれています。

  • clojure.set

  • clojure.string

  • clojure.walk

  • clojure.zip

  • clojure.data

  • clojure.core.reducers

    • fold は現在 reduce のエイリアスです。

  • cljs.pprint (clojure.pprint の移植版)

  • cljs.spec (clojure.spec の移植版)

  • cljs.test (clojure.test の移植版)

貢献

Clojure と ClojureScript は、同じ貢献者契約と開発プロセスを共有しています。