HN 日本語サマリー

← 一覧へ戻る
プログラミング

空間言語:コードを2Dで書く

Spatial languages: Writing code in 2D (shukla.io)

96 pointsby BinRoo37 コメント

要約

本記事では、従来の線形的なコード記述を超え、2次元空間でコードを書くという概念を探求します。3項演算子 `@@` を導入し、空間的な配置が3方向トグルや量子コンピューティング回路のような複雑なロジックを、垂直連鎖や演算のよりクリーンな表現を可能にすることで、どのように簡略化できるかを示しています。

全文翻訳

式は必ずしも左から右へ、一行で読む必要はないと思います。a < b を垂直に書くことはできないでしょうか? a < b これまでの間、私たちは1次元空間で式を書いてきましたが、 extra の次元を解き放つとどうなるでしょうか?もちろん、IDEとパーサーはすべてがうまく機能するように協力する必要があります。それでも、そのポイントは何でしょうか?さて、以下の例を見てみましょう。 3項関数 私たちは3項関数をあまり多くのinfix表記で見かけることはありません。例えば、Python(およびほとんどの他の言語)では、一度に3つ以上のものを扱うことができます。 x and y and z 厳密には、それは複数の二項関数を組み合わせただけです。 (a < b) and (b < c) を簡略化するためのPythonのショートカットでさえ、同じことの繰り返しです。 a < b < c さて、私が遊びで使っている andFlip という面白い小さな演算子を紹介しましょう。これは、真の3項infix表記の必要性を動機づけます。まず、関数の定義は次のとおりです。 def andFlip(args): if args[0] and args[1]: args[2] = not args[2] return args[2] これは2つの入力を受け取り、両方の入力が真であればターゲットを反転させます。infix表記でこれを書く良い方法はありません。しかし、とにかく試してみましょう! andFlip 演算子のinfix記号として @@ を定義します。実際にはどのように見えるでしょうか? 方法1:標準的な方法 1つの選択肢は andFlip をカリー化することです:infix @@ は2つの引数を取り、3番目の引数を待つ関数を返します。たとえば次のようになります。 (a1 @@ a2) a3 ええ、それはまあまあです。機能します。しかし… 方法2:奇妙な方法 しばらくの間、コードについて知っていることを忘れてください。もし私が魔法の杖を振ることができたら、3項表記が次のように見えるように願うでしょう。 a3 a1 @@ a2 y軸を活用するのも良いでしょう、そう思いませんか?連鎖がずっと簡単になります。 t1 = 0 t2 = 0 t1 t2 (x @@ y) @@ z 連鎖では、x @@ y は t1 を切り替え、t1 は連鎖 t1 @@ z を継続し、t2 を切り替えます。これは成功した3方向トグルであり、ターゲットを切り替えるには x、y、z のすべてが真である必要があります。 ちなみに、私たちは今2D空間で作業しているので、次の式、t1 x @@ y は次のものと同等です。 x @@ y t1 例:私の鶏小屋のドア 私が鶏小屋に設置した自動ドアを考えてみましょう。 自動ドア(右)とその制御ボックス 光センサーと内部時計だけで、マイクロコントローラーは次のものを追跡します。 x – 光センサーが夜明け/夕暮れのしきい値を超えた y – 読み取りが5分間安定している z – ゲートが最近切り替えられていない ドアは、これら3つすべてが一致した場合にのみ切り替わります。通り過ぎる雲は x をトリップさせますが、y より長く持続することはないため、ドアはそのままです。 小屋のコントローラーは、上記の連鎖式と同じです。 t1 = 0 t2 = 0 t1 t2 (x @@ y) @@ z なぜ単に x and y and z と書かないのか疑問に思うかもしれません。なぜなら、その場合、ドアはセンサーをミラーリングし、条件が消えるとすぐにスイングバックするからです。@@ はその場で切り替わります:ドアは一度動き、そのまま残り、部分的な答えはスクラッチビットである t1 に格納されます。 空間言語は、垂直連鎖を導入するとさらに魅力的になります。 垂直連鎖 その可変変数 t1 は、式を「純粋関数」と呼ぶ資格を失わせます。しかし、それを偽装することはできるでしょうか?同じ式内で t1 をリセットすることはできるでしょうか?2つのパズルを通して垂直連鎖を紹介したいと思います。 パズル1:t1 を 0 にリセットする 前述のように、上記の連鎖例では、変数 t1 は可変です。ここにパズルがあります:それを 0 にリセットするにはどうすればよいですか?式は不変に見えるようにするにはどうすればよいですか? 単に t1 = 0 を追加して終わりにする誘惑に駆られるかもしれませんが、パズルの目標はすべて同じ式内で行うことです。スクロールダウンする前に試してみてください! 答えは次のとおりです。 t1 = 0 t2 = 0 (x @@ y) @@ z t1 t2 (x @@ y) おそらくあなたの頭は吹き飛んだでしょう。データフローは、プログラミング言語で予想されるように、左から右、上から下へと続いています。x と y が真の場合、最初の x @@ y は t1 を切り替え、次に2番目の x @@ y はそれをリセットします。そうでなければ、どちらも t1 に影響を与えません。 このパズルは、量子コンピューティングの CCCX ゲートを実際にモデル化しており、中間値 t1 のアンコンピュートを同じ回路内で行うことは重要な要件です。 パズル2:一時変数を元の値に戻す t1 が未知のブール値 n で開始された場合、さらに複雑になります。 t1 = n t2 = 0 t1 t2 (x @@ y) @@ z t1 を元の値に戻すための上記と同じトリックは機能しません。自分で確認してください:t1 = 1 で、x、y、z がすべて真の場合、t2 は 0 のままですが、これは正しくありません。 t1 = 1 t2 = 0 (1 @@ 1) @@ 1 t1 t2 (1 @@ 1) では、答えは何でしょうか?タダ! t1 = n t2 = 0 t1 t2 (x @@ y) @@ z t1 t2 (x @@ y) @@ z 絶対に伝説的です。この単一の2D式は、t2 の正しい結果を生成しながら、t1 を n にリセットします。 このパズルは、量子コンピューティングで貸し借りしたアンシラの概念に触発されています。t1 は「借りた」または「汚れた」値であり、変更されません。 同じ回路が Qiskit で表示されます。 from qiskit import QuantumCircuit, QuantumRegister x = QuantumRegister(1, "x") y = QuantumRegister(1, "y") z = QuantumRegister(1, "z") t1 = QuantumRegister(1, "t1") # starts in unknown state n t2 = QuantumRegister(1, "t2") # target, starts at 0 qc = QuantumCircuit(x, y, z, t1, t2) qc.ccx(x, y, t1) qc.ccx(t1, z, t2) qc.ccx(x, y, t1) qc.ccx(t1, z, t2) Qiskit の実装は、変数の追跡が前後するため、追跡が難しいと私は主張します。ちなみに、対応する量子回路図は次のとおりです。 ワイヤー:x、y、z、t1 = n、t2 = 0 出力:x、y、z、n、x·y·z ccx x y t1 ccx t1 z t2 ccx x y t1 ccx t1 z t2 ある意味では、この回路図は理解しやすいですが、おそらくスケーラブルではありません。空間言語の構文は、両方の世界の最良のものです。 新しい演算子の定義 まず、@@ の弟を紹介します。単一の @ は、2つではなく1つの条件を受け取ります:x が真の場合、x @ y は y を反転させます。 @ と @@ を手に入れれば、新しい演算子を空間的に定義できます。 := の左側は形状であり、右側はその形状が書き換えられるものです。 b maj c (a @ b) @@ (a @ c) a := a この奇妙な演算子は、a が真の場合に b と c の両方を反転させ、両方の側が真になった場合に a を反転させます。これは a ^ ((a^b) & (a^c)) を a に計算します。これは偶然にも3つの入力の多数と等しくなります。そのため、この演算子は maj と呼ばれます。そして maj は任意の選択ではありません。これは古典的な量子リップルキャリーアダーの MAJ ゲートであり、(carry, bit, bit) の多数が次のキャリーです。 maj は b と c を汚れたままにし、a^b と a^c を保持します。これは、貸し借りした t1 と同じ種類の混乱です。クリーンアップするために、uma も導入します。この名前は、同じアダー論文「UnMajority and Add」に由来しています。これは c をアンメジャーし、a を復元し、合計を b に追加します。 c uma b ((b @@ a) @ a) @ b a := c それらを配線したときに何が起こるか見てみましょう。 3ビットアダー 3つの maj を使用して3ビットアダーを構築できます。これにより、キャリーが a ワイヤーを介してリプルアップし、最後の @ が c3 にキャリーアウトをドロップし、最後に1行の uma がすべてのスクラッチワイヤーをリセットします。 c0 = 0 c3 = 0 (b2 maj (b1 maj (b0 maj c0))) @ c3 a2 a1 a0 (( . uma b2) uma b1) uma b0 c0 これは、自己の後始末も行う、単一の2D式での完全な3ビットアダーです。b ワイヤーには a + b が格納され、c3 にはキャリーアウトが格納され、c0 と a ワイヤーは元の状態に戻ります。(c0 = 1 に設定すると、a + b + 1 が無料で得られます。) 2つの線が互いをミラーリングしていることに注意してください。上部は右にネストして b0 まで下がり、キャリーを上にリプルバックします。下部は左にネストして b2 まで下がり、外側にアンワインドします。共有されている a1 と a0 のラベルはピボットであり、それぞれが上の maj と下の uma を務めます。そして . は垂直パイプであり、直接上にあるワイヤー、ここでは a2 を表します。キャリーチェーンは垂直にステッチされています:アンコンピュートはコンピュートに触れます、パズルのように。 他の空間言語のスピードランニング いくつかの他の空間言語の簡単なツアーです。 Befunge 2D言語の祖父(Befunge、1993):命令ポインタが物理的に移動します