178

レコードのフィールドにアクセスして操作するための人気のあるライブラリが少なくとも3つあります。私が知っているのは、データアクセサー、fclabels、レンズです。

個人的にはデータアクセサーから始めて、今は使っています。しかし最近、haskell-cafeでfclabelsが優れているという意見がありました。

したがって、私はこれら3つの(そしておそらくそれ以上の)ライブラリの比較に興味があります。

4

1 に答える 1

201

私がレンズを提供していることを知っているライブラリは少なくとも4つあります。

レンズの概念は、それが同形の何かを提供するということです

data Lens a b = Lens (a -> b) (b -> a -> a)

ゲッターとセッターの2つの機能を提供します

get (Lens g _) = g
put (Lens _ s) = s

3つの法律に従う:

まず、何かを入れれば、それを取り戻すことができるということです

get l (put l b a) = b 

第二に、取得してから設定しても答えは変わりません

put l (get l a) a = a

そして第三に、2回置くことは1回置くことと同じであり、むしろ2番目の入れ方が勝ちます。

put l b1 (put l b2 a) = put l b1 a

型システムはこれらの法則をチェックするのに十分ではないことに注意してください。したがって、使用するレンズの実装に関係なく、自分で法則を確認する必要があります。

これらのライブラリの多くは、上部に多数の追加のコンビネータを提供し、通常、単純なレコードタイプのフィールド用のレンズを自動的に生成するための何らかの形式のテンプレートhaskell機構を提供します。

それを念頭に置いて、さまざまな実装に目を向けることができます。

実装

fclabels

fclabelsa :-> bは、上記のタイプに直接変換できるため、レンズライブラリの中でおそらく最も簡単に推論できます。レンズを作成できるので便利なCategoryインスタンスを提供します。(:->)また、ここで使用されるレンズの概念を一般化する無法Pointタイプと、同型を処理するためのいくつかの配管も提供します。

の採用の障害の1つfclabelsは、メインパッケージにtemplate-haskell配管が含まれているため、パッケージがHaskell 98ではなく、(かなり議論の余地のない)TypeOperators拡張機能も必要になることです。

データアクセサ

[編集:data-accessorこの表現は使用されなくなりましたが、のような形式に移動しましたdata-lens。しかし、私はこの解説を保持しています。]

data-accessorは、Haskell 98であるfclabelsという理由もあり、よりもいくらか人気があります。ただし、内部表現を選択しているため、少し口に入れてしまいます。

Tレンズを表すために使用するタイプは、内部的に次のように定義されます。

newtype T r a = Cons { decons :: a -> r -> (a, r) }

したがって、getレンズの値を取得するには、「a」引数に未定義の値を送信する必要があります。これは、信じられないほど醜いアドホックな実装だと思います。

とは言うものの、Henningには、別の「 data-accessor-template」パッケージにアクセサーを自動的に生成するためのtemplate-haskell配管が含まれています。

Haskell 98であり、非常に重要なCategoryインスタンスを提供する、すでにそれを採用しているかなり大きなパッケージのセットの利点があります。したがって、ソーセージの製造方法に注意を払わない場合、このパッケージは実際にはかなり合理的な選択です。 。

レンズ

次に、レンズをそのようなモナド準同型として直接定義することにより、レンズが2つの状態モナド間に状態モナド準同型を提供できることを観察するレンズパッケージがあります。

実際にレンズのタイプを提供するのが面倒な場合、次のようなランク2のタイプになります。

newtype Lens s t = Lens (forall a. State t a -> State s a)

結果として、私はむしろこのアプローチが好きではありません。なぜなら、Haskell 98から不必要にあなたを引き離し(抽象的にレンズにタイプを提供したい場合)、Categoryレンズのインスタンスを奪うからです。それらをで構成します.。実装には、マルチパラメータ型クラスも必要です。

ここで説明する他のすべてのレンズライブラリは、コンビネータを提供するか、同じ状態のフォーカリゼーション効果を提供するために使用できるため、この方法でレンズを直接エンコードしても何も得られないことに注意してください。

さらに、冒頭で述べたサイドコンディションは、この形ではあまり良い表現ではありません。'fclabels'と同様に、これは、メインパッケージで直接レコードタイプのレンズを自動的に生成するためのtemplate-haskellメソッドを提供します。

インスタンスの欠如Category、バロックエンコーディング、およびメインパッケージのtemplate-haskellの要件のため、これは私の最も嫌いな実装です。

データレンズ

[編集:1.8.0の時点で、これらはcomonad-transformersパッケージからdata-lensに移動しました]

私のdata-lensパッケージは、Storecomonadの観点からレンズを提供します

newtype Lens a b = Lens (a -> Store b a)

どこ

data Store b a = Store (b -> a) b

これを拡張すると、

newtype Lens a b = Lens (a -> (b, b -> a))

これは、ゲッターとセッターから共通の引数を除外して、要素を取得した結果で構成されるペアを返し、セッターを新しい値に戻すと見なすことができます。これにより、「セッター」という計算上の利点が得られます。ここでは、値を取得するために使用される作業の一部をリサイクルできるためfclabels、特にアクセサーがチェーンされている場合は、定義よりも効率的な「変更」操作が可能になります。

この応答の冒頭に記載されている3つの法則を満たす「レンズ」値のサブセットは、ラップされた関数がストアcomonadの「comonad余代数」であるレンズであるため、この表現には理論上の正当性もあります。 。これにより、レンズの3つの毛深い法則lが2つの適切なポイントフリーの法則に変換されます。

extract . l = id
duplicate . l = fmap l . l

このアプローチは、Russell O'Connor's Functoris to Lensas Applicativeis to Biplate:Introducing Multiplateで最初に注目され、説明され、JeremyGibbonsによるプレプリントに基づいてブログに掲載されました

また、レンズを厳密に操作するための多数のコンビネータと、などのコンテナ用のストックレンズも含まれていData.Mapます。

したがって、(パッケージとは異なり)data-lensフォームaのレンズは、( /とは異なり)Haskell 98であり、(のバックエンドとは異なり)正常であり、わずかに効率的な実装を提供し、外に出ることをいとわない人のためにMonadStateを操作するための機能を提供しますHaskell 98の、およびtemplate-haskellの機械がを介して利用できるようになりました。Categorylensesfclabelslensesdata-accessordata-lens-fddata-lens-template

2012年6月28日更新:その他のレンズ実装戦略

同形レンズ

検討する価値のある他の2つのレンズエンコーディングがあります。1つ目は、構造をフィールドの値、および「その他すべて」に分割する方法としてレンズを表示するための優れた理論的方法を提供します。

同型写像のタイプが与えられた

data Iso a b = Iso { hither :: a -> b, yon :: b -> a }

有効なメンバーがを満たすようhither . yon = idに、yon . hither = id

レンズは次のように表すことができます。

data Lens a b = forall c. Lens (Iso a (b,c))

これらは主にレンズの意味を考える方法として役立ち、他のレンズを説明するための推論ツールとして使用できます。

vanLaarhovenレンズ

を使用して、インスタンスがなくても、(.)とで構成できるようにレンズをモデル化できます。idCategory

type Lens a b = forall f. Functor f => (b -> f b) -> a -> f a

私たちのレンズのタイプとして。

次に、レンズの定義は次のように簡単です。

_2 f (a,b) = (,) a <$> f b

そして、関数の合成がレンズの合成であることを自分で検証できます。

最近、van Laarhovenレンズをさらに一般化して、このシグニチャを次のように一般化するだけで、フィールドのタイプを変更できるレンズファミリを取得する方法について書きました。

type LensFamily a b c d = forall f. Functor f => (c -> f d) -> a -> f b

これは、レンズについて話す最良の方法がランク2のポリモーフィズムを使用することであるという不幸な結果をもたらしますが、レンズを定義するときにその署名を直接使用する必要はありません。

Lens上で定義したのは_2実際にはLensFamilyです。

_2 :: Functor f => (a -> f b) -> (c,a) -> f (c, b)

レンズ、レンズファミリー、およびゲッター、セッター、フォールド、トラバーサルなどの他の一般化を含むライブラリを作成しました。パッケージとしてハッキングで入手でき lensます。

Functor f => (b -> f b) -> a -> f a繰り返しになりますが、このアプローチの大きな利点は、ライブラリメンテナが、特定のタイプ「a」および「b」に対してタイプを指定する関数を提供するだけで、レンズライブラリの依存関係をまったく発生させずに、ライブラリでこのスタイルのレンズを実際に作成できることです。これにより、導入コストが大幅に削減されます。

新しいレンズを定義するために実際にパッケージを使用する必要がないので、ライブラリHaskell98を維持することについての私の以前の懸念から多くのプレッシャーを取り除きます。

于 2011-04-24T07:31:21.233 に答える