またHaskell基本

Haskellの基本動作あれこれを少し。ループはアキュムレータを再帰で渡して実現。しかし、プライムで別の値を作るのって効率悪くないのかしら。以下の例は10進数の数値を表す文字列を数値に変換する。

import Data.Char

asInt :: String -> Int
asInt xs = loop 0 xs

loop :: Int -> String -> Int
loop acc [] = acc
loop acc (x:xs) = let acc' = acc * 10 + digitToInt x
                             in loop acc' xs
% ghci
GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude> :load loop.hs
[1 of 1] Compiling Main             ( loop.hs, interpreted )
Ok, modules loaded: Main.
*Main> asInt "3376"
3376
*Main> asInt "1234"
1234

zipWithとかも面白い。Haskellでひとこぶラクダ的なキャメルケースを使う理由って、dropWhile、takeWhileのように合成関数のような感じのことをよくやるからなのかしら。

Prelude> zipWith (*) [1..10] [2..11]
[2,6,12,20,30,42,56,72,90,110]
Prelude> drop
drop       dropWhile
Prelude> dropWhile even [1,2,3,4,5]
[1,2,3,4,5]
Prelude> dropWhile even [2,3,4,5]
[3,4,5]
Prelude> dropWhile even [2,4,5,6,8]
[5,6,8]
Prelude> takeWhile even [2,4,5,6,8]
[2,4]

Haskell

Prelude> zipWith (*) [1..10] [2..11]
[2,6,12,20,30,42,56,72,90,110]

というのは、Rubyでは、

>> (1..10).zip(2..11).map{|a|a.inject(:*)}
=> [2, 6, 12, 20, 30, 42, 56, 72, 90, 110]

という感じかしら。なるほど。

一瞬、(1..10).zipは(1..10).to_a.zipとすべきなのだろうかと迷ったけど、zipというのはRangeやArrayのメソッドではなくて、Enumerable#zipでArrayやRangeにmix-inされてるのだった。なるほど! このへん、Rubyってよくできてるなぁと思う。そうか、そう考えると、mix-inとして実装すべきなんだから「Rangeでもzipできるはず」という直感は働くのかも。まあ、そんなの覚えればいいだけかもしれないというのもあるけど。

一方、Rubyのようにオブジェクト中心だと、関数をいっぱいつなげるような処理では、どうも不自然な流れになりがちな気がする。mapの中でレシーバを明示しないといけないとか、zipが作用する2要素が表記上ではレシーバと引数というように等価でないとか。このzipWidthの例だと、Haskellのほうが美しいように思う。