web-dev-qa-db-ja.com

レンズ、fclabels、データアクセサー-構造へのアクセスと突然変異のためのライブラリの方が優れています

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

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

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

170
Tener

レンズの提供を認識しているライブラリが少なくとも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回置くことと同じであり、2番目に置くことは勝つことです。

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

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

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

それを念頭に置いて、さまざまな実装に切り替えることができます。

実装

fclabels

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

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

data-accessor

[編集:data-accessorはこの表現を使用しなくなりましたが、data-lensの形式に類似した形式に移行しました。しかし、私はこの解説を続けています。]

data-accessor は、一部がis Haskell 98であるため、fclabelsよりもやや人気があります。しかし、内部表現の選択により、私はスローされます私の口の中に少し。

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

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

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

とは言うものの、Henningは別の ' data-accessor-template 'パッケージにアクセサを自動的に生成するテンプレートハッセル配管を含めました。

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

レンズ

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

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

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

その結果、このアプローチが嫌いです。Haskell98から不要に引っ張られ(抽象的にレンズに提供したい場合)、Categoryインスタンスを奪います.でレンズを構成できます。実装には、マルチパラメータ型クラスも必要です。

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

さらに、冒頭で述べた副条件には、この形式のニース式はありません。 'fclabels'と同様に、これはメインパッケージで直接レコードタイプのレンズを自動的に生成するテンプレートハスケル法を提供します。

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

data-lens

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

私の data-lens パッケージは、 Store comonadに関してレンズを提供します。

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つの法則を満たす「レンズ」値のサブセットが、ストアコモナのラップされた関数が「コモナ代数」であるレンズであるためです。これは、レンズlの3つの毛むくじゃらの法則を、2つの適切な点のない等価物に変換します。

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

このアプローチは、Russell O'Connorの FunctorLensApplicativeBiplateに:マルチプレートの導入 そして、プレレに基づいて ブログされました / Jeremy Gibbons。

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

data-lensのレンズはCategorylensesパッケージとは異なります)を形成し、Haskell 98(fclabels/lensesとは異なります)は正気(data-accessorのバックエンドとは異なり)およびわずかに効率的な実装を提供します。 data-lens-fd は、Haskellの外に踏み出そうとする人々にMonadStateで作業するための機能を提供します98、およびtemplate-haskell機械は data-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))

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

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

また、機能構成がレンズ構成であることを自分で検証できます。

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

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

残念ながら、レンズについて話す最良の方法はランク2の多型を使用することですが、レンズを定義するときにそのシグネチャを直接使用する必要はありません。

上記で_2に対して定義したLensは、実際はLensFamilyです。

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

レンズ、レンズファミリー、およびゲッター、セッター、フォールド、トラバーサルを含むその他の一般化を含むライブラリを作成しました。 lens パッケージとしてハッカーで利用可能です。

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

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

196
Edward KMETT