Underscore.jsがちょっと便利だったので紹介してみる。
数日前@naoiwata師匠に「JavaScriptにパターンマッチってないですよね?」って聞いていたら「Underscore.jsならあるかも」みたいなことをそそのかされたので少し触ってみた。
Underscore.jsは関数型プログラミングをしたい人のための軽量ライブラリだという認識でだいたい良いと思います。最近、「JavaScriptで学ぶ関数型プログラミング」みたいな本も出ているみたいで、ちょっと中身みたけどUnderscore.jsの使い方ぽい感じの本だったので興味ある人は読んでみるといいかと*1。
ちなみに僕が求めているパターンマッチは文字列の正規表現ではなくて、関数型言語でいわれるようなパターンマッチ。Gaucheのutil.match的なやつ。
で、とりあえずUnderscore.jsなんぞということでバーっとひと通りみてみた。
だいたい大きく6つくらいの機能に分類されているみたい。
- Collection Functions
- Array Functions
- Function Functions
- Object Functions
- Utility Functions
- Chaining
ちょっと面白い機能とかあったので幾つか紹介したいと思う。ちなみに僕はJavaScriptよりCoffeeScriptを好むので、当然ながら例は全てCoffeeで書きます。
Collection Functions
each関数
_.each [1..3], (n) -> console.log(n) #1 #2 #3
for n in [1..3]っていう普通の書き方でも全然いいけど、なんとなくこの書き方の方が落ち着きますよね。
map関数
fizzbuzz = -> _.map [1..100], (n) -> if n%15 == 0 "FizzBuzz" else if n%5 == 0 "Buzz" else if n%3 == 0 "Fizz" else n console.log fizzbuzz()
さくっとFizzBuzzしてみた。こんな感じの書き方ができます。
reduce(foldl)関数
console.log _.foldl [1..10], ((n, m) -> n + m), 0
いわゆる畳み込み。例ではfoldlを使っていますが、これはエイリアスです。ちなみにlがあるので当然rもあります。
filter関数
person = (name, age) -> name : name age : age persons = [person("ayato_p", 21),person("naoiwata", 20), person("shohe_i", 18),person("alea", 10)] adult_persons = _.filter persons, (p) -> p.age >= 20 _.each adult_persons, (p) -> console.log p.name+","+p.age #ayato_p,21 #naoiwata,20
ちょっと便利な関数。
コレクションに対する操作系が綺麗にまとまっていて結構使いやすい印象ですね。他にもsample,shuffle,max,min色々ありますけど、便利過ぎて鼻血出ます。countByとかも良い。
Array Functions
first関数,rest関数
f = (li) -> if _.isEmpty(li) 0 else _.first(li) + f(_.rest li) console.log f [1..10] #55
Lispのcar,cdrですね!とか言ってこんな使い方しちゃうLisp脳でごめんなさい。
partition関数
is_even = (n) -> n%2 != 1 console.log _.partition [0..10], is_even #[[0,2,4,6,8,10], [1,3,5,7,9]]
配列を基準を決めて分割できる。
zip関数
console.log _.zip [1..3], ["hoge", "fuga", "piyo"], [10, 20, 30] #[[1, "hgoe", 10], [2, "fuga", 20], [3, "piyo", 30]]
配列を受け取ってそれぞれのインデックスごとにマージする。
あと、もうひとつ機能があって、zip.apply関数を使うことによってネストした、配列の縦横を入れ替えることができる。
table_data = [["a", 1], ["b", 2], ["c", 3]] console.log table_data #[["a", 1], ["b", 2], ["c", 3]] console.log _.zip.apply(_, table_data) #[["a", "b", "c"], [1, 2, 3]]
みたいな感じ。これは少し便利かも。
object,range関数なども便利だと思いますが、range関数はCoffeeであれば[0..10]のように配列を作ることもできるので、必要かと言われるとちょっと微妙かな。
Function Functions
関数のための関数って感じ。ちょっと難しいけど、幾つか紹介。
bind関数
func = (greeting) -> greeting + ', ' + this.name sister = name: "moe" age: 10 greeting_to_sister = _.bind func, sister, 'Hi' console.log greeting_to_sister()
ちょっと用途が分からないけど、オブジェクトに関数を束縛するとあるんだけど、イメージは関数にオブジェクトを束縛するっていうイメージなんだよねー。ちなみに上記の例だと使い道がちょっと分かんないと思うけど、たぶん以下の用に使うのが良さそう。
int_obj = (n) -> this.val = n double : _.bind ( -> @.val * @.val), this ten_obj = int_obj(10) console.log ten_obj.double() #100
つまり、内部関数にthisを受け渡すのに使えそう。こういう風に使えばthatとか変数作って、thisを入れる必要なさそうだし*2。
partial関数
g = (greeting, name) -> greeting + name h = _.partial(g, _, "ayato_p") console.log h "Hello," #Hello,ayato_p
部分適用的な感じ。で、この関数は本当は手前から変数を埋めていくんだけど、「_(アンダースコア)」を使うことによって手前の引数をプレースホルダとして残しておける。まぁ例の通りですね。
memoize関数
fib = _.memoize (n) -> if n<2 then n else fib(n-1) + fib(n-2) console.log fib(100)
メモ化を簡単にできます。例のような本当ならちゃんと書かないと死んじゃうような再帰処理だって、メモ化してしまえば何も怖くない!!ビバ、メモ化!!
他にもonceとかnow,compose関数といった面白い関数があります。関数合成とかは結構使うよね。便利そう。onceはシングルトンを保証するイメージなのかなーと*3。
Object Functions
オブジェクトに対する関数群ですね。
keys関数,values関数
table = hoge: 1 fuga: 2 piyo:3 console.log _.keys table #["hoge", "fuga", "piyo"] console.log _.values table #[1, 2, 3]
これはこれで便利。かな?*4
pick関数
sister = first_name : "hanako" last_name : "tanaka" age : 10 adress : "hoge fuga piyo" console.log _.pick sister, 'first_name', 'age'
matches関数
person = (name, register) -> name : name register : register persons = [person("alea", false), person("ayato_p", true), person("naoiwata", false), person("shohe_i", true)] is_registered = _.matches {register : true} console.log _.filter persons, is_registered #[{name: "ayato_p", register: true}, {name: "shohe_i", register: true}]
同じkey/valueを含むものかという述語を作るのに便利な関数。
んー。オブジェクトに対する関数はあんまりパッとするものがないなー。pairs関数は面白いけど、使い道がビミョ。isHoge関数群はRubyあたりをやっている人的には嬉しい関数ですね。たぶん。*5
Utility Functions
便利関数群です。よく分からいですw
identity関数
sister = {name: "moe", age: 20} console.log sister == _.identity sister
これはいわゆるf(x) = xなので難しいことは何もない。
times関数
_.times 10, -> console.log "Hello, world" _(10).times -> console.log "Hello, world"
よくある繰り返し処理が書きやすくなるアレ。ただ、これの場合は後者の書き方の方が分かりやすい気もします。
template関数
$ -> person = (name, age) -> name: name age: age persons = [person("ayato_p", 10), person("naoiwata", 13), person("alea12", 11)] list = "<% _.each(persons, function(p) { %> <tr><td><%= p.name %></td><td><%= p.age %></td></tr> <% }); %>" $('tbody').append(_.template list, {persons: persons})
これめっちゃ便利。注意しないといけないのはCoffeeで書いてたとしても、templateに渡す文字列の部分に書く命令文などはJavaScriptで書いてある必要があるということ。*6
例ではtbodyタグの中に埋め込むようにって書いています。
あと、templateSettings関数を使えばmustache形式のテンプレートを作ることもできたりするぽい。ちょっとこの辺複雑なので興味ある人は下のリンク参照。
あとはmixin,result関数あたりが面白そう。
Chaining
Underscore.jsの関数をチェーンしていくための関数群*7。
chain関数,value関数
person = (name, age) -> name: name age: age persons = [person("ayato_p", 10), person("naoiwata", 13), person("alea12", 11), person("zero_u", 3)] youngest = _.chain persons .sortBy (p) -> p.age .map (p) -> p.name + " is " + p.age .first() .value() console.log youngest #zero_u is 3
と、まぁこんな感じ。便利ですね!!
と、いうわけで?
なんとなくUnderscore.jsの魅力を伝えることが出来たでしょうか??使いこなすことができればかなり便利だと思います。あと、CoffeeScriptとの相性良すぎて鼻血でます。
余談
僕が求めていたパターンマッチ機能はなさそうorz