zipを考える

Haskellのzip,zipWith関数はこんな感じで定義することができます.

zip :: [a] -> [b] -> [(a,b)]
zip [] ys         = []
zip xs []         = []
zip (x:xs) (y:ys) = (x,y):zip xs ys

zipWith :: (a->a->a) -> [a] -> [a] -> [a]
zipWith f [] ys         = []
zipWith f xs []         = []
zipWith f (x:xs) (y:ys) = f x y :zipWith f xs ys

ライブラリのソースコードがどこにあるか分からないので(^^;,実際はどういう定義なのかは分かりません.処理系に組み込まれているということはないとは思うのですが.

相手にするリストの個数を増やしていって,zip3/zipWith3からzip7/zipWith7まであるようですが(4から7はData.Listモジュールを読み込むと使える),こういう風に具体的に数が決め打ちになっているのは,私はあんまり好きではないです.そこで

zipWithAny :: ([a] -> b) -> [[a]] -> [b]  

なんていう型で

 zipWithAny sum [ [1,2,3,4,5,...], [10,20,30,40,50,...], [100,200,300,400,500,...] ] 
= [111,222,333,444,555,...]

というような関数,つまり,

[ [a11,a12,a13,....],[a21,a22,a23,...],[a31,a32,a33,...],...,[an1,an2,an3,...],... ]
-> [ (f [a11,a21,a31,...,an1, ...]), (f [a12,a22,a32,...,an2, ...]),... ]

となる関数を定義してみました.イメージは行列で各行に対して何か操作をして,ベクトルを返すというところです.
 \left( \begin{array}{ccccc}\left( \begin{array}{c} a_{11} \\ a_{12} \\  a_{13} \\  \vdots \end{array} \right) & \left( \begin{array}{c} a_{21} \\ a_{22} \\  a_{23} \\  \vdots \end{array} \right) & \cdots& \left( \begin{array}{c} a_{n1} \\  a_{n2} \\ a_{n3} \\  \vdots \end{array} \right) & \cdots \end{array}  \right) \to \left( \begin{array}{ccccc}\left( \begin{array}{c} a_{11} \\ a_{21} \\  a_{31} \\  \vdots \end{array} \right) & \left( \begin{array}{c} a_{12} \\ a_{22} \\  a_{32} \\  \vdots \end{array} \right) & \cdots& \left( \begin{array}{c} a_{1n} \\  a_{2n} \\ a_{3n} \\  \vdots \end{array} \right) & \cdots \end{array}  \right)
と変換して,各列にmapを適用するということです*1

zipWithAny :: ([a] -> b) -> [[a]] -> [b]
zipWithAny f ([]:xs) = []
zipWithAny f xs      = (tupleToListWith . unzip) $ map headTail xs
    where headTail xs = (head xs, tail xs)
          tupleToListWith (a,bs)  = (f a):zipWithAny f bs

この定義では,暗黙に第二引数のリストの各要素(これもリスト)が同じ要素数(無限も含みます)を持つことを仮定しています.同じであるので,最初の要素が空になればすべて空なので空リストを返すとして,これを終了条件としています.ghciで実行させると

> zipWithAny sum [ [1,2,3], [10,20,30], [100,200,300] ]
[111,222,333]
> zipWithAny product [ [1,2,3], [10,20,30], [100,200,300] ]
[1000,8000,27000]
> zipWithAny (flip (!!) 2 )  [ [1,2,3], [10,20,30], [100,200,300] ]
[100,200,300]

となって期待した動作です.

追記その1(2007/07/11)

_さんからのコメントで「転置」の存在を知りました.

import List
zipWithAny :: ([a] -> b) -> [[a]] -> [b]
zipWithAny f = map f . transpse

ですっきりできますね.すっきりした上に,「暗黙の仮定」も緩和できます.どうもありがとうございました.下手を承知で晒している甲斐がありました.

追記その2(2007/07/11)

Haskellでインデントは意味がある」・・わかってるつもりでもつい.

import List
zipWithAny :: ([a] -> b) -> [[a]] -> [b]
  zipWithAny f = map f . transpose

こんな風にすると,エラーがでるんですよね。。結構真剣に悩みました(-_-;;;

*1:_さんのコメントより行列を前面にだして書いてみました.まさに転置で最初からこう書けば分かりやすかったです.