Ekmett勉強会発表資料

891 views
812 views

Published on

2013/3/31 ekmett勉強会発表資料

中で紹介されているURLのバージョンが古かったり治ったり(?)してるのはご愛嬌

0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
891
On SlideShare
0
From Embeds
0
Number of Embeds
57
Actions
Shares
0
Downloads
5
Comments
0
Likes
1
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. と に か く 超 不 満 値の取得と変更の識別子が異なる タプルの要素数が異なると同じ識別子が使えな い 構造がネストすると値の設定が超めんどい そこで Lens ですよ!! Java とか C# のような手続き言語で public 変数にアクセスするみたいに もっとスマートに構造を扱う事はできないの?
  12. 12. Lens のインストール Cabal でいっぱつ$ cabal install lens Hackage から直接アーカイブを取得 http://hackage.haskell.org/package/lens-3.8.7.3 コンパイルに時間がかかるので、 すごい H 本か TaPL あたりを読んでゆっくり待 とう
  13. 13. Lens を import Haskell のソースコードにImport Control.Lens あるいは、 ghci で:m Contorol.Lens
  14. 14. 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 える
  15. 15. ネストしたタプルから要素を取り出 す  _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 を (.) で合成して、ネストした 複雑なタプルの内側の値をピンポイントで取り 出し, (310, (321, 322, 323, 999, 325), 330), 400)^._3._2._4                                     
  16. 16. 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
  17. 17. 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)
  18. 18. 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)
  19. 19. ここまでのまとめ タプルの操作には不満がまんまん でも Lens を使えば・・・ 値の取得も変更も同じ識別子で参照できる ネストしたタプルの値の変更も 手続き言語の代入感覚でらくらく書ける それでいてしっかり型安全 ( これ重要) タプル以外の型もこんな風にできない?→
  20. 20. 任意のデータ型を Lens で使う
  21. 21. 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 } }
  22. 22. Point 単位の操作は簡単 取得 startPoint sampleLine -- => Point {x = 100, y = 150} endPoint sampleLine -- => Point {x = 200, y = 250} 置き換え SampleLine {    endPoint = Point { x = 1000, y = 1500 }}
  23. 23. では、座標単位の操作は? 取得は関数合成を使えば良い x . endPoint $ sampleLine -- => 200 置き換えは・・・いまいち分りづらい よし、 Lens を使おう! sampleLine { endPoint = (endPoint sampleLine) { x = 999 } } -- => Line { startPoint = Point {x = 100, y = 150}, endPoint = Point {x = 999, y = 250}}
  24. 24. 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 を  有効にしておく必要がある
  25. 25. 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}} カッコイイ!!
  26. 26. こんな場合はどうなる? 型変数が含まれる型でも 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}
  27. 27. ここまでのまとめ 自分で作った方も Lens で操作したい! でも型とかややこしそうだし面倒では? TemplateHaskell の力を借りて ちょ〜簡単に Lens になるよ 型変数を含む場合も無問題! それでいてしっかり型安全 ( 大事なことなのでn (ry いったいどういう仕組みなんだろう?→
  28. 28. Lens の仕組ってどーなってんの?
  29. 29. Setter を作ろう 単純に 2 要素のタプルの 2 つめの要素を任意の 値に置き換える関数を考えると、次のような型 になる f :: a -> (b, c) -> (b, a) 値を x に置き換えたい場合は const x を適用すれば良い これだけではつまらないので、一つ目の引数を 関数で取るようにする f :: (a -> b) -> (c, a) -> (c, b)
  30. 30. ところで この型、何かと似てない? f :: (a -> b) -> (c, a) -> (c, b) fmap :: Functor f => (a -> b) -> f a -> f b とそっくり・・・
  31. 31. ところで この型、何かと似てない? f :: (a -> b) -> (c, a) -> (c, b) ※ 衆知のとおり、 2 値のタプルは Functor に なっていて、次のような事ができる fmap (*2) ("Hey!", 5) -- => ("Hey!",10) しかし Functor では一つ目の要素は操作できな い
  32. 32. fmap のもうひとつの実装 Data.Traversable で定義されている Traversable 型クラスで、次の型を持つ Id は 関数が定義されている traverseData.Functor.Identity の定義に同じ 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)
  33. 33. 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
  34. 34. 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
  35. 35. 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 と似た別の関数を得る事ができる
  36. 36. _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)
  37. 37. 実際にやってみる 導きだした型を満足させるよう _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)
  38. 38. こうなれば後は簡単 (.~) は次のようにして簡単に再実装できる (.~) :: 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)
  39. 39. それじゃぁ次は 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
  40. 40. 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 等のインスンタンス
  41. 41. 同じようにして 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
  42. 42. アクセサの定義、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
  43. 43. (^.) の実装も超簡単(^.) :: s -> Getting a s a -> av ^. l = (foldMapOf l) id v 値をそのまま取り出したいのだから 引数に対して何もしない関数 id :: a -> a を、適用してやれば良い(111, 222)^._1 -- => 111(111, 222)^._2 -- => 222
  44. 44. 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 ->Monoid を要求するようにしておく 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
  45. 45. 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
  46. 46. _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)
  47. 47. 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))) 合成しても性質が維持される!
  48. 48. 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 が関数合成して使えるのは 型を見れば当然の事だった!!
  49. 49. そんなワケで 今回再実装したオレオレ Lens も _1 や _2 を関数合成して、ネストしたタプルの 好きな要素にアクセスできるよっ _2._1.~"Lens" $ ("Hello", ((), "World")) -- => ("Hello",("Lens","World")) ("Hello", ("Lens", "World"))^._2._1 -- => "Lens"
  50. 50. (注) 今回の再実装で Id 、 Const という型を使った が、 実際の Lens の実装では Mutator 、 Accessor と いう別実装を用いている これは、型エラーが発生した時に、よりエラー の原因を特定しやすくするため。
  51. 51. ここまでのまとめ Lens の仕組みって凄い複雑そう・・・ 超人的な知能を持っていないと理解でき ないんじゃ? Traversable 型クラスの fmapDefault 関 数 /foldMapDefault 関数から、型を中心に 追っていけば自然と導き出せるよ! もっと Lens の事が知りたいな!→
  52. 52. Lens の便利な関数紹介
  53. 53. と、その前に・・・ Control.Lens モジュール内では、 Lens と同じ ような型に様々な別名が付けられていて・・・ ・ Lens ・ Getter ・ Setter ・ Fold ・ Action ... 等々 それぞれ型クラスの制約なんかが少しづつ違っ ていたりするので、必要に応じて Hackage を
  54. 54. foldMapOf 関数 / over 関数 前の節で実装した foldMapOf 関数と over 関数 は Lens モジュールをインポートしてそのまま使 (foldMapOf _2) (*100) (1, 2, 3) --=> 200 える (*100) (1, 2, 3) -- => (1,200,3) (over _2) (foldMapOf y) (*2) $ Point { _x = 100, _y = 200 } -- => 400 (over x) (*2) $ Point { _x = 100, _y = 200 } -- => Point {_x = 200, _y = 200}_2 %~ (*100) $ (1, 2, 3) -- => (1,200,3) %~ は over の中置バージョン
  55. 55. to 関数で関数適用  to 関数を使えば、 (^.) で取得した値に対して関 数適用できる (1, 2, 3)^._2.to (*100) -- => 200  さらに関数合成を連ねて次のようにしても良い(10,20),3)^._2.to swap._2.to (*100)                        -- =>
  56. 56. 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)
  57. 57. (.=) と 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
  58. 58. (.=) と use 関数 (.=) や use の簡単な例: sample :: State Line () sample = do -- (.=) で状態に代入 startPoint .= Point { _x = 100, _y = 200 } endPoint.x .= 300 -- 状態から値を取り出し sp <- use startPoint epx <- use $ endPoint.x return ()
  59. 59. 各 Setter 演算子のMonadState バージョン 何処かの言語で見たような書き方ができる sample2 = do v %= (*100) --over v += 10 -- 加算 v -= 10 -- 減算 v *= 10 -- 乗算 v //= 10 -- 除算 b ||= True --OR b &&= True --AND ※ 「状態」に対してかなりやりたい放題できる ようになるので乱用注意!
  60. 60. 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
  61. 61. などなど Lens モジュールには Lens をより便利に使う仕 組みが沢山用意されているので、 Hackage を 一読してみよう! http://hackage.haskell.org/package/lens-3.9.0.2
  62. 62. ところで・・・これは何だ・・・? Lens の構成図を見 ると、さらに下層に ISO とか Equality と かゆー輩がいます… が Lens ISO Equality 勉強不足でご紹介で きないです orz
  63. 63. まとめ的な何か
  64. 64. 改めて Lens って何? レンズで覗くように複雑なデータ構造の内部に 自在にアクセスする手段を提供してくれるライ ブラリ軍 仕組みはやや複雑だけど Traversable を起点に 考えていけば考え方はとっても明瞭 単なるアクセサの域を超えた自在な操作を可能 にするスーパーウルトラアルティメットアクセ サ もうちょっと秘められたパワーがありそうです
  65. 65. 大きいライブラリなので尻込みしちゃうかもしれないけど Lens は全然怖くないよ!
  66. 66. !????
  67. 67. (((( ;゚ Д ゚ )))) こ・・・怖くねーし Lens 全然怖くねーから・・・
  68. 68. さぁ、みんな Lens を使おう! ご清聴ありがとうございまし た!!

×