Haskell がミューテーションをまったく提供しないというのはよくある神話です。実際には、これは非常に特別な種類の変更を提供します。値は、未評価から評価済みに一度だけ変更できます。この特別な種類の突然変異を利用する技術は、結び目を結ぶことと呼ばれます。C++ のものと同じようなデータ構造から始めます。
data Vector -- held abstract
data Point = Point
{ position :: Vector
, v, w :: Double
, neighbors :: [Point]
}
次に、同じ配列内の他の要素へのポインターを含むArray Point
を作成します。neighbors
次のコードの主な機能はArray
、spine-lazy (その要素をすぐに強制しない) であり、高速なランダム アクセスを備えていることです。必要に応じて、お気に入りの代替データ構造をこれらのプロパティに置き換えることができます。
近隣探索機能のインターフェースには多くの選択肢があります。具体的にし、自分の仕事を簡単にするために、Vector
と のリストを取り、Vectors
隣人のインデックスを与える関数があると仮定します。
findNeighbors :: Vector -> [Vector] -> [Int]
findNeighbors = undefined
computeV
とのいくつかの型も配置しましょうcomputeW
。ノンスについては、computeV
あなたが述べた非公式の契約に従うようにお願いします。つまり、任意のフィールドのposition
andフィールドを参照できますが、 orフィールドは参照できません。(同様に、それが手に入れることができる任意のフィールド以外は何でも見ることができます。) あまり多くの体操をしなくても、型レベルでこれを強制することは実際には可能ですが、今はそれをスキップしましょう。neighbors
Point
v
w
computeW
w
Point
computeV, computeW :: Point -> Double
(computeV, computeW) = undefined
これで、(ラベル付きの) インメモリ グラフを作成する準備が整いました。
buildGraph :: [Vector] -> Array Int Point
buildGraph vs = answer where
answer = listArray (0, length vs-1) [point pos | pos <- vs]
point pos = this where
this = Point
{ position = pos
, v = computeV this
, w = computeW this
, neighbors = map (answer!) (findNeighbors pos vs)
}
それだけです。今、あなたはあなたを書くことができます
newPositions :: Point -> [Vector]
newPositions = undefined
wherenewPositions
は、渡されたフィールドのいずれかを完全に自由に検査し、Point
すべての機能をまとめます。
update :: [Vector] -> [Vector]
update = newPositions <=< elems . buildGraph
編集: ...冒頭の「特別な種類の突然変異」コメントを説明する: 評価中にw
、 a のフィールドを要求すると、次のPoint
順序で物事が起こることが期待できます:フィールドcomputeW
を強制しv
ます; 次に、フィールドcomputeV
を強制します。neighbors
次に、neighbors
フィールドは未評価から評価済みに変化します。次に、v
フィールドは未評価から評価済みに変化します。その後、w
フィールドは未評価から評価済みに変化します。これらの最後の 3 つのステップは、C++ アルゴリズムの 3 つの変更ステップと非常によく似ています。
二重編集: 私はこのことが実行されるのを見たいと思ったので、上記の抽象化されたすべてのものをダミーの実装でインスタンス化しました。また、正しく評価したかどうかさえ確信が持てなかったので、一度だけ評価するのを見たかったのです! それで私はいくつかのtrace
電話をかけました。完全なファイルは次のとおりです。
import Control.Monad
import Data.Array
import Debug.Trace
announce s (Vector pos) = trace $ "computing " ++ s ++ " for position " ++ show pos
data Vector = Vector Double deriving Show
data Point = Point
{ position :: Vector
, v, w :: Double
, neighbors :: [Point]
}
findNeighbors :: Vector -> [Vector] -> [Int]
findNeighbors (Vector n) vs = [i | (i, Vector n') <- zip [0..] vs, abs (n - n') < 1]
computeV, computeW :: Point -> Double
computeV (Point pos _ _ neighbors) = sum [n | Point { position = Vector n } <- neighbors]
computeW (Point pos v _ neighbors) = sum [v | Point { v = v } <- neighbors]
buildGraph :: [Vector] -> Array Int Point
buildGraph vs = answer where
answer = listArray (0, length vs-1) [point pos | pos <- vs]
point pos = this where { this = Point
{ position = announce "position" pos $ pos
, v = announce "v" pos $ computeV this
, w = announce "w" pos $ computeW this
, neighbors = announce "neighbors" pos $ map (answer!) (findNeighbors pos vs)
} }
newPositions :: Point -> [Vector]
newPositions (Point { position = Vector n, v = v, w = w }) = [Vector (n*v), Vector w]
update :: [Vector] -> [Vector]
update = newPositions <=< elems . buildGraph
そしてghciで実行:
*Main> length . show . update . map Vector $ [0, 0.25, 0.75, 1.25, 35]
computing position for position 0.0
computing v for position 0.0
computing neighbors for position 0.0
computing position for position 0.25
computing position for position 0.75
computing w for position 0.0
computing v for position 0.25
computing neighbors for position 0.25
computing v for position 0.75
computing neighbors for position 0.75
computing position for position 1.25
computing w for position 0.25
computing w for position 0.75
computing v for position 1.25
computing neighbors for position 1.25
computing w for position 1.25
computing position for position 35.0
computing v for position 35.0
computing neighbors for position 35.0
computing w for position 35.0
123
ご覧のとおり、各フィールドは位置ごとに最大 1 回計算されます。