この広告は、90日以上更新していないブログに表示しています。
前々から気になっていたArrowを覚えるべく、あちこち資料を探してみたものの、そもそも日本語の資料が少ないので。自分で色々いじって試してみる事にしました。
一つの日本語資料として活用できるように、手順を追って何回かに別けてなるべく詳細に書いていこうと思います。
自分も勉強しながらになりますので、足りない部分ありましたら指摘いただければ幸いです。
まず、Arrowの、関数をパイプ感覚で繋いでいく(事ができるらしい)>>>演算子の定義は次のようになってます。
Prelude Control.Arrow> :i (>>>)(>>>) :: (Control.Category.Category cat) => cat a b -> cat b c -> cat a c -- Defined in Control.Categoryinfixr 1 >>>
ごちゃごちゃして分かりづらいですが、必要な部分だけゆっくりと汲みとっていきましょう。
ここに書かれているControl.Category.Cateogoryというのは、Control.Categoryモジュールで定義されいる型クラスです。
Arrowの話になると必ず出てくる >>> ですが、実はCategoryクラスで定義されてるんですね。
>>> が関数をつなぐ演算子であるという前提知識から、「cat a b」という記述は、cat型が(a -> b)という関数を格納するデータ型であるという事がわかります。
その事から 「cat a b -> cat b c -> cat a c」という定義は、 >>> が (a -> b)という関数と、(b -> c)という関数を合成して、(a -> c)という関数を返す演算子であると予想させてくれます。
実際に、>>> の定義を元に、同じような動作をする(であろう)MyArrowを定義しながら、(>>>)の実装を再現してみる事にしましょう。
--(a -> b)という関数を格納するdata MyArrow a b = MyArrow { runArr :: (a -> b) }
ghciで実行してみます。
*Main> let myArrInc = MyArrow (\x -> x + 1)*Main> :t myArrIncmyArrInc :: MyArrow Integer Integer
MyArrow が (Integer -> Integer) という関数を格納できた事が確認できました。
ラベルrunArrで、この関数を取り出せます。
*Main> :t runArr myArrIncrunArr myArrInc :: Integer -> Integer*Main> runArr myArrInc $ 56*Main> runArr myArrInc $ 910
次に、MyArrowに関数を格納するmyarr関数を定義します。
--関数をMyArrow型に格納するmyarr :: (a -> b) -> MyArrow a bmyarr f = MyArrow f
これは、Arrowの実際のarrに対応させたもので、関数をMyArrow型のコンテナに入れるだけなので今はあまり意味は無いですが、型クラス関数にする事でポリモーフィズムが生まれます。
そしてもう一つ、>>> に対応する -->演算子を定義しましょう。
--関数を結合する(-->) :: MyArrow a b -> MyArrow b c -> MyArrow a cfa --> fb = myarr $ runArr fb . runArr fa
型定義を見ると、>>>の定義、cat a b -> cat b c -> cat a c とよく似ているのが解ると思います。
では、ここまで書いたコードを実際に実行してみましょう。
MyArrowから、関数そのものをrunArrで取り出し、値を適用しています。
*Main> let triInc = myarr (+1) --> myarr (+1) --> myarr (+1)*Main> let incShow = myarr (+1) --> myarr show*Main> runArr triInc $ 58*Main> runArr incShow $ 5"6"
class MyArrow a where myarr :: (b -> c) -> a b c (-->) :: a b c -> a c d -> a b d runArr :: a b c -> (b -> c)
ラベルrunArrはそのままの意味で使えるよう、一旦型クラスの関数としておきます。
このMyArrowクラスを実装したFooという型を定義してみましょう。
data Foo a b = Foo { runFoo :: (a -> b) }instance MyArrow Foo where myarr f = Foo f f --> g = Foo $ runFoo g . runFoo f runArr f = runFoo f--値を+1する関数をFooに包んで返すfooInc = Foo (+1)
Foo型の動作を簡潔に確認するために、(+1)をFoo型のコンテナに包んだfooInc関数を定義しておきました、実行してみましょう。
*Main> let fooDblInc = fooInc --> fooInc*Main> runArr fooDblInc $ 57*Main> let fooIncShow = fooInc --> myarr show*Main> runArr fooIncShow $ 5"6"*Main> runArr (fooInc --> fooInc --> myarr show --> myarr ("val = "++)) $ 5"val = 7"
この例では、show関数はFoo型では無いため、myarr関数でFooに包んでいますが、これでポリモーフィズムが正しく機能している事も確認できます。
Prelude Control.Arrow> (+1) >>> (+1) >>> show >>> ("val = "++) $ 5"val = 7"
これはどういう事かと思ったのですが、実はプリミティブで関数そのものを表す(->)という型が定義されていて、これをインスタンス化し拡張する事で、関数に新たな定義を追加する事ができます。
Prelude Control.Arrow> :i (->)data (->) a b -- Defined in GHC.Priminstance Monad ((->) r) -- Defined in Control.Monad.Instancesinstance Functor ((->) r) -- Defined in Control.Monad.Instancesinstance ArrowLoop (->) -- Defined in Control.Arrowinstance ArrowApply (->) -- Defined in Control.Arrowinstance ArrowChoice (->) -- Defined in Control.Arrowinstance Arrow (->) -- Defined in Control.Arrow
この定義を見てみると、関数はArrowのインスタンスである事がわかります。
つまり、(->)に型クラスMyArrowを実装する事で、関数そのものを --> でつなぐ事できそうです。
instance MyArrow (->) where myarr f = f f --> g = g.f runArr f = f
実行結果
*Main> (+1) --> (+1) --> show --> ("val = "++) $ 5"val = 7"
めでたく、 >>> の動作を再現する事ができました!
ここまでのメリットは、(.)の逆をやる事によって、場合によって見やすくなるだけなんですけど、MyArrowのインスタンスの定義次第では、 --> にちょっとしたからくりを仕込んだりする事ができたりします。
その他、結局どう嬉しいの?とか、Arrowって他に変な演算子色々あるよね?っていう話を、MyArrowを実際のArrowを真似て拡張しつつ、のんびり書いていこうと思います。
参考資料:
3分で解るHaskellのArrowの基本メモ
http://d.hatena.ne.jp/r-west/20070529/1180455881
Haskell/Understanding arrows
http://en.wikibooks.org/wiki/Haskell/Understanding_arrows
2011/1/11:文言等、何箇所か修正
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。