lens、fclabels、data-accessor-構造体へのアクセスと変更のためのライブラリのどちらが優れているか


173

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

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

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


3
今日現在、 lensパッケージには最も豊富な機能とドキュメントが含まれているため、その複雑さと依存関係を気にしないのであれば、それで十分です。
モジュラー

回答:


200

レンズの提供を知っているライブラリは少なくとも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

そして、3番目に、2回置くことは、1回置くことと同じです。

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

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

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

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

実装

fclabels

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

の採用に対する1つの障害fclabelsは、メインパッケージにテンプレートハスケル配管が含まれているため、パッケージがHaskell 98ではなく、(かなり論争の余地のない)TypeOperators拡張が必要なことです。

データアクセサー

[編集:data-accessorはこの表現を使用していませんが、のような形式に移動しましたdata-lens。ただし、私はこの解説を続けています。]

データ・アクセサよりもいくらか人気がありfclabels、それはので、部分的には、ありますしかし、内部表現のその選択は私の口の中で少し投げますハスケル98。

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)

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

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

さらに、最初に述べられたサイドコンディションは、この形式では実際に良い表現をしていません。「fclabels」と同様に、これはメインパッケージで直接レコードタイプのレンズを自動生成するテンプレートハスケルメソッドを提供します。

Categoryインスタンスの欠如、バロックエンコーディング、およびメインパッケージでのテンプレートハスケルの要件のため、これは私の最も好きではない実装です。

データレンズ

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

私のdata-lensパッケージは、ストアコマンドの観点からレンズを提供しています。

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つの法則を満たす「レンズ」値のサブセットは、厳密に言えば、包まれた関数がストアコモナドの「コモナ代数」であるレンズであるため、この表現には理論上の正当な理由もあります。 。これは、レンズの3つの毛のような法則lを2つの適切にポイントフリーな同等物に変換します。

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

このアプローチは、最初に述べたとラッセル・オコナーさんに説明したFunctorのであるLensようApplicativeにですBiplate:紹介マルチプレートとされたプレプリントに基づいについてブログジェレミー・ギボンズ。

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

したがって、(パッケージとは異なり)data-lensa形式のレンズは(/ とは異なり)Haskell 98であり、(のバックエンドとは異なり)正気であり、わずかに効率的な実装を提供し、外に出ようとする人のためにMonadStateと連携する機能を提供しますHaskell 98のテンプレートであり、template-haskell機構はを介して利用できるようになりました。Categorylensesfclabelslensesdata-accessordata-lens-fddata-lens-template

アップデート6/28/2012:その他のレンズ実装戦略

同型レンズ

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

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

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

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

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

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

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

van Laarhovenレンズ

インスタンスを使用せずに、(.)およびidで構成できるようにレンズをモデル化できます。Category

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

レンズのタイプとして。

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

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

機能構成がレンズ構成であることを自分で確認できます。

最近、この署名を一般化するだけで、ファンラーホーフェンレンズをさらに一般化して、フィールドのタイプを変更できるレンズファミリーを取得する方法について書きました

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'の関数をtype で提供するだけです。これにより、導入コストが大幅に削減されます。

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


28
楽観的なアプローチでfclabelsが好き:->
Tener

3
データアクセサへの重要なガイドfclabels への重要なガイドの記事は注目に値するかもしれません
hvr

10
Haskell 1998互換性は重要ですか?コンパイラの開発が簡単になるからですか?代わりに、Haskell 2010の話に切り替えるべきではありませんか?
yairchu

55
大野!私はの元の作者でしたがdata-accessor、それをヘニングに渡し、注意を向けなくなりました。このa -> r -> (a,r)表現はまた私を不快にさせ、私の元の実装はちょうどあなたのLensタイプのようでした。Heeennnninngg !!
luqui

5
Yairchu:ほとんどの場合、ライブラリはghc以外のコンパイラで動作する可能性があります。テンプレートHaskell 他に誰もません。2010はここに関連するものを追加しません。
エドワードKMETT
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.