読者です 読者をやめる 読者になる 読者になる

(define -ayalog '())

括弧に魅せられて道を外した名前のないプログラマ

Underscore.jsがちょっと便利だったので紹介してみる。

JavaScript CoffeeScript

数日前@師匠に「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

*1:中身は関数型言語とか触ったことあるなら読む必要なさそうな雰囲気だった

*2:JavaScriptの関数呼び出しの場合、thisがグローバル環境を指してしまう問題があるので、こうすれば回避できますっていう風に例を書いてみた。本来の用途かはわかんないw

*3:ちゃんと読んでない

*4:JavaScript本体にこういう機能なかったっけ??

*5:個人的な好みを言えば「?(クエスチョン)」を使いたいのだけど。あとisZeroはないんだって思った。まぁオブジェクトに対する関数だから仕方ないか

*6:だって、コンパイルできないからね

*7:といっても2つしかないけど