これらは、データの更新よりもはっきりとした抽象化を提供し、決して本当に必要なものではありません。彼らはちょうどあなたが別の方法で問題について理由を挙げさせる。
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つのものをグループ化すると、ゲッターが属するデータ構造を持つ対応する関数を持つ値(例えば、vecX
とmatCol2
など)の「ゲッター」その値を変更して新しいデータ構造を作成すると、たくさんのすてきなことができます。例:
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。あるタイプのゲッターとセッターの組み合わせで、タイプがb
のa
のため、上記のmemberLens
はLens 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を参照してください。
あなたはエドワード・クメットの「レンズ:機能的要望」(http://www.youtube.com/watch?v=efv0SQNde5Q)の話を楽しむかもしれません。これはScalaで紹介されていますが、Haskellでのレンズの有用性への翻訳ははっきりしているはずです。 –