Perlの関数には引数を省略した場合に$_
が引数として与えられたものとして振る舞う関数があるが、この性質を使うと気持ち悪いほどHaskellっぽいコードをPerlで書くことができる。
逆に、自分のようなPerlプログラマーがHaskellに入門する時は、「あーここでは$_
が省略されてんのね」って考えるとポイントフリー記法とか理解しやすいと思った。
map
まずは基本中の基本のmap。
Perl
$, = ","; ## リスト出力のセパレータ (以下同様)
$\ = "\n"; ## 出力行の末尾 (以下同様)
my @fruits = qw(apple orange strawberry);
print map { length } @fruits; # => 5,6,10
Haskell
fruits = ["apple", "orange", "strawberry"]
main = print $ map length fruits -- => [5,6,10]
Perlは無名関数のブロック記法と$_
の省略により、極限までHaskellに近くなっている。
grep / filter
正規表現によるフィルタリング。
Perl
my @fruits = qw(apple orange strawberry);
print grep { /berry/ } @fruits; # => strawberry
Haskell
import Text.Regex.PCRE
fruits = ["apple", "orange", "strawberry"]
main = print $ filter (=~ "berry") fruits -- => ["strawberry"]
Haskellで正規表現を使うには外部ライブラリをimportする必要がある。
Perlではマッチング演算子(=~
)ごと省略する。こうして見るとやはりPerlでは正規表現が優遇されている。
for
for文。
Perl
my @fruits = qw(apple orange strawberry);
for (@fruits) { print }
## => apple
## => orange
## => strawberry
Haskell
import Control.Monad
fruits = ["apple", "orange", "strawberry"]
main = forM_ fruits putStrLn
-- => apple
-- => orange
-- => strawberry
これは非常によく似ている。
join / intercalate
文字列処理では頻出の、mapからのjoinパターン。join
は$_
を使うわけではないが、これもPerlとHaskellでだいぶ書き方のパターンが似てくる。
Perl
my @fruits = qw(apple orange strawberry);
print join ":", map { uc } @fruits; ## => APPLE:ORANGE:STRAWBERRY
Haskell
import Data.List (intercalate)
import Data.Char (toUpper)
fruits = ["apple", "orange", "strawberry"]
uc = map toUpper
main = print $ intercalate ":" $ map uc fruits -- => "APPLE:ORANGE:STRAWBERRY"
Perlのjoin
関数と同等のHaskell関数はintercalate
という、聞いたこともないような英単語の関数である(自分はこれの名前が分からなくて数十分、Hackageをウロウロしたことがある)。
また、Data.Char.toUpper
はChar
を対象とする関数なので、String
を対象とするにはmap
で持ち上げる必要がある。
reduce / foldl
リストを連想配列に変換する。
Perl
use List::Util qw(reduce);
use Data::Dumper;
my @fruits = qw(apple orange strawberry);
print Dumper reduce { push @{$a->{length($b)}}, $b; $a } {}, @fruits;
## => $VAR1 = {10 => ['strawberry'], 6 => ['orange'], 5 => ['apple']};
Haskell
import qualified Data.Map as M
fruits = ["apple", "orange", "strawberry"]
main = print $ foldl (\a b -> M.insertWith (++) (length b) [b] a) M.empty fruits
-- => fromList [(5,["apple"]),(6,["orange"]),(10,["strawberry"])]
ここまで来るとちょっと無理があるかな・・・しかも$_
使ってないし。。
Perlのコードではハッシュ要素($a->{length($b)}
)に対していきなりpush
しているが、まだ要素がない場合でもautovivificationにより勝手に配列を生成してくれる。
HaskellでこういったことをやるにはinsertWith
関数が便利だ。これは挿入するキーに対応する要素がMap内にまだ存在しない場合は指定の値をそのまま突っ込み、存在する場合は指定の関数(今回の場合は(++)
)で既存の要素と新規要素を結合する。
あと、Perlではハッシュオブジェクトは組み込みで使えるがreduce
はimportする必要がある一方、Haskellではfoldl
は組み込みで使えるものの連想配列をimportする必要があるあたりが面白い。