Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Ekmett勉強会発表資料

980 views

Published on

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

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

  • Be the first to comment

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 を使おう! ご清聴ありがとうございまし た!!

×