レンズには不利な点はないようですが、標準のHaskellに比べて大きな利点があります。可能な限りレンズを使用すべきでない理由はありますか?パフォーマンスに関する考慮事項はありますか?さらに、テンプレートHaskellには大きなオーバーヘッドがありますか?
2 に答える
レンズは、データ コンストラクターに対して直接クロージャーを使用する代替手段を形成します。したがって、レンズには、関数とデータ コンストラクターを直接使用する場合とほぼ同じ注意事項があります。
これによるいくつかの短所:
レンズを変更するたびに、多くのオブジェクトが (再) 作成される可能性があります。たとえば、次のデータ構造があるとします。
A { B { C { bla = "foo" } } }
...そして タイプ のレンズ
Lens A String
、新しい を作成し、A
そのB
レンズC
を「変更」するたびに。これは、Haskell (多数のオブジェクトを作成する) では珍しいことではありませんが、オブジェクトの作成はレンズの背後に隠されているため、潜在的なパフォーマンス シンクを見つけるのが難しくなっています。- レンズは、「マッピング機能」が使用されているため、非効率を生み出す可能性もあります。たとえば、リストの 26番目の要素を変更するレンズを作成すると、ルックアップ時間が原因で多くの速度が低下する可能性があります。
そして長所:
- レンズは、通常のレコードと組み合わせて、状態モナド (例については を参照) で美しく使用できます。これにより、大規模なデータ共有により、ほとんどの場合、多くのオブジェクトを再
data-lens-fd
作成することを回避できます。関数の例と、Snap Web フレームワークで関数focus
を使用する同様のパターンを参照してください。withSomething
- レンズは明らかに、実際にはメモリをその場で変更しないため、並行性のコンテキストで状態について推論する必要がある場合に非常に役立ちます。したがって、レンズは、さまざまな種類のグラフを扱うときに非常に役立ちます。
ただし、レンズは、データ コンストラクター上のクロージャーと常に同形であるとは限りません。違いのいくつかを次に示します (ここでdata-lens
は実装として取り上げます)。
- ほとんどのレンズ実装では、何らかの形式のデータ型を使用して、「アクセサー」と「ミューテーター」をペアとして格納します。の場合
data-lens
、それはStore
コモナドです。これは、レンズを作成するたびに、そのデータ構造が作成されるため、ごくわずかな余分なオーバーヘッドが発生することを意味します。 - レンズは何らかの未知のマッピングを介して値に依存するため、ガベージ コレクションを推論するのが難しくなる可能性があり、大量のメモリ チャンクに依存する非常に一般的なレンズを使用していることを忘れて、(論理的な) メモリ リークが発生する可能性があります。たとえば、ある大きなベクトルの要素にアクセスするレンズを考えてみましょう。このレンズは別のレンズで構成されているため、最初のレンズが隠されているため、構成されたレンズがまだ大量のメモリに依存していることを確認するのが難しくなっています。
テンプレート Haskell コードはコンパイル時に実行され、レンズの実行時のパフォーマンスにはまったく影響しません。
data-lensパッケージを想定しています。レンズは、データのようなもの(レコード、タプル、マップなど)に対して非常にうまく機能しました。実際、おそらく共有が優れているために、通常のアプローチよりもパフォーマンスが向上することもあります。パフォーマンスに関しては、手作業で記述したコードとほぼ同じパフォーマンスが得られます。
ただし、レンズにペナルティが課せられる可能性のある機能のようなものがあります。たとえば、私は少なくとも一度はこのようなレンズを使用したことを覚えています。
result :: (Eq a) => a -> Lens (a -> b) b
クエリは非常に高速でしたが、関数の特定の結果値を上書きして、特定のシナリオに合わせて調整することがありました。これは、関数の本体を大きなで囲むことに相当しif
ます。もちろん、パフォーマンスへの影響はレンズ自体とは関係ありませんが、注目に値するものです。