ネストしたレコードの更新(Functional lenses)
下記記事にて、リフレクションを用いてネストしたレコードの更新を実装してみました。
ネストしたレコードの更新 (F#) - 何でもプログラミング
今回はFunctional lensesを利用して同様の内容を実装してみたいと思います。
Lens型定義
Lens型は対象オブジェクトから値を取得するGetと、値を更新した新しいオブジェクトを返すSetからなります。
type Lens<'Type, 'Field> = { Get : 'Type -> 'Field Set : 'Field -> 'Type -> 'Type }
Lens関数群定義
Lensを合成するcomposeが最も重要なものとなります。
module Lens = let create get set = { Get = get; Set = set; } let set (lens : Lens<'a, 'b>) (value : 'b) (object : 'a) = lens.Set value object let update (lens : Lens<'a, 'b>) (f : 'b -> 'b) (object : 'a) = lens.Set (lens.Get object |> f) object let compose (lens1 : Lens<'a, 'b>) (lens2 : Lens<'b, 'c>) = { Get = lens1.Get >> lens2.Get Set = lens2.Set >> update lens1 } // composeを楽にする演算子 let inline (>-) (lens1 : Lens<'a, 'b>) (lens2 : Lens<'b, 'c>) = Lens.compose lens1 lens2
実際の利用
下記のレコードが定義されているとします。
type Container = { Value : int } type Child = { Container : Container } type Root = { Child : Child } let record1 = { Child = { Container = { Value = 100 } } }
まずは各レコードのLensを定義します。
module Container = let value = Lens.create (fun x -> x.Value) (fun x y -> { y with Value = x }) module Child = let container = Lens.create (fun x -> x.Container) (fun x y -> { y with Container = x }) module Root = let child = Lens.create (fun x -> x.Child) (fun x y -> { y with Child = x })
レコードの更新は、下記のように記述します。
現在値を参照する場合はLens.updateを利用します。
let record2 = record1 |> Lens.set (Root.child >- Child.container >- Container.value) 200
リフレクションと比較
リフレクションを利用したほうがLensの定義も必要なく簡潔に記述できます。
一方Lensを利用するとパフォーマンスを期待でき、リフレクションのない言語でも応用可能です。