ぽんこつ@ponkotuyです。勝手に会社のプロダクトをリファクタしたり高速化したりするだけの簡単なお仕事をしています。Scalaも楽しいけど、SQLのチューニングはもっと楽しいです。
という自己紹介をガン無視してリスト操作関数とUnderscore.jsの話をします。
リスト操作関数
関数型言語の長所で真っ先に出てきて、手続型言語に慣れた人でも比較的恩恵を理解しやすいのがリスト操作関数だと思います。
例えばある数値のリストの中から偶数で一番大きい数を取るとかいう処理
-- Haskell maximum . filter even $ [2, 4, 5, 7, 1, 8, 9, 10]
// Scala List(1, 2, 4, 5, 7, 1, 8, 9, 10).filter(_ % 2 == 0).max
# coffee _ = require('underscore') _.max(_.filter [1, 2, 4, 5, 7, 1, 8, 9, 10], (x) -> x % 2 == 0)
ワンライナーで短かく、可読性を損なわずに書けます。また、副作用をできるだけ避ける書き方がしやすいように作られているので、低コストで副作用を回避できます。
Underscore.js
このように便利なリスト操作関数をJavaScriptでも使えるようにしたい。そこで便利関数を集めたUnderscore.jsです。
Undescore.jsを使えば、若干余計な_.を書かなくてはいけない問題はありつつも、とりあえずはリスト操作関数の恩恵にあずかることができます。
ただしUnderscore.jsには一方で「名前が分かりづらい」という問題があります。HaskellやScalaと同じだったら苦労しないのに。そこで、Underscore.jsのある関数が、HaskellやScalaだと何と呼ばれているかの一覧を作ってみました。
早見表
Underscore.js | Scala | Haskell | 備考 |
---|---|---|---|
each(xs, f) forEach |
xs.foreach(f) | forM_ xs f | |
map(xs, f) collect |
xs.map(f) | map f xs | |
pluck(xs, propName) | mapの特殊形 | ||
reduce(xs, f, [memo]) inject foldl |
xs.reduceLeft(f) | foldl1 f xs 空で例外 |
|
xs.foldLeft(memo)(f) | foldl f memo xs | ||
reduceRight(xs, f, [memo]) foldr |
xs.reduceRight(f) | foldr1 f xs 空で例外 |
|
xs.foldRight(memo)(f) | foldr f memo xs | ||
find(xs, f) 返り値はvalue | undefined |
xs.find(f) 返り値はOption[A] |
find f xs 返り値はMaybe |
|
findWhere(xs, obj) | findの特殊形 | ||
filter(xs, f) | xs.filter(f) | filter f xs | |
where(xs, obj) | filterの特殊形 | ||
compact(xs) | filterの特殊形 | ||
without(xs, values*) | filterの特殊形 | ||
reject(xs, f) | xs.filterNot(f) | ||
every(xs, [f]) all |
xs.forall(f) | all f xs | |
some(xs, [f]) any |
xs.exists(f) | any f xs | |
contains(xs, value) includes |
xs.contains(value) | elem value xs | |
max(xs, [f]) | xs.max | maximum xs | minimumも同様なので省略 |
xs.maxBy(f) | (maximumBy g xs) | ||
sortBy(xs, f) | xs.sortBy(f) | (sortBy g xs) | |
groupBy(xs, f) | xs.groupBy(f) | × | |
indexBy(xs, f) | × | × | groupByと同じだが結果が1つのみ |
countBy(xs, f) | xs.count(f) | × | |
shuffle(xs) | Random.shuffle(xs) | × | 副作用あり |
sample(xs, [n]) | × | × | |
size(xs) | xs.size | length xs | |
partition(xs, f) | xs.partition(f) | partition f xs | |
first(xs, [n]) head take 返り値はvalue | undefined | array |
xs.headOption 返り値はOption[A] |
listToMaybe xs | |
xs.take(n) | take n xs | Lodashにはない | |
× | xs.head | head xs | 例外(非推奨) |
initial(xs, [n]) 空のとき空が返る |
xs.init 空のとき例外 |
init xs 空のとき例外 |
|
xs.dropRight(n) | × | Lodashにはない | |
last(xs, [n]) | xs.lastOption | × | |
xs.takeRight(n) | × | Lodashにはない | |
× | xs.last | last xs | 例外(非推奨) |
rest(xs, [n]) tail drop 空のとき空が返る |
xs.tail 空のとき例外 |
tail xs 空のとき例外 |
|
xs.drop(n) | drop n xs | Lodashはdropでおこなう | |
flatten(xs, true) | xs.flatten | concat xs | |
flatten(xs) | × | × | 再帰的なflatten |
union(xs*) | xs.union(ys) | union xs ys | 2つの場合 |
xss.flatten.distinct | foldl union [] xss | ||
intersection(xs*) | xs & ys // Set xs.intersect(ys) // 汎用 |
intersect xs ys | 2つの場合 |
xss.reduce(_ & _) // Set xss.reduce { case (x, y) => x.intersect(y) } // 汎用 |
last $ scanl1 intersect xss | ||
difference(xs, ys) | xs.diff(ys) | xs \\ ys | |
uniq(xs) unique |
xs.distinct | nub xs | |
zip(xs*) unzip(xs*) |
xss.transpose | transpose xss | JSにはTupleがないので、厳密にはzipではない |
object(xs, ys) | xs.zip(ys).toMap | Data.Map.fromList $ xs `zip` ys | |
indexOf(xs, value) | xs.indexOf(value) | elemIndex value xs | |
lastIndexOf(xs, value) | xs.lastIndexOf(value) | × | |
findIndex(xs, f) | xs.indexWhere(f) | findIndex f xs | |
findLastIndex(xs, f) | xs.lastIndexWhere(f) | × | |
range([start], stop, [step]) | start until stop Range(start, end, [step]) List.range(start, end, [step]) |
[start..(end - 1)] [start, start + step .., end - step] |