$_と関数型Perl

  • 22
    Like
  • 0
    Comment
More than 1 year has passed since last update.

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.toUpperCharを対象とする関数なので、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する必要があるあたりが面白い。