LoginSignup
27
23

More than 5 years have passed since last update.

$_と関数型Perl

Posted at

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する必要があるあたりが面白い。

27
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
23