Lens で Haskell をもっと格好良く!          for 2013/3/31 ekmett 勉強会                          ちゅーん
私はだあれ?   山手圏内で活動している    下っ端プログラマ   仕事の疲れは Haskell で癒す    日曜 Haskeller   Free モナドとか好きです   あと SDVX とか好き。音ゲーマーは Join me!
本日のメニュー   Lens とは何か   Lens でタプルを便利にする   任意のデータ型を Lens で使う   Lens の仕組ってどーなってんの?   Lens の便利な関数紹介   まとめ的な何か            ...
Lens とは何か
Lens とは・・・ タプルを始めとした任意のデータ構造の要素に対 する Setter や Getter を得るためのライブラリ Haskell で、 Java や C# といったオブジェクト指向 手続きプログラミングに似た記法で、要素にアクセ...
Lens でタプルを便利にする
タプルの要素を取り出す方法   パターンマッチで取得する    f (a, _, _) = a * 2   関数を定義して使う    first   (x, _, _) = x    secound (_, x, _) = x    thi...
ネストした内側の要素を取り出す   パターンマッチするとなんかキモい    全パターン網羅するとか無理ぽ    f ((_, (_, _, x)), _, _) = x   関数合成を使えば綺麗&簡単    third.snd.first ...
任意の位置の値を置き換えるには   パターンマッチを使って関数を書く。めんどい    secondTo999 (x, _, y) = (x, 999, y)   関数を定義する    setFirst x (_, a, b) = (x, a...
ネストした内側の値を変更   素直に関数定義・・・超キモいf x ((a, (b, c, _)), d, e) = ((a, (b, c, x)), d, e)   関数合成ではできないghci> :t setFirst.setThirds...
と に か く 超 不 満   値の取得と変更の識別子が異なる   タプルの要素数が異なると同じ識別子が使えない   構造がネストすると値の設定が超めんどい    Java とか C# のような手続き言語で    public 変数にアク...
と に か く 超 不 満   値の取得と変更の識別子が異なる   タプルの要素数が異なると同じ識別子が使えない   構造がネストすると値の設定が超めんどい       そこで Lens ですよ!!    Java とか C# のような手...
Lens のインストール   Cabal でいっぱつ$ cabal install lens   Hackage から直接アーカイブを取得    http://hackage.haskell.org/package/lens-3.9.0.2...
Lens を import   Haskell のソースコードにImport Control.Lens   あるいは、 ghci で:m Contorol.Lens
Lens で要素の取得    (^.) と _1, _2, _3,... で簡単に取り出し    ("Foo", "Bar", "Buz")^._1 -- => "Foo"    ("Foo", "Bar", "Buz")^._2 -- =>...
ネストしたタプルから要素を取り出す    _1 ~ _9 は (.) で関数合成しても同じ型  ghci> :t _1  _1     :: (Functor f, Field1 s t a b, Indexable Int p) =>   ...
Lens でタプルの値を変更    (.~) に _1 ~ _9 と、任意の値を適用    ghci> :t _2.~"Foo"    _2.~"Foo" :: Field2 s t a [Char] => s -> t    要素が2つ以...
Lens でタプルの値を変更    (.~) に _1 ~ _9 と、任意の値を適用    ghci> :t _2.~"Foo"    _2.~"Foo" :: Field2 s t a [Char] => s -> t    _2.~”F...
Lens でタプルの値を変更   勿論、 _1 ~ _9 を関数合成しても良い    _4._2.~999 $ (1,2,3,(1,2,3),5)                         -- => (1,2,3,(1,999,3),...
Lens でタプルの値を変更   勿論、 _1 ~ _9 を関数合成しても良い    _4._2.~999 $ (1,2,3,(1,2,3),5)                         -- => (1,2,3,(1,999,3),...
ここまでのまとめ    タプルの操作には不満がまんまん    でも Lens を使えば・・・   値の取得も変更も同じ識別子で参照できる   ネストしたタプルの値の変更も    手続き言語の代入感覚でらくらく書ける    それでいてしっか...
任意のデータ型を Lens で使う
Point 型 /Line 型を作る    次のような型を作る    data Point = Point {      x :: Int,      y :: Int      } deriving (Show, Eq)    data L...
Point 単位の操作は簡単   取得    startPoint sampleLine       -- => Point {x = 100, y = 150}    endPoint sampleLine       -- => Poin...
では、座標単位の操作は?    取得は関数合成を使えば良い      x . endPoint $ sampleLine -- => 200    置き換えは・・・いまいち分りづらい    sampleLine {      endPoin...
では、座標単位の操作は?    取得は関数合成を使えば良い      x . endPoint $ sampleLine -- => 200    置き換えは・・・いまいち分りづらい            よし、 Lens を使おう!   ...
Point 型 /Line 型を Lens にする   フィールド名の前に” _” を付加し    『 makeLenses  型名』 と記述する    data Point = Point {      _x :: Int,      _y...
Point 型 /Line 型を Lens にする    フィールド名から” _” を抜いた識別子を使って     (^.) や (.~) で要素にアクセスできるようになる    sampleLine^.startPoint     --  ...
こんな場合はどうなる?   型変数が含まれる型でも    data Foo a = Foo {      _hoge :: a,      _piyo :: Int } deriving (Show, Eq)    makeLenses Fo...
ここまでのまとめ    自分で作った型も Lens で操作したい!    でも型とかややこしそうだし面倒では?   TemplateHaskell の力を借りて    ちょ〜簡単に Lens になるよ   型変数を含む場合も無問題!   ...
Lens の仕組ってどーなってんの?
Setter を作ろう   単純に 2 要素のタプルの 2 つめの要素を任意の    値に置き換える関数を考えると、次のような型にな    る       f :: a -> (b, c) -> (b, a)   これだけではつまらないので...
Setter を作ろう   単純に 2 要素のタプルの 2 つめの要素を任意の    値に置き換える関数を考えると、次のような型にな    る       f :: a -> (b, c) -> (b, a)         値を x に置き...
ところで   この型、何かと似てない?    f :: (a -> b) -> (c, a) -> (c, b)      fmap :: Functor f => (a -> b) -> f a -> f b                ...
ところで   この型、何かと似てない?     f :: (a -> b) -> (c, a) -> (c, b)    ※ 衆知のとおり、 2 値のタプルは Functor になっ    ていて、次のような事ができる    fmap (*2...
fmap のもうひとつの実装    Data.Traversable で定義されている Traversable     型クラスで、次の型を持つ traverse 関数が定義さ     れている    traverse :: Applicat...
fmap のもうひとつの実装    Data.Traversable で定義されている Traversable     型クラスで、次の型を持つ traverse 関数が定義さ     れているData.Functor.Identity の定...
fmapDefault の動作を決めるのは traverse 関数    なら、 traverse を別の関数と差し替えれば別の動     きをするんじゃなイカ?というわけで ...     fmapDefault から traverse を...
fmapDefault の動作を決めるのは traverse 関数    なら、 traverse を別の関数と差し替えれば別の動     きをするんじゃなイカ?というわけで ...     fmapDefault から traverse を...
fmapDefault の動作を決めるのは traverse 関数    なら、 traverse を別の関数と差し替えれば別の動     きをするんじゃなイカ?というわけで ...             これにより、 over の型がこう...
fmapDefault の動作を決めるのは traverse 関数    なら、 traverse を別の関数と差し替えれば別の動     きをするんじゃなイカ?というわけで ...     fmapDefault から traverse を...
fmapDefault の動作を決めるのは traverse 関数    なら、 traverse を別の関数と差し替えれば別の動     きをするんじゃなイカ?というわけで ...     fmapDefault から traverse を...
_1, _2 を実装するには?    最終的に欲しい型    Over _1 ::              (a -> b) -> (a, v) -> (b, v)    over の型を読み替えOver :: Setter (a, v)...
実際にやってみる    導きだした型を満足させるよう _1, _2 を実装_1 :: Setter (a, v) (b, v) a b_1 f (x, y) = Id (getId . f $ x, y)_2 :: Setter (v, a)...
こうなれば後は簡単    (.~) は次のようにして簡単に再実装できる    (.~) :: Setter s t a b -> b -> s -> t    a .~ v = over a (const v)    Lens と同じ書き方...
それじゃぁ次は Getter だ!   2 値のタプルからの値の取得は次のような型をイ    メージできる              f :: (a, b) -> b   単に取り出すだけでなく、何か関数を適用して返す    ようにしてみる...
Traversable の foldMapDefault   FoldMapDefault の定義が Data.Traversable    に!foldMapDefault  :: (Traversable t, Monoid m) => ...
同じようにして traverse を外に出す    foldMapDefault の実装から traverse を取り出し     foldMapOf 関数を定義    foldMapOf      :: ((a1 -> Const b1 b...
アクセサの定義、foldMapOf への適用    改めて、 2 値のタプルに対する _1, _2 を次のよう     に定義    _1 :: Getting r (a, s) a    _1 f (x, _) = Const (getCo...
(^.) の実装も超簡単(^.) :: s -> Getting a s a -> av ^. l = (foldMapOf l) id v           値をそのまま取り出したいのだから          引数に対して何もしない関数 i...
Setter と Getting    どちらも traverse 関数を元に定義された型なのだ     から、揃える事はできないだろうか?    type Getting r s a =                 (a -> Cons...
Setter と Getting    どちらも traverse 関数を元に定義された型なのだ     から、揃える事はできないだろうか?    type Getting r s a =                  型定義に登場しない...
Id も Const も Functor !    従って、次の赤字の部分は、 Functor を要求する     型変数に置き換えることができる    type Getting s t a   b = forall m .Monoid m ...
_1, _2 を作り替える   後は _1 と _2 を、それぞれ Lens 型に合うように実    装    _1 :: Lens (a, v) (b, v) a b    _1 f (x, y) = fmap (x -> (x, y)) ...
traverse. traverse   traverse 関数同士を関数合成するとこうなるtraverse  :: (Control.Applicative.Applicative f, Traversable t) =>     (a -...
Lnes 型の関数は traverse と同じ型    なら _1 や _2 も同じ性質を持っているはず・・・    _2       :: Functor f =>    (a -> f b) -> (v,           a)    ...
そんなワケで    今回再実装したオレオレ Lens も     _1 や _2 を関数合成して、ネストしたタプルの好き     な要素にアクセスできるよっ    _2._1.~"Lens" $ ("Hello", ((), "World")...
(注)   今回の再実装で Id 、 Const という型を使ったが、    実際の Lens の実装では Mutator 、 Accessor と    いう別実装を用いている    これは、型エラーが発生した時に、よりエラーの原    因...
ここまでのまとめ    Lens の仕組みって凄い複雑そう・・・    超人的な知能を持っていないと理解できない    んじゃ?   Traversable 型クラスの fmapDefault 関    数 /foldMapDefault 関...
Lens の便利な関数紹介
と、その前に・・・   Control.Lens モジュール内では、 Lens と同じよう    な型に様々な別名が付けられていて・・・    ・ Lens    ・ Getter    ・ Setter    ・ Fold    ・ Act...
foldMapOf 関数 / over 関数    前の節で実装した foldMapOf 関数と over 関数は     Lens モジュールをインポートしてそのまま使える    (foldMapOf _2) (*100) (1, 2, 3...
to 関数で関数適用   to 関数を使えば、 (^.) で取得した値に対して関数    適用できる    (1, 2, 3)^._2.to (*100) -- => 200   さらに関数合成を連ねて次のようにしても良い    (1,(1...
Setter の演算子色々   対象となる要素が Num 型クラスのインスタンスや    Bool 等の特定の型であれば、それらに対して便利    な演算子を使う事ができる。-- 加算(10, 20)&_1 +~ 100       -- =...
(.=) と use 関数   どちらも型クラス制約に MonadState クラスが含ま    れている。    状態系のモナドと組み合わせて使う関数。ghci> :t (.=)(.=) :: MonadState s m => ASett...
(.=) と use 関数   (.=) や use の簡単な例:    sample :: State Line ()    sample = do      -- (.=) で状態に代入      startPoint .= Point ...
各 Setter 演算子のMonadState バージョン   何処かの言語で見たような書き方ができる    sample2 = do      v %= (*100) --over      v   += 10    -- 加算      ...
Getter のアクセス時にモナドアクションを付加する Action     (^.) の代わりに (^!) を使うと、 act 関数を使ってモ      ナドのアクセサにモナドアクションを追加する事が      できる(("Foo", "B...
などなど   Lens モジュールには Lens をより便利に使う仕組    みが沢山用意されているので、 Hackage を一読し    てみよう!    http://hackage.haskell.org/package/lens-3....
ところで・・・これは何だ・・・?   Lens の構成図を見る    と、さらに下層に ISO    とか Equality とか    ゆー輩がいます…が         Lens        ISO         Equality  ...
まとめ的な何か
改めて Lens って何?   レンズで覗くように複雑なデータ構造の内部に自在    にアクセスする手段を提供してくれるライブラリ軍   仕組みはやや複雑だけど Traversable を起点に    考えていけば考え方はとっても明瞭  ...
大きいライブラリなので尻込みしちゃうかもしれないけど Lens は全然怖くないよ!
!????
(((( ;゚ Д ゚ )))) こ・・・怖くねーしLens 全然怖くねーから・・・
さぁ、みんな Lens を使おう! ご清聴ありがとうございました!!
Ekmett勉強会発表資料
Upcoming SlideShare
Loading in...5
×

Ekmett勉強会発表資料

4,224

Published on

5 Comments
10 Likes
Statistics
Notes
  • とんでもない!ご指摘感謝します!

    ちょっと恥ずかしいですけど、本職の方が多忙で治してる余裕が無いのと、あちこちからリンクが張られていて張り替えていただくのも大変なので、このまま上げておこうと思います。
    スペルミスは恥ずかしいですねw 時間に余裕があったら治したいのですが・・・

    ※Getterという名称は実際のモジュール内に型変数の数が異なる定義があるので悩んだのですが、Githubページwikiの、Derivationにて、Gettingという型名を使って説明していたので、それをそのまま採用しました。

    ※フォントについては、コードの部分はインデントを揃えたかったので、あえて説明文と違うフォントを使っていたのですが、揃えたほうが良かったですかね・・・
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • ちゅーんさんのプロフィールみたら、このような誤りは気にしないんだよという気持ちも伝わりました。一連のコメントが邪魔になるようでしたら遠慮なく言ってください。コメントを消します。細かいところをつついてどうもすみませんでした。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • l と r の間違いはたとえば 77 ページです。Control が正しいのですが、2 カ所どちらも誤ってます。誰が見てもわかるのでどうでもいい誤りなんですけど、日本語でいうと「こんちにわ」と書いていて気にかかるみたいな雰囲気があります。なお、12 ページのスライドの「そこで Lens ですよ」は、皆さんのお気に入りになったことも申し添えておきます。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • 訂正は多数のページにわたるので、要点を列挙します:1. 英単語の l と r が入れ替わっています 2. 単語の始まりが小文字であるべきところが大文字になっています。またその逆の場合もあります 3. p.51 の Getting と Setter は両者が合っていません。Setting にしましょう 4. ~ のフォントの問題 (p.45 を注意深く見てください)
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • すばらしいスライドをありがとうございます。IRC freenode #haskell-lens においても、このスライドを皆で見ました。そして、訂正したほうがよりよい場所を、皆で見つけました。どのように報告したらよいでしょうか。ここにコメントとして残すには長過ぎるので困っています。僕は twitter やってないので…
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total Views
4,224
On Slideshare
0
From Embeds
0
Number of Embeds
9
Actions
Shares
0
Downloads
18
Comments
5
Likes
10
Embeds 0
No embeds

No notes for slide

Ekmett勉強会発表資料

  1. 1. Lens で Haskell をもっと格好良く! for 2013/3/31 ekmett 勉強会 ちゅーん
  2. 2. 私はだあれ? 山手圏内で活動している 下っ端プログラマ 仕事の疲れは Haskell で癒す 日曜 Haskeller Free モナドとか好きです あと SDVX とか好き。音ゲーマーは Join me!
  3. 3. 本日のメニュー Lens とは何か Lens でタプルを便利にする 任意のデータ型を Lens で使う Lens の仕組ってどーなってんの? Lens の便利な関数紹介 まとめ的な何か ※ ゆるふわ注意
  4. 4. Lens とは何か
  5. 5. Lens とは・・・ タプルを始めとした任意のデータ構造の要素に対 する Setter や Getter を得るためのライブラリ Haskell で、 Java や C# といったオブジェクト指向 手続きプログラミングに似た記法で、要素にアクセ スできるようになる
  6. 6. Lens でタプルを便利にする
  7. 7. タプルの要素を取り出す方法 パターンマッチで取得する f (a, _, _) = a * 2 関数を定義して使う first (x, _, _) = x secound (_, x, _) = x third (_, _, x) = x g v = (first v) * 2
  8. 8. ネストした内側の要素を取り出す パターンマッチするとなんかキモい 全パターン網羅するとか無理ぽ f ((_, (_, _, x)), _, _) = x 関数合成を使えば綺麗&簡単 third.snd.first $ ((1, (1, 1, 999)), 1, 1) -- => 999
  9. 9. 任意の位置の値を置き換えるには パターンマッチを使って関数を書く。めんどい secondTo999 (x, _, y) = (x, 999, y) 関数を定義する setFirst x (_, a, b) = (x, a, b) setSecond x (a, _, b) = (a, x, b) setThird x (a, b, _) = (a, b, x) f = setSecond 999 (1, 2, 3) -- => (1,999,3)
  10. 10. ネストした内側の値を変更 素直に関数定義・・・超キモいf x ((a, (b, c, _)), d, e) = ((a, (b, c, x)), d, e) 関数合成ではできないghci> :t setFirst.setThirdsetFirst.setThird :: a -> (t, t2, t3) -> ((t4, t5, t1) -> (t4, t5, a), t2, t3) こうすればちょっとはマシf x t = setThird (setFirst x $ third t) t
  11. 11. と に か く 超 不 満 値の取得と変更の識別子が異なる タプルの要素数が異なると同じ識別子が使えない 構造がネストすると値の設定が超めんどい Java とか C# のような手続き言語で public 変数にアクセスするみたいに もっとスマートに構造を扱う事はできないの?
  12. 12. と に か く 超 不 満 値の取得と変更の識別子が異なる タプルの要素数が異なると同じ識別子が使えない 構造がネストすると値の設定が超めんどい そこで Lens ですよ!! Java とか C# のような手続き言語で public 変数にアクセスするみたいに もっとスマートに構造を扱う事はできないの?
  13. 13. Lens のインストール Cabal でいっぱつ$ cabal install lens Hackage から直接アーカイブを取得 http://hackage.haskell.org/package/lens-3.9.0.2 コンパイルに時間がかかるので、 すごい H 本か TaPL あたりを読んでゆっくり待とう
  14. 14. Lens を import Haskell のソースコードにImport Control.Lens あるいは、 ghci で:m Contorol.Lens
  15. 15. Lens で要素の取得 (^.) と _1, _2, _3,... で簡単に取り出し ("Foo", "Bar", "Buz")^._1 -- => "Foo" ("Foo", "Bar", "Buz")^._2 -- => "Bar" ("Foo", "Bar", "Buz")^._3 -- => "Buz" _1 ~ _9 まで別々の型クラスに定義されているので 要素数が異なるタプルに対しても同じように使える (A, B, C, D, E)^._3 -- => C
  16. 16. ネストしたタプルから要素を取り出す  _1 ~ _9 は (.) で関数合成しても同じ型 ghci> :t _1 _1 :: (Functor f, Field1 s t a b, Indexable Int p) => p a (f b) -> s -> f t ghci> :t _1._2 _1._2 :: (Functor f, Field2 s1 t1 a b, Field1 s t s1 t1, Indexable Int p) => p a (f b) -> s -> f t  _1 ~ _9 を (.) で合成して、ネストした 複雑なタプルの内側の値をピンポイントで取り出し(100, 200, (310, (321, 322, 323, 999, 325), 330), 400)^._3._2._4                                          -- => 999
  17. 17. Lens でタプルの値を変更 (.~) に _1 ~ _9 と、任意の値を適用 ghci> :t _2.~"Foo" _2.~"Foo" :: Field2 s t a [Char] => s -> t 要素が2つ以上のタプルは Field2 型クラス s のインスタンス ghci> :i Field2 class Field2 s t a b | s -> a, t -> b, s b -> t, t a -> s where _2 :: (Indexable Int p, Functor f) => p a (f b) -> s -> f t -- Defined in `Control.Lens.Tuple ... -- Defined in `Control.Lens.Tuple instance Field2 (a, b, c) (a, b, c) b b -- Defined in `Control.Lens.Tuple instance Field2 (a, b) (a, b) b b -- Defined in `Control.Lens.Tuple
  18. 18. Lens でタプルの値を変更 (.~) に _1 ~ _9 と、任意の値を適用 ghci> :t _2.~"Foo" _2.~"Foo" :: Field2 s t a [Char] => s -> t _2.~”Foo” にタプルを適用すると 二つ目の要素が ” Foo” に変更される _2.~"Foo" $ (1, 2) -- => (1,"Foo") _2.~"Foo" $ (1, 2, 3) -- => (1,"Foo",3) _2.~"Foo" $ (1, 2, 3, 4) -- => (1,"Foo",3,4)
  19. 19. Lens でタプルの値を変更 勿論、 _1 ~ _9 を関数合成しても良い _4._2.~999 $ (1,2,3,(1,2,3),5) -- => (1,2,3,(1,999,3),5) ($) の代わりに flip ($) と外延的等価な (&) を使う ghci> :i (&) (&) :: a -> (a -> b) -> b     -- Defined in `Control.Lens.Combinators infixl 1 & (1,2,3,(1,2,3),5)&_4._2 .~ 999 -- => (1,2,3,(1,999,3),5)
  20. 20. Lens でタプルの値を変更 勿論、 _1 ~ _9 を関数合成しても良い _4._2.~999 $ (1,2,3,(1,2,3),5) -- => (1,2,3,(1,999,3),5) ($) の代わりに flip ($) と外延的等価な (&) を使う Java や C# の代入文そっくり! ghci> :i (&) (&) :: a -> (a -> b) -> b     -- Defined in `Control.Lens.Combinators infixl 1 & (1,2,3,(1,2,3),5)&_4._2 .~ 999 -- => (1,2,3,(1,999,3),5)
  21. 21. ここまでのまとめ タプルの操作には不満がまんまん でも Lens を使えば・・・ 値の取得も変更も同じ識別子で参照できる ネストしたタプルの値の変更も 手続き言語の代入感覚でらくらく書ける それでいてしっかり型安全 ( これ重要) タプル以外の型もこんな風にできない?→
  22. 22. 任意のデータ型を Lens で使う
  23. 23. Point 型 /Line 型を作る 次のような型を作る data Point = Point { x :: Int, y :: Int } deriving (Show, Eq) data Line = Line { startPoint :: Point, endPoint :: Point } deriving (Show, Eq) 次の値を例に色々考えてみよう sampleLine = Line { startPoint = Point { x = 100, y = 150 }, endPoint = Point { x = 200, y = 250 } }
  24. 24. Point 単位の操作は簡単 取得 startPoint sampleLine -- => Point {x = 100, y = 150} endPoint sampleLine -- => Point {x = 200, y = 250} 置き換え SampleLine {    endPoint = Point { x = 1000, y = 1500 }}
  25. 25. では、座標単位の操作は? 取得は関数合成を使えば良い x . endPoint $ sampleLine -- => 200 置き換えは・・・いまいち分りづらい sampleLine { endPoint = (endPoint sampleLine) { x = 999 } } -- => Line { startPoint = Point {x = 100, y = 150}, endPoint = Point {x = 999, y = 250}}
  26. 26. では、座標単位の操作は? 取得は関数合成を使えば良い x . endPoint $ sampleLine -- => 200 置き換えは・・・いまいち分りづらい よし、 Lens を使おう! sampleLine { endPoint = (endPoint sampleLine) { x = 999 } } -- => Line { startPoint = Point {x = 100, y = 150}, endPoint = Point {x = 999, y = 250}}
  27. 27. Point 型 /Line 型を Lens にする フィールド名の前に” _” を付加し 『 makeLenses 型名』 と記述する data Point = Point { _x :: Int, _y :: Int } deriving (Show, Eq) makeLenses Point data Line = Line { _startPoint :: Point, _endPoint :: Point } deriving (Show, Eq) makeLenses Line ※ コンパイルのためには GHC 拡張の TemplateHaskell を  有効にしておく必要がある
  28. 28. Point 型 /Line 型を Lens にする フィールド名から” _” を抜いた識別子を使って (^.) や (.~) で要素にアクセスできるようになる sampleLine^.startPoint -- => Point {_x = 100, _y = 150} sampleLine^.endPoint -- => Point {_x = 200, _y = 250} sampleLine^.startPoint.x -- => 100 sampleLine^.endPoint.y -- => 250 sampleLine&startPoint.x.~999 -- => Line { -- _startPoint = Point {_x = 999, _y = 150}, -- _endPoint = Point {_x = 200, _y = 250}} sampleLine&endPoint.x.~999 -- => Line { -- _startPoint = Point {_x = 100, _y = 150}, -- _endPoint = Point {_x = 999, _y = 250}} カッコイイ!!
  29. 29. こんな場合はどうなる? 型変数が含まれる型でも data Foo a = Foo { _hoge :: a, _piyo :: Int } deriving (Show, Eq) makeLenses Foo sampleFoo = Foo { _hoge = "Hello!", _piyo = 100 } もちろん大丈夫☆(ゝ ω ・) v sampleFoo^.hoge -- => "Hello!" sampleFoo^.piyo -- => 100 sampleFoo&hoge.~True -- => Foo {_hoge = True, _piyo = 100} sampleFoo&piyo.~999 -- => Foo {_hoge = "Hello!", _piyo = 999}
  30. 30. ここまでのまとめ 自分で作った型も Lens で操作したい! でも型とかややこしそうだし面倒では? TemplateHaskell の力を借りて ちょ〜簡単に Lens になるよ 型変数を含む場合も無問題! それでいてしっかり型安全 ( 大事なことなのでn (ry いったいどういう仕組みなんだろう?→
  31. 31. Lens の仕組ってどーなってんの?
  32. 32. Setter を作ろう 単純に 2 要素のタプルの 2 つめの要素を任意の 値に置き換える関数を考えると、次のような型にな る f :: a -> (b, c) -> (b, a) これだけではつまらないので、一つ目の引数を関 数で取るようにする f :: (a -> b) -> (c, a) -> (c, b)
  33. 33. Setter を作ろう 単純に 2 要素のタプルの 2 つめの要素を任意の 値に置き換える関数を考えると、次のような型にな る f :: a -> (b, c) -> (b, a) 値を x に置き換えたい場合は const x を適用すれば良い これだけではつまらないので、一つ目の引数を関 数で取るようにする f :: (a -> b) -> (c, a) -> (c, b)
  34. 34. ところで この型、何かと似てない? f :: (a -> b) -> (c, a) -> (c, b) fmap :: Functor f => (a -> b) -> f a -> f b とそっくり・・・
  35. 35. ところで この型、何かと似てない? f :: (a -> b) -> (c, a) -> (c, b) ※ 衆知のとおり、 2 値のタプルは Functor になっ ていて、次のような事ができる fmap (*2) ("Hey!", 5) -- => ("Hey!",10) しかし Functor では一つ目の要素は操作できない さて、どうしよう?
  36. 36. fmap のもうひとつの実装 Data.Traversable で定義されている Traversable 型クラスで、次の型を持つ traverse 関数が定義さ れている traverse :: Applicative f => (a -> f b) -> t a -> f (t b) 同モジュールの fmapDefault 関数は、 traverse 関 数を用いた fmap の別実装 fmapDefault :: Traversable t => (a -> b) -> t a -> t b fmapDefault f = getId . traverse (Id . f)
  37. 37. fmap のもうひとつの実装 Data.Traversable で定義されている Traversable 型クラスで、次の型を持つ traverse 関数が定義さ れているData.Functor.Identity の定義に同じ Id は newtype Id a = Id { getId :: a } traverse :: Applicative f => (a -> f b) -> t a -> f (t b) Functor と Applicative のインスンタンス 同モジュールの fmapDefault 関数は、 traverse 関 数を用いた fmap の別実装 fmapDefault :: Traversable t => (a -> b) -> t a -> t b fmapDefault f = getId . traverse (Id . f)
  38. 38. fmapDefault の動作を決めるのは traverse 関数 なら、 traverse を別の関数と差し替えれば別の動 きをするんじゃなイカ?というわけで ... fmapDefault から traverse を外出しした over 関数 を定義すると、次のような型になる over :: ((a1 -> Id b) -> a -> Id c) -> (a1 -> b) -> a -> c over l f = getId . l (Id . f) ここで、第一引数の型に対し Setter という別名を 付けよう type Setter s t a b = (a -> Id b) -> s -> Id t
  39. 39. fmapDefault の動作を決めるのは traverse 関数 なら、 traverse を別の関数と差し替えれば別の動 きをするんじゃなイカ?というわけで ... fmapDefault から traverse を外出しした over 関数 を定義すると、次のような型になる over :: Setter a c a1 b -> (a1 -> b) -> a -> c over l f = getId . l (Id . f) ここで、第一引数の型に対し Setter という別名を 付けよう type Setter s t a b = (a -> Id b) -> s -> Id t
  40. 40. fmapDefault の動作を決めるのは traverse 関数 なら、 traverse を別の関数と差し替えれば別の動 きをするんじゃなイカ?というわけで ... これにより、 over の型がこう書ける fmapDefault から traverse を外出しした over 関数 を定義すると、次のような型になる over :: Setter a c a1 b -> (a1 -> b) -> a -> c over l f = getId . l (Id . f) ここで、第一引数の型に対し Setter という別名を 付けよう type Setter s t a b = (a -> Id b) -> s -> Id t
  41. 41. fmapDefault の動作を決めるのは traverse 関数 なら、 traverse を別の関数と差し替えれば別の動 きをするんじゃなイカ?というわけで ... fmapDefault から traverse を外出しした over 関数 を定義すると、次のような型になる over :: Setter a c a1 b -> (a1 -> b) -> a -> c over l f = getId . l (Id . f) ここで、第一引数の型に対し Setter という別名を 付けよう type Setter s t a b = (a -> Id b) -> s -> Id t
  42. 42. fmapDefault の動作を決めるのは traverse 関数 なら、 traverse を別の関数と差し替えれば別の動 きをするんじゃなイカ?というわけで ... fmapDefault から traverse を外出しした over 関数 を定義すると、次のような型になる over :: Setter a c a1 b -> (a1 -> b) -> a -> c over l f = getId . l (Id . f) ここで、第一引数の型に対し Setter という別名を 当然、 traverse 関数を適用すれば 付けよう fmapDefault と同値になる さらに何かしら Setter 型の関数を引数に取る事により type Setter s t a b = (a -> Id b) -> s -> Id t fmap と似た別の関数を得る事ができる
  43. 43. _1, _2 を実装するには? 最終的に欲しい型 Over _1 :: (a -> b) -> (a, v) -> (b, v) over の型を読み替えOver :: Setter (a, v) (b, v) a b -> (a -> b) -> (a, v) -> (b, v) _1 の型_1 :: Setter (a, v) (b, v) a b -- つまり_1 :: (a -> Id b) -> (a, v) -> Id (b, v)
  44. 44. 実際にやってみる 導きだした型を満足させるよう _1, _2 を実装_1 :: Setter (a, v) (b, v) a b_1 f (x, y) = Id (getId . f $ x, y)_2 :: Setter (v, a) (v, b) a b_2 f (x, y) = Id (x, getId . f $ y) 任意の要素に fmap できるようになる! (over _1) (*2) (50, 50) -- => (100,50) (over _2) (*2) (50, 50) -- => (50,100)
  45. 45. こうなれば後は簡単 (.~) は次のようにして簡単に再実装できる (.~) :: Setter s t a b -> b -> s -> t a .~ v = over a (const v) Lens と同じ書き方で要素を変更できるようになる _1.~999 $ (1, 2) => (999,2) _2.~999 $ (1, 2) => (1,999)
  46. 46. それじゃぁ次は Getter だ! 2 値のタプルからの値の取得は次のような型をイ メージできる f :: (a, b) -> b 単に取り出すだけでなく、何か関数を適用して返す ようにしてみると・・・ f :: (a -> b) -> (c, a) -> b これは Data.Foldable で定義されている foldMap 関数とそっくりfoldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m
  47. 47. Traversable の foldMapDefault FoldMapDefault の定義が Data.Traversable に!foldMapDefault :: (Traversable t, Monoid m) => (a -> m) -> t a -> mfoldMapDefault f = getConst . traverse (Const . f) Const は Data.Functor.Constant に定義 newtype Const a b = Const {getConst :: a} Foldable や Applicative 等のインスンタンス
  48. 48. 同じようにして traverse を外に出す foldMapDefault の実装から traverse を取り出し foldMapOf 関数を定義 foldMapOf :: ((a1 -> Const b1 b2) -> a -> Const c b) -> (a1 -> b1) -> a -> c foldMapOf l f = getConst . l (Const . f) 第一引数の関数の型に別名を付けてみる type Getting r s a = (a -> Const r a) -> s -> Const r s
  49. 49. アクセサの定義、foldMapOf への適用 改めて、 2 値のタプルに対する _1, _2 を次のよう に定義 _1 :: Getting r (a, s) a _1 f (x, _) = Const (getConst . f $ x) _2 :: Getting r (s, a) a _2 f (_, y) = Const (getConst . f $ y) foldMapOf と組み合わせて任意の場所の要素を foldMap (foldMapOf _1) (*2) (100, 1000) -- => 200 (foldMapOf _2) (*2) (100, 1000) -- => 2000
  50. 50. (^.) の実装も超簡単(^.) :: s -> Getting a s a -> av ^. l = (foldMapOf l) id v 値をそのまま取り出したいのだから 引数に対して何もしない関数 id :: a -> a を、適用してやれば良い(111, 222)^._1 -- => 111(111, 222)^._2 -- => 222
  51. 51. Setter と Getting どちらも traverse 関数を元に定義された型なのだ から、揃える事はできないだろうか? type Getting r s a = (a -> Const r a) -> s -> Const r s type Setter s t a b = (a -> Id b) -> s -> Id t Getting の型変数を Setter に合わせて変えてみるtype Getting s t a b = forall m .Monoid m => (a -> Const m b) -> s -> Const m ttype Setter s t a b = (a -> Id b) -> s -> Id t
  52. 52. Setter と Getting どちらも traverse 関数を元に定義された型なのだ から、揃える事はできないだろうか? type Getting r s a = 型定義に登場しない型変数 m (a -> Const r a) -> s -> Const r s type Setter s t a b = コンパイルのため、 FoldMap の実装に合せ (a -> Id b) -> s -> Id t Monoid を要求するようにしておく でもあまり嬉しくない制約 Getting の型変数を Setter に合わせて変えてみるtype Getting s t a b = forall m .Monoid m => (a -> Const m b) -> s -> Const m ttype Setter s t a b = (a -> Id b) -> s -> Id t
  53. 53. Id も Const も Functor ! 従って、次の赤字の部分は、 Functor を要求する 型変数に置き換えることができる type Getting s t a b = forall m .Monoid m => (a -> Const m b) -> s -> Const m t type Setter s t a b = (a -> Id b) -> s -> Id t これで型宣言も一つに纏められる しかも Getter の Monoid も消えた!やったね! type Lens s t a b = forall f .Functor f => (a -> f b) -> s -> f t
  54. 54. _1, _2 を作り替える 後は _1 と _2 を、それぞれ Lens 型に合うように実 装 _1 :: Lens (a, v) (b, v) a b _1 f (x, y) = fmap (x -> (x, y)) (f x) _2 :: Lens (v, a) (v, b) a b _2 f (x, y) = fmap (y -> (x, y)) (f y) ちゃんと使えるかどうか確認・・・バッチリ! (100, 200)^._1 -- => 100 _1.~999 $ (100, 200) -- => (999,200)
  55. 55. traverse. traverse traverse 関数同士を関数合成するとこうなるtraverse :: (Control.Applicative.Applicative f, Traversable t) => (a -> f b) -> t a -> f (t b)traverse.traverse :: (Control.Applicative.Applicative f, Traversable t, Traversable t1) => (a -> f b) -> t (t1 a) -> f (t (t1 b))traverse.traverse.traverse :: (Control.Applicative.Applicative f, Traversable t, Traversable t1, Traversable t2) => (a -> f b) -> t (t1 (t2 a)) -> f (t (t1 (t2 b))) 合成しても性質が維持される!
  56. 56. Lnes 型の関数は traverse と同じ型 なら _1 や _2 も同じ性質を持っているはず・・・ _2 :: Functor f => (a -> f b) -> (v, a) -> f (v, b) _2._2 :: Functor f => (a -> f b) -> (v, (v1, a)) -> f (v, (v1, b)) _2._2._2 :: Functor f => (a -> f b) -> (v, (v1, (v2, a))) -> f (v, (v1, (v2, b))) Lens が関数合成して使えるのは 型を見れば当然の事だった!!
  57. 57. そんなワケで 今回再実装したオレオレ Lens も _1 や _2 を関数合成して、ネストしたタプルの好き な要素にアクセスできるよっ _2._1.~"Lens" $ ("Hello", ((), "World")) -- => ("Hello",("Lens","World")) ("Hello", ("Lens", "World"))^._2._1 -- => "Lens"
  58. 58. (注) 今回の再実装で Id 、 Const という型を使ったが、 実際の Lens の実装では Mutator 、 Accessor と いう別実装を用いている これは、型エラーが発生した時に、よりエラーの原 因を特定しやすくするため。
  59. 59. ここまでのまとめ Lens の仕組みって凄い複雑そう・・・ 超人的な知能を持っていないと理解できない んじゃ? Traversable 型クラスの fmapDefault 関 数 /foldMapDefault 関数から、型を中心に 追っていけば自然と導き出せるよ! もっと Lens の事が知りたいな!→
  60. 60. Lens の便利な関数紹介
  61. 61. と、その前に・・・ Control.Lens モジュール内では、 Lens と同じよう な型に様々な別名が付けられていて・・・ ・ Lens ・ Getter ・ Setter ・ Fold ・ Action ... 等々 それぞれ型クラスの制約なんかが少しづつ違って いたりするので、必要に応じて Hackage を参照
  62. 62. foldMapOf 関数 / over 関数 前の節で実装した foldMapOf 関数と over 関数は Lens モジュールをインポートしてそのまま使える (foldMapOf _2) (*100) (1, 2, 3) --=> 200 (over _2) (*100) (1, 2, 3) -- => (1,200,3) (foldMapOf y) (*2) $ Point { _x = 100, _y = 200 } -- => 400 (over x) (*2) $ Point { _x = 100, _y = 200 } -- => Point {_x = 200, _y = 200} %~ は over の中置バージョン_2 %~ (*100) $ (1, 2, 3) -- => (1,200,3)
  63. 63. to 関数で関数適用 to 関数を使えば、 (^.) で取得した値に対して関数 適用できる (1, 2, 3)^._2.to (*100) -- => 200 さらに関数合成を連ねて次のようにしても良い (1,(10,20),3)^._2.to swap._2.to (*100)                          -- => 1000
  64. 64. Setter の演算子色々 対象となる要素が Num 型クラスのインスタンスや Bool 等の特定の型であれば、それらに対して便利 な演算子を使う事ができる。-- 加算(10, 20)&_1 +~ 100 -- => (110,20)-- 減算(10, 20)&_1 -~ 5 -- => (5,20)-- 乗算(10, 20)&_1 *~ 100 -- => (1000,20)-- 除算(10, 20)&_1 //~ 5 -- => (2.0,20)--AND(True, 1)&_1 &&~ False -- => (False,1)--OR(True, 1)&_1 ||~ False -- => (True,1)
  65. 65. (.=) と use 関数 どちらも型クラス制約に MonadState クラスが含ま れている。 状態系のモナドと組み合わせて使う関数。ghci> :t (.=)(.=) :: MonadState s m => ASetter s s a b -> b -> m ()ghci> :t useuse :: MonadState s m => Getting a s t a b -> m a
  66. 66. (.=) と use 関数 (.=) や use の簡単な例: sample :: State Line () sample = do -- (.=) で状態に代入 startPoint .= Point { _x = 100, _y = 200 } endPoint.x .= 300 -- 状態から値を取り出し sp <- use startPoint epx <- use $ endPoint.x return ()
  67. 67. 各 Setter 演算子のMonadState バージョン 何処かの言語で見たような書き方ができる sample2 = do v %= (*100) --over v += 10 -- 加算 v -= 10 -- 減算 v *= 10 -- 乗算 v //= 10 -- 除算 b ||= True --OR b &&= True --AND ※ 「状態」に対してかなりやりたい放題できるように なるので乱用注意!
  68. 68. Getter のアクセス時にモナドアクションを付加する Action  (^.) の代わりに (^!) を使うと、 act 関数を使ってモ ナドのアクセサにモナドアクションを追加する事が できる(("Foo", "Bar"), "Buz")^!_1.act (Just)._2 -- => Just "Bar"(("Lens", "Hello"), "World!")^!_1.act (x -> print x >> return x).to swap -- => ("Hello","Lens") ※ 途中で ("Lens","Hello") を print  次は Nothing になるかと思ったけど、 No instance エラーになった・・・ (´ ・ ω ・ `) ? (("Foo", "Bar"), "Buz")^!_1.act (const Nothing)._2
  69. 69. などなど Lens モジュールには Lens をより便利に使う仕組 みが沢山用意されているので、 Hackage を一読し てみよう! http://hackage.haskell.org/package/lens-3.9.0.2
  70. 70. ところで・・・これは何だ・・・? Lens の構成図を見る と、さらに下層に ISO とか Equality とか ゆー輩がいます…が Lens ISO Equality 勉強不足でご紹介で きないです orz ゴメンナサイ
  71. 71. まとめ的な何か
  72. 72. 改めて Lens って何? レンズで覗くように複雑なデータ構造の内部に自在 にアクセスする手段を提供してくれるライブラリ軍 仕組みはやや複雑だけど Traversable を起点に 考えていけば考え方はとっても明瞭 単なるアクセサの域を超えた自在な操作を可能に するスーパーウルトラアルティメットアクセサ もうちょっと秘められたパワーがありそうです
  73. 73. 大きいライブラリなので尻込みしちゃうかもしれないけど Lens は全然怖くないよ!
  74. 74. !????
  75. 75. (((( ;゚ Д ゚ )))) こ・・・怖くねーしLens 全然怖くねーから・・・
  76. 76. さぁ、みんな Lens を使おう! ご清聴ありがとうございました!!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×