永続的なデータとは? clojureは、変更できない
永続的な値と、変異間の時間的な寿命を持つ一時的な
値を区別します。永続的なデータ構造を変更しようとすると、変更が適用された状態で新しい構造を返すことで、基礎となるデータの変異を避けます。この区別が理論的なプログラミング言語でどのように見えるかを見るのに役立つかもしれません。
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
値を押したときに一時的なリストが変異したことがわかります。 AとBの両方が同じ可変値を指します。対照的に、永続的なリストでプッシュを呼び出すと新しい値が返され、CとDが離散リストとは異なることがわかります。
これらの永続的なデータ構造を変異させることはできません。つまり、値を参照したら、変更されないという保証もあります。これらの保証は、通常、より安全でシンプルなコードを書くのに役立ちます。たとえば、引数として永続的なデータ構造を採用する関数は、それらを変化させることができないため、関数が意味のある変更を伝達したい場合、返品値から来る必要があります。これは、参照的に透明で純粋な機能を書くことにつながります。これは、テストと最適化が簡単です。 より簡単に言えば、不変のデータにより、より機能的なコードを書くことが強制されます。森とは何ですか?
Moriは、ClojureScriptコンパイラを使用して、Clojureの標準ライブラリのデータ構造の実装をJavaScriptにコンパイルします。コンパイラは最適化されたコードを発します。つまり、追加の考慮がなくても、JavaScriptのコンパイルされたClojureと通信するのは簡単ではありません。森は、追加の考慮事項の層です。
moriは、可能な限り多くの元の構造を共有することにより、構造共有を使用してデータに効率的な変更を加えます。これにより、永続的なデータ構造は、通常の一時的なデータ構造とほぼ同じように効率的になります。これらの概念の実装については、このビデオではさらに詳細に説明しています。
なぜそれが役立つのですか?最初に、継承したJavaScriptコードベースのバグを追跡しようとしていると想像してみましょう。私たちは、フェローシップの間違った価値になった理由を理解しようとしてコードを読んでいます。
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
コンソールにログに記録されたフェローシップの価値は何ですか?
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
deleteperson()の実装に関係なく、単に変異できないという保証があるという理由だけで、元のベクトルが記録されることを知っています。関数を便利にしたい場合は、指定されたアイテムが削除された状態で新しいベクトルを返す必要があります。
不変のデータで機能する機能を介したフローを理解するのは簡単です。なぜなら、それらの唯一の効果は、明確な不変の価値を導き出して返すことであることを知っているからです。
可変データで動作する関数は常に値を返すわけではなく、入力を変異させることができ、時にはプログラマに任せて反対側で再び値を拾うことができます。
実際には
Codepenをフォローしているか、MORIと次のHTMLを使用してES2015環境で作業していると仮定します。
セットアップ&ユーティリティ
これは主に文体的な好みです。 Moriオブジェクト(例:mori.list())に直接アクセスすることで、MORIの関数を使用することもできます。
最初に行うことは、永続的なデータ構造を表示するためのヘルパー関数をセットアップすることです。森の内部表現はコンソールではあまり意味がないため、tojs()関数を使用して理解可能な形式に変換します。<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
次に、いくつかの構成値とユーティリティ関数を設定します。
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
データの構造
to2d()関数を使用して、キャンバス上のすべてのピクセルを表す一連の座標を作成します。
<span>import <span>{ vector, hashMap }</span> from 'mori'; </span> <span>const fellowship = vector( </span> <span>hashMap( </span> <span>"name", "Mori", </span> <span>"race", "Hobbit" </span> <span>), </span> <span>hashMap( </span> <span>"name", "Poppin", </span> <span>"race", "Hobbit" </span> <span>) </span><span>) </span> <span>const newFellowship = deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
座標の構造を視覚化するのに役立つかもしれません。
<span><span><span><div</span>></span> </span> <span><span><span><h3</span>></span>Mori Painter<span><span></h3</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span> id<span>="container"</span>></span> </span> <span><span><span><canvas</span> id<span>='canvas'</span>></span><span><span></canvas</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span>></span> </span> <span><span><span><button</span> id<span>='undo'</span>></span>↶<span><span></button</span>></span> </span><span><span><span></div</span>></span> </span>
各座標と並んで、色の値も保存する必要があります。
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
repeat()関数を使用して、「#FFF」文字列の無限シーケンスを作成しています。 MORIシーケンスはレイジー評価をサポートしているため、このメモリを埋めてブラウザをクラッシュさせることを心配する必要はありません。後で尋ねると、シーケンス内のアイテムの値を計算します。
最後に、ハッシュマップの形で座標と色を組み合わせたい。
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
ZIPMAP()関数を使用してハッシュマップを作成し、キーとしての座標と色を値として使用しています。繰り返しますが、データの構造を視覚化するのに役立つかもしれません。
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
ピクセル
を描画しますxおよびy座標を使用して、キーとして使用できる座標ベクトルを作成し、そのキーを新しい色に関連付けるためにassoc()を使用します。データ構造は永続的であるため、Assoc()関数は古いマップを変異させるのではなく、
new<span>import <span>{ vector, hashMap }</span> from 'mori'; </span> <span>const fellowship = vector( </span> <span>hashMap( </span> <span>"name", "Mori", </span> <span>"race", "Hobbit" </span> <span>), </span> <span>hashMap( </span> <span>"name", "Poppin", </span> <span>"race", "Hobbit" </span> <span>) </span><span>) </span> <span>const newFellowship = deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
絵を描く これで、キャンバスにシンプルな画像を描くために必要なものはすべて揃っています。ピクセルに対して座標のハッシュマップを取得し、それらをrenderingContext2Dに描画する関数を作成しましょう。
各()を使用して、ピクセルのハッシュマップを反復します。各キーと値(シーケンスとして一緒に)をpaspとしてコールバック関数に渡します。次に、IntherArray()関数を使用して、それを破壊できる配列に変換するため、必要な値を選択できます。
<span><span><span><div</span>></span> </span> <span><span><span><h3</span>></span>Mori Painter<span><span></h3</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span> id<span>="container"</span>></span> </span> <span><span><span><canvas</span> id<span>='canvas'</span>></span><span><span></canvas</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span>></span> </span> <span><span><span><button</span> id<span>='undo'</span>></span>↶<span><span></button</span>></span> </span><span><span><span></div</span>></span> </span>
一緒に配線する
ここで、これらすべての部品をまとめて動作させるためだけに、少し配管する必要があります。
<span>const { </span> list<span>, vector, peek, pop, conj, map, assoc, zipmap, </span> range<span>, repeat, each, count, intoArray, toJs </span><span>} = mori; </span>
キャンバスを手に入れ、それを使用して画像をレンダリングするためのコンテキストを作成します。また、次元を反映するために適切にサイズを変更します。
最後に、ペイントメソッドによって描画されるピクセルでコンテキストを渡します。運が良ければ、キャンバスは白いピクセルとしてレンダリングする必要があります。最もエキサイティングな公開ではありませんが、私たちは近づいています。<span>const log = (<span>...args</span>) => { </span> <span>console.log(...args.map(toJs)) </span><span>}; </span>
クリックイベントを聞き、それらを使用して特定のピクセルの色を変更して、以前のdraw()関数を変更したい。
<span>// the dimensions of the canvas </span><span>const [height, width] = [20, 20]; </span> <span>// the size of each canvas pixel </span><span>const pixelSize = 10; </span> <span>// converts an integer to a 2d coordinate vector </span><span>const to2D = (i) => vector( </span> i <span>% width, </span> <span>Math.floor(i / width) </span><span>); </span>
この時点で、黒いピクセルをキャンバスに描くことができ、各フレームは前のフレームに基づいて、複合画像を作成します。
トラッキングフレーム
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
クリックリスナーを変更して、フレームスタックを使用して作業する必要があります。
PEEK()関数を使用して、スタックの上部にフレームを取得しています。次に、それを使用して、draw()関数を備えた新しいフレームを作成します。最後に、conj()を使用して、フレームスタックの上部に新しいフレームを
conjoin<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
を使用します。 ローカル状態(frame = conj(frames、newFrame))を変更していますが、実際にデータを変異させていません。 変更を取り消す
最後に、スタックからトップフレームをポップするために元に戻すボタンを実装する必要があります。
最後に、変更を反映するために、新しいスタックの上部フレームをpaint()関数に渡します。この時点で、キャンバスの変更を描画して元に戻すことができるはずです。
demo<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
これが私たちが最終的に次のことをします:
codepenでSitePoint(@SitePoint)でペン森のピクセルを参照してください。
このアプリケーションを改善できる方法のアイデアのリストは次のとおりです。
カラーパレットを追加して、ユーザーが描画する前に色を選択できるようにします
ctrl zキーボードショートカットを元に戻す
を作成しますJavaScriptの不変性は、作成後に変更できないオブジェクトの状態を指します。これは、変数に値が割り当てられたら、変更できないことを意味します。この概念は、副作用を回避し、コードをより予測可能で理解しやすくするため、機能的なプログラミングにおいて重要です。また、効率的なデータ検索とメモリの使用を許可することにより、アプリケーションのパフォーマンスを向上させます。 JavaScriptへの永続的なデータ構造。これらのデータ構造は不変です。つまり、作成されると変更できません。これは、データの完全性を維持するのに役立ち、偶発的な変更を回避します。 Moriは、これらのデータ構造を操作しやすくする機能的プログラミングユーティリティの豊富なセットも提供します。不変のデータを処理する方法を提供している森は、より効率的で堅牢な方法を提供します。森の永続的なデータ構造は、ネイティブのJavaScriptメソッドよりも速く、消費量が少ないメモリを消費します。さらに、MoriはJavaScriptで利用できない幅広い機能プログラミングユーティリティを提供します。 。不変のオブジェクトを作成すると変更できないため、変更されるリスクなしに複数の機能呼び出しで安全に再利用できます。これにより、効率的なメモリ使用量とデータの検索が速くなり、アプリケーションの全体的なパフォーマンスが向上します。作成後に変更されます。一方、不変のデータ構造は作成されると変更できません。不変のデータ構造の操作は、新しいデータ構造をもたらします。
MORIは、データを操作するための機能的なプログラミングユーティリティの豊富なセットを提供します。これらのユーティリティを使用すると、元のデータを変更せずにデータ構造でマップ、削減、フィルターなど、さまざまな操作を実行できます。森には、変更されたときに以前のバージョンのデータを保存する不変のデータ構造があります。これは、永続的なデータ構造で操作を実行するたびに、データ構造の新しいバージョンが作成され、古いバージョンが保持されることを意味します。
Moriは、不変のデータ構造を提供することにより、データの整合性を保証します。これらのデータ構造を作成すると変更できないため、偶発的なデータ変更のリスクは排除されます。これは、データの整合性を維持するのに役立ちます。
以上がMoriを使用した不変のデータと機能的JavaScriptの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。