Arrowの基本(2) 関数を並列に繋ぐ ***、&&& 演算子
前回からの変更点:
実際の実装に合わせて、各MyArrowクラスから関数を取り出す関数は
MyArrow型クラスに定義したrunArrでは無く、各型定義のアクセサ関数を使うようにします。module Main where --Arrow型クラス class MyArrow a where myarr :: (b -> c) -> a b c (-->) :: a b c -> a c d -> a b d --関数にMyArrowを実装 instance MyArrow (->) where myarr f = f f --> g = g.f --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
前回のエントリで、 >>>演算子を真似た -->演算子を定義する事で、Arrowが関数を繋ぐ動きを追っていきました。
今回は同じように、関数を並列に繋ぐための ***演算子、 &&&演算子の動作を再実装していきます。
Prelude Control.Arrow> :i (***) class (Control.Category.Category a) => Arrow a where ... (***) :: a b c -> a b' c' -> a (b, b') (c, c') ... -- Defined in Control.Arrow infixr 3 *** Prelude Control.Arrow> :i (&&&) class (Control.Category.Category a) => Arrow a where ... (&&&) :: a b c -> a b c' -> a b (c, c') -- Defined in Control.Arrow infixr 3 &&&
「a b c」という型は、(b -> c)という関数を包んだArrow型クラスのインスタンス aという型を表していました。
同様に、***演算子の返り値は、( (b, b') -> (c, c') )という型の関数・・・つまりタプルで分けられた二つの値に対して、それぞれの関数( (b -> c) と (b' -> c') )を適用する関数を保持した、Arrow型のデータである事がわかります。
&&&演算子は一つの値を受け取り、それをタプルに分けてそれぞれの関数に渡す関数を返します。
つまり、
f *** g が (\(x,y) -> (f x, g y))となる関数を作るのに対して、 f &&& g は (\x -> (f x, g x)) を作るわけです。
では ***演算子を真似た *-*演算子
&&&演算子を真似た &-&演算子
を、それぞれ定義していきます。
infixr 1 --> infixr 3 *-* infixr 3 &-& class MyArrow a where ... (*-*) :: a b c -> a b' c' -> a (b,b') (c,c') (&-&) :: a b c -> a b c' -> a b (c,c')
前回作ったFoo型に、これらの関数を実装すると、次のようになります
data Foo a b = Foo { runFoo :: (a -> b) } instance MyArrow Foo where ... Foo f *-* Foo g = Foo $ \(x,y) -> (f x,g y) Foo f &-& Foo g = Foo $ \x -> (f x,g x)
ghciで実行してみます。
*Main> runFoo (myarr (+1) *-* myarr (+2)) $ (1,1) (2,3) *Main> runFoo (myarr (*2) &-& myarr (*3)) $ 2 (4,6)
実際に、左右のタプル別々の関数が適用されているのがわかりますね。
もちろん、これをさらに --> 演算子で繋ぐ事もできます。
*Main> runFoo (myarr (+1) *-* myarr (+2) --> myarr show --> myarr ("res = "++) --> myarr putStrLn) $ (1,1) res = (2,3)
関数を並行に繋ぐというのが、どういう事かわかった所で前回同様、関数にMyArrow型を実装してみましょう。
instance MyArrow (->) where ... f *-* g = \(x,y) -> (f x,g y) f &-& g = \x -> (f x,g x)
実行結果。
Main> (*2)&-&(*3) --> ("res = "++).show --> putStrLn $ 2 res = (4,6)
最後に、ここまで書いたコードが、ちゃんと本来のArrowを再現したものになっているかどうか確認して、このエントリを終わろうと思います。
Prelude Control.Arrow> (+1)***(+2) $ (1,1) (2,3) Prelude Control.Arrow> (*2)&&&(*3) >>> ("res = "++).show >>> putStrLn $ 2 res = (4,6)
とても直感的に関数を組み合わせていけます。Arrowすごい!