2012-05-28 4 views
33

実際の例でどのレンズが使われているのかわかりません。 Hackageページのこの短い段落は、私が見つけた最も近いものです:レンズにはどのようなものが使われていますか?

このモジュールは、構造の要素にアクセスして更新する便利な方法を提供します。これはData.Accessorsと非常によく似ていますが、もう少し汎用的であり、依存性はより少なくなっています。私は特に、状態モナドの入れ子構造をいかにきれいに扱うのが好きです。

だから、彼らは何のために使われていますか?他の方法と比較してどのようなメリットとデメリットがありますか?彼らはなぜ必要なのですか?

+2

あなたはエドワード・クメットの「レンズ:機能的要望」(http://www.youtube.com/watch?v=efv0SQNde5Q)の話を楽しむかもしれません。これはScalaで紹介されていますが、Haskellでのレンズの有用性への翻訳ははっきりしているはずです。 –

答えて

45

これらは、データの更新よりもはっきりとした抽象化を提供し、決して本当に必要なものではありません。彼らはちょうどあなたが別の方法で問題について理由を挙げさせる。

Cのような必須/オブジェクト指向プログラミング言語では、いくつかの値のコレクション(構造体と呼ぶ)と、コレクションの各値にラベルを付ける方法についてよく知られていますいわゆる "フィールド")。

typedef struct { /* defining a new struct type */ 
    float x; /* field */ 
    float y; /* field */ 
} Vec2; 

typedef struct { 
    Vec2 col1; /* nested structs */ 
    Vec2 col2; 
} Mat2; 

は、あなたがそのようにように、この新しく定義された型の値を作成することができます:これは、このような定義につながる

data Vec2 = 
    Vec2 
    { vecX :: Float 
    , vecY :: Float 
    } 

data Mat2 = 
    Mat2 
    { matCol1 :: Vec2 
    , matCol2 :: Vec2 
    } 
:同様にHaskellでは

Vec2 vec = { 2.0f, 3.0f }; 
/* Reading the components of vec */ 
float foo = vec.x; 
/* Writing to the components of vec */ 
vec.y = foo; 

Mat2 mat = { vec, vec }; 
/* Changing a nested field in the matrix */ 
mat.col2.x = 4.0f; 

を、我々はデータ型を持っています

このデータ型は、次のように使用されます。

let vec = Vec2 2 3 
    -- Reading the components of vec 
    foo = vecX vec 
    -- Creating a new vector with some component changed. 
    vec2 = vec { vecY = foo } 

    mat = Mat2 vec2 vec2 

しかし、Haskellでは、データ構造内のネストされたフィールドを簡単に変更する方法はありません。これは、Haskellの値が不変であるため、変更している値の周りにすべてのラッピングオブジェクトを再作成する必要があるためです。あなたはHaskellで上記のような行列を持ち、かつマトリックス中に右上のセルを変更したい場合は、これを書かなければならない:

mat2 = mat { matCol2 = (matCol2 mat) { vecX = 4 } } 

それは動作しますが、それは不器用に見えます。だから、誰かが思いついたのは、基本的にはこれです.2つのものをグループ化すると、ゲッターが属するデータ構造を持つ対応する関数を持つ値(例えば、vecXmatCol2など)の「ゲッター」その値を変更して新しいデータ構造を作成すると、たくさんのすてきなことができます。例:

data Data = Data { member :: Int } 

-- The "getter" of the member variable 
getMember :: Data -> Int 
getMember d = member d 

-- The "setter" or more accurately "updater" of the member variable 
setMember :: Data -> Int -> Data 
setMember d m = d { member = m } 

memberLens :: (Data -> Int, Data -> Int -> Data) 
memberLens = (getMember, setMember) 

レンズを実装する方法はたくさんあります。このテキストでは、レンズが上記のようなものであるとしましょう:

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

e。あるタイプのゲッターとセッターの組み合わせで、タイプがbaのため、上記のmemberLensLens Data Intとなります。これは何を私たちにさせるのですか?

さて、第1のレンズからゲッターとセッターを取り出す2つの単純な関数を作ってみましょう:

getL :: Lens a b -> a -> b 
getL (getter, setter) = getter 

setL :: Lens a b -> a -> b -> a 
setL (getter, setter) = setter 

今、私たちはものの上に抽象化を開始することができます。上記の状況をもう一度取り上げて、「2階建て」の値を変更したいとしましょう。私たちは、他のレンズとのデータ構造を追加します。

data Foo = Foo { subData :: Data } 

subDataLens :: Lens Foo Data 
subDataLens = (subData, \ f s -> f { subData = s }) -- short lens definition 

それでは、二つのレンズを構成する機能を追加してみましょう。

(#) :: Lens a b -> Lens b c -> Lens a c 
(#) (getter1, setter1) (getter2, setter2) = 
    (getter2 . getter1, combinedSetter) 
    where 
     combinedSetter a x = 
     let oldInner = getter1 a 
      newInner = setter2 oldInner x 
     in setter1 a newInner 

コードは一種の迅速書かれているが、私はそれが何をするかは明らかだと思います:ゲッターは単純に合成されます。内側のデータ値を取得し、次にそのフィールドを読み取ります。セッターは、新しい内部フィールドの値がxである値aを変更すると仮定すると、まず古い内部データ構造を取り出し、その内部フィールドを設定してから、新しい内部データ構造で外部データ構造を更新します。今

、のは、単にレンズの値をインクリメント機能を作りましょう:

increment :: Lens a Int -> a -> a 
increment l a = setL l a (getL l a + 1) 

我々はこのコードを持っている場合は、それはそれが何をするか明確になる:今

d = Data 3 
print $ increment memberLens d -- Prints "Data 4", the inner field is updated. 

、なぜなら我々レンズを構成することができます:

f = Foo (Data 5) 
print $ increment (subDataLens#memberLens) f 
-- Prints "Foo (Data 6)", the innermost field is updated. 

すべてのレンズパッケージは基本的にwレンズのこのコンセプトを「セッター」と「ゲッター」のグループ分けで使いやすくしています。特定のレンズの実装では、1を書くことができるでしょう:だから

with (Foo (Data 5)) $ do 
    subDataLens . memberLens $= 7 

、あなたは、コードのCバージョンに非常に近い取得します。データ構造のツリーでネストされた値を変更するのは非常に簡単になります。

レンズはこれだけではありません。一部のデータの一部を変更する簡単な方法です。それらのために特定の概念を推論するのが非常に簡単になるので、さまざまな方法で相互にやりとりする必要がある膨大なデータ構造を持つ状況で幅広く使用されています。

レンズの長所と短所については、a recent question here on SOを参照してください。

+2

あなたの答えが欠落している重要な点の1つは、レンズが*ファーストクラス*なので、それらから他の抽象を構築できるということです。その点で組み込みのレコード構文は失敗します。 – jberryman

+2

また、OPに役立つかもしれないレンズに関するブログ記事を書いた:http://www.haskellforall.com/2012/01/haskell-for-mainstream-programmers_28.html –

12

レンズは便利な方法を提供しますデータ構造を統一的な構成で編集します。

多くのプログラムは、以下の操作を中心に構築されています

レンズが提供する(おそらくネストされた)データ構造体のフィールドを更新(おそらくネストされた)データ構造のコンポーネント

  • を見ます編集内容の一貫性を保証する方法で構造を表示および編集するための言語サポート。その編集は簡単に構成できます。構造体の一部を更新する場合には、構造体の一部を表示するために同じコードを使用することができます。

    レンズは、ビューから構造体にプログラムを書きやすくします。それらの構造のビュー(およびエディタ)に戻る構造から始まります。彼らはレコードアクセサやセッターの混乱をきれいにします。

    Pierce et al。普及したレンズ、例えば。 Quotient Lenses paperでは、Haskellの実装が広く使用されています(例えば、fclabelsとデータアクセサ)。具体的な使用事例について

    、検討:ユーザは

  • パーサとかなりプリンタ
  • コンパイラ
  • は、データ構造を更新する同期化構造化された方法で情報を編集している

    • グラフィカル・ユーザ・インターフェースを、
    • データベースおよびスキーマ

    など、世界中のデータ構造モデルと、そのデータへの編集可能なビューを持つ他の多くの状況があります。

  • 6

    レンズには、「フィールドアクセスと更新」という非常に一般的な概念が実装されていることがよく見落とされます。レンズは、関数のようなオブジェクトを含むあらゆる種類のものに対して記述することができます。これは、これを鑑賞する抽象的思考のビットを必要とするので、私はあなたに、レンズのパワーの例を示しましょう:

    at :: (Eq a) => a -> Lens (a -> b) b 
    

    あなたが実際にそれ以前の引数に応じて、複数の引数を持つ関数にアクセスして操作することができますatを使用します。 Lensはカテゴリです。これは、関数や他のものを局所的に調整するための非常に便利なイディオムです。

    あなたは、プロパティまたは代替表現することによってデータをもアクセスすることができます。

    polar :: (Floating a, RealFloat a) => Lens (Complex a) (a, a) 
    mag :: (RealFloat a) => Lens (Complex a) a 
    

    あなたは、個々のフーリエ変換された信号のバンドとより多くにアクセスするために、さらに書き込みレンズを行くことができます。

    関連する問題