GroovyとJavaScriptのクロージャ

よく見るカウンタのクロージャをGroovyとJavaScriptで書いてみる。

Groovy版

def clo() {
  def i=0
  return {
    i++
  }
}

def a = clo()
println "a():${a()}" // a():0
println "a():${a()}" // a():1
println "a():${a()}" // a():2

def b = clo()
println "b():${b()}" // b():0
println "b():${b()}" // b():1
println "b():${b()}" // b():2

println "a():${a()}" // a():3
println "b():${b()}" // b():3

JavaScript

function clo() {
  var i=0;
  return function(){
    return i++;
  };
}

var a = clo();
console.log("a():" + a()); // a():0
console.log("a():" + a()); // a():1
console.log("a():" + a()); // a():2

var b = clo();
console.log("b():" + b()); // b():0
console.log("b():" + b()); // b():1
console.log("b():" + b()); // b():2

console.log("a():" + a()); // a():3
console.log("b():" + b()); // b():3


メソッド(関数)を定義しない(という表現で良いのだろうか?)場合は以下のようになる。
Groovy版

def clo = {
  def i = 0
  return {
    i++
  }
}

def a = clo()
println "a():${a()}" // a():0
println "a():${a()}" // a():1
println "a():${a()}" // a():2

def b = clo()
println "b():${b()}" // b():0
println "b():${b()}" // b():1
println "b():${b()}" // b():2

println "a():${a()}" // a():3
println "b():${b()}" // b():3

JavaScript

var clo = function(){
  var i=0;
  return function(){
    return i++;
  };
}

var a = clo();
console.log("a():" + a()); // a():0
console.log("a():" + a()); // a():1
console.log("a():" + a()); // a():2

var b = clo();
console.log("b():" + b()); // b():0
console.log("b():" + b()); // b():1
console.log("b():" + b()); // b():2

console.log("a():" + a()); // a():3
console.log("b():" + b()); // b():3

[Groovy]追記(2012/01/11)

よくわかってないけどこうかくとエラーになる。

def clo() {
  x ->
  return {
    x++
  }
}

「->」の前に書かれたものはパラメータになるらしい。

def clo = {
  x ->
  return {
    x++
  }
}

def a = clo(1)
println "a():${a()}" // a():1
println "a():${a()}" // a():2
println "a():${a()}" // a():3

パラメータなしで呼び出すとエラー

def clo = {
  x ->
  return {
    x++
  }
}

def a = clo()
println "a():${a()}" // java.lang.NullPointerException

初期化すれば大丈夫

def clo = {
  x=0 ->
  return {
    x++
  }
}

def a = clo()
println "a():${a()}" // a():0
println "a():${a()}" // a():1
println "a():${a()}" // a():2

def b = clo(9)
println "b():${b()}" // b():9
println "b():${b()}" // b():10
println "b():${b()}" // b():11

また、引数が一個以下なら「->」を省略可能
とあるが、

def clo = {
  return {
    x++
  }
}

def a = clo()
println "a():${a()}" // groovy.lang.MissingPropertyException

となるのはわかる。xが宣言されていないから。

ただ暗黙変数itで書いたこれが何でエラーになるのかが全然わからなかった。

def clo = {
  return {
    it++
  }
}

def a = clo()
println "a():${a()}" // java.lang.NullPointerException

//def a = clo(1)
//println "a():${a()}" // java.lang.NullPointerException

ちなみにこれもエラーだった。

def clo = {
  it=0 ->
  return {
    it++
  }
}

def a = clo()
println "a():${a()}" // java.lang.NullPointerException

//def a = clo(1)
//println "a():${a()}" // java.lang.NullPointerException

結果、いろいろ試したら何となくわかった。

def clo = {
  return {
    it
  }
}

def a = clo()
println "a():${a()}" // null
def b = clo(4)
println "b():${b()}" // null

retrunで返却されるクロージャの中のitはa(とかb)の実行時の引数を見ていたらしい。

def clo = {
  return {
    it
  }
}

def a = clo()
println "a(1):${a(1)}" // 1
def b = clo(4)
println "b(7):${b(7)}" // 7


参考になりました。
http://groovy.codehaus.org/Japanese+Closures
groovyクロージャについて徒然と - uehaj's blog

JavaからGoogle Spreadsheetを参照する

以下を参考にGoogle Spreadsheetのデータを参照してみた。(ほぼそのまま)
GData APIでGoogleスプレッドシートを参照するには (1/3):Spreadsheets Data APIを使うための基礎知識(1) - @IT
https://sites.google.com/site/niusounds/programming/google-data-apinyuumon


本当にそのままコピーして自分のusername/passwordを設定し実行したらエラーになった。
公開範囲の影響(?)なのか分らなかったので自分でデータを作った。

使用したデータはこれ。
https://docs.google.com/spreadsheet/ccc?key=0AkxY01rLRjOfdElOZ3B0YXhQZjk0QkpWYnlPQmNaRkE


SpreadsheetSearch.java

package sample;

import java.io.IOException;

import com.google.gdata.client.spreadsheet.FeedURLFactory;
import com.google.gdata.client.spreadsheet.ListQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetQuery;
import com.google.gdata.client.spreadsheet.SpreadsheetService;
import com.google.gdata.data.spreadsheet.CustomElementCollection;
import com.google.gdata.data.spreadsheet.ListEntry;
import com.google.gdata.data.spreadsheet.ListFeed;
import com.google.gdata.data.spreadsheet.SpreadsheetEntry;
import com.google.gdata.data.spreadsheet.SpreadsheetFeed;
import com.google.gdata.data.spreadsheet.WorksheetEntry;
import com.google.gdata.util.ServiceException;

public class SpreadsheetSearch {

    /**
     * @param args
     */
    public static void main(String[] args) throws IOException, ServiceException {
        // このアプリケーションの名称。任意の名前を設定
        String applicationName = "com-SpreadsheetSearch-1";
        // Google AppsもしくはGoogleアカウントのメールアドレスとパスワードを設定
        String username = "";
        String password = "";
        // Spreadsheetsサービスへの認証を行う
        SpreadsheetService service = new SpreadsheetService(applicationName);
        service.setUserCredentials(username, password);

        // 検索対象のスプレッドシートを取得
        FeedURLFactory urlFactory = FeedURLFactory.getDefault();
        SpreadsheetQuery spreadsheetQuery = new SpreadsheetQuery(urlFactory
                .getSpreadsheetsFeedUrl());
        spreadsheetQuery.setTitleQuery("検索データ"); // 検索対象のスプレッドシート名を指定している
        SpreadsheetFeed spreadsheetFeed = service.query(spreadsheetQuery,
                SpreadsheetFeed.class);
        SpreadsheetEntry spreadsheetEntry = spreadsheetFeed.getEntries().get(0);
        System.out.println("名前:" + spreadsheetEntry.getTitle().getPlainText());

        // 検索対象のワークシートを取得
        WorksheetEntry worksheetEntry = spreadsheetEntry.getDefaultWorksheet();

        // ワークシート内を検索
        ListQuery listQuery = new ListQuery(worksheetEntry.getListFeedUrl());
//        listQuery.setSpreadsheetQuery("HEADER1 = HEADER1-1"); エラーになる
//        listQuery.setSpreadsheetQuery("header1 = HEADER1-1"); エラーになる
        listQuery.setSpreadsheetQuery("header1 = \"HEADER1-1\""); // OK
//        listQuery.setSpreadsheetQuery("\"HEADER1\" = \"HEADER1-1\""); ダメでした
//        listQuery.setSpreadsheetQuery("\"header1-9\" = \"HEADER1-1\""); // OK
        ListFeed listFeed = service.query(listQuery, ListFeed.class);
        ListEntry listEntry = listFeed.getEntries().get(0);
        CustomElementCollection elements = listEntry.getCustomElements();
        System.out.println("HEADER1:" + elements.getValue("HEADER1"));
        System.out.println("HEADER2:" + elements.getValue("header2"));
        System.out.println("HEADER3:" + elements.getValue("header3"));
    }

}


実行結果

名前:検索データ
HEADER1:HEADER1-1
HEADER2:HEADER2-1
HEADER3:HEADER3-1


よくわからなかった点
(1)検索時はカラム部分に大文字が使えない?
Spreadsheet上ではカラムをHEADER1と記述しているのにsetSpreadsheetQueryの部分でHEADER1と記述するとエラーになる。
小文字でheader1と記述すると大丈夫だった。
そのわりにはgetValue("HEADER1")でちゃんと値がとれる。

名前:検索データ
Exception in thread "main" com.google.gdata.util.InvalidEntryException: Bad Request
Parse error: Invalid column name: HEADER1

    at com.google.gdata.client.http.HttpGDataRequest.handleErrorResponse(HttpGDataRequest.java:594)
    at com.google.gdata.client.http.GoogleGDataRequest.handleErrorResponse(GoogleGDataRequest.java:563)
    at com.google.gdata.client.http.HttpGDataRequest.checkResponse(HttpGDataRequest.java:552)
    at com.google.gdata.client.http.HttpGDataRequest.execute(HttpGDataRequest.java:530)
    at com.google.gdata.client.http.GoogleGDataRequest.execute(GoogleGDataRequest.java:535)
    at com.google.gdata.client.Service.getFeed(Service.java:1135)
    at com.google.gdata.client.Service.getFeed(Service.java:1077)
    at com.google.gdata.client.GoogleService.getFeed(GoogleService.java:662)
    at com.google.gdata.client.Service.query(Service.java:1237)
    at com.google.gdata.client.Service.query(Service.java:1178)
    at sample.SpreadsheetSearch.main(SpreadsheetSearch.java:48)


(2)setSpreadsheetQueryでハイフンを入れるとエラー
文字通りハイフンを入れて検索を行ったらエラーになった。
ただアンスコ(_)は大丈夫だった。

        // ワークシート内を検索
        ListQuery listQuery = new ListQuery(worksheetEntry.getListFeedUrl());
//        listQuery.setSpreadsheetQuery("HEADER1 = HEADER1-1"); //エラーになる
//        listQuery.setSpreadsheetQuery("header1 = HEADER1-1"); //エラーになる
        listQuery.setSpreadsheetQuery("header1 = \"HEADER1-1\""); // OK
//        listQuery.setSpreadsheetQuery("\"HEADER1\" = \"HEADER1-1\""); //ダメでした
//        listQuery.setSpreadsheetQuery("\"header1-9\" = \"HEADER1-1\""); // OK(header1-9というカラム名にした場合)
名前:検索データ
Exception in thread "main" com.google.gdata.util.InvalidEntryException: Bad Request
Parse error: null

    at com.google.gdata.client.http.HttpGDataRequest.handleErrorResponse(HttpGDataRequest.java:594)
    at com.google.gdata.client.http.GoogleGDataRequest.handleErrorResponse(GoogleGDataRequest.java:563)
    at com.google.gdata.client.http.HttpGDataRequest.checkResponse(HttpGDataRequest.java:552)
    at com.google.gdata.client.http.HttpGDataRequest.execute(HttpGDataRequest.java:530)
    at com.google.gdata.client.http.GoogleGDataRequest.execute(GoogleGDataRequest.java:535)
    at com.google.gdata.client.Service.getFeed(Service.java:1135)
    at com.google.gdata.client.Service.getFeed(Service.java:1077)
    at com.google.gdata.client.GoogleService.getFeed(GoogleService.java:662)
    at com.google.gdata.client.Service.query(Service.java:1237)
    at com.google.gdata.client.Service.query(Service.java:1178)
    at sample.SpreadsheetSearch.main(SpreadsheetSearch.java:50)

どうにかならないものかと適当に""でくくってみたら大丈夫だった。
ただカラム部分を大文字にして""でくくってみても駄目だった。

まとめ

  • 検索時はカラム部分を小文字にする。
  • ハイフンを使うときは""でくくる。


Groovy版
参考にしました。
Java使いをGroovyに引き込むサンプル集 - No Programming, No Life
Eclipseで作成。
手順
01.Groovy Projectを作成(Google Spreadsheetを参照するにあたって必要なjarも設定する)
02.SpreadsheetSearch.groovyを作成
03.SpreadsheetSearch.javaのmainメソッドの中をコピー
04.SpreadsheetSearch.groovyのmainメソッドに3.でコピーしたコードを貼り付け
05.ctrl+shift+oでインポートの編成
ここまででひとまず動いた。*1
06.行末のセミコロンを削除
07.String等の型をdefに変更する
08.ctrl+shift+oでインポートの編成
09.インポートするクラスに別名をつける
10.別名をつけたクラスに変更する
11.ちょっと手を加える


SpreadsheetSearch.groovy(コメント部分は削除)

package sample

import com.google.gdata.client.spreadsheet.FeedURLFactory as FUF
import com.google.gdata.client.spreadsheet.ListQuery as LQ
import com.google.gdata.client.spreadsheet.SpreadsheetQuery as SQ
import com.google.gdata.client.spreadsheet.SpreadsheetService as SS
import com.google.gdata.data.spreadsheet.ListFeed as LF
import com.google.gdata.data.spreadsheet.SpreadsheetFeed as SF

class SpreadsheetSearch {

    static main(args) {
        def applicationName = "com-SpreadsheetSearch-1"
        def username = ""
        def password = ""

        def service = new SS(applicationName)
        service.setUserCredentials(username, password)

        def urlFactory = FUF.getDefault()
        def spreadsheetQuery = new SQ(urlFactory.getSpreadsheetsFeedUrl())
        spreadsheetQuery.setTitleQuery("検索データ")
        def spreadsheetFeed = service.query(spreadsheetQuery, SF.class)
        def spreadsheetEntry = spreadsheetFeed.getEntries().get(0)
        println "名前:${spreadsheetEntry.getTitle().getPlainText()}"

        def worksheetEntry = spreadsheetEntry.getDefaultWorksheet()

        def listQuery = new LQ(worksheetEntry.getListFeedUrl())
        listQuery.setSpreadsheetQuery("header1 = \"HEADER1-1\"")
        def listFeed = service.query(listQuery, LF.class)
        def listEntry = listFeed.getEntries().get(0)
        def elements = listEntry.getCustomElements()
        println "HEADER1:${elements.getValue("HEADER1")}"
        println "HEADER2:${elements.getValue("header2")}"
        println "HEADER3:${elements.getValue("header3")}"
    }

}

もっと簡単に書けると思うけど知っている知識ではここまで。
名前の付け方によるかもしれないが別名インポートを使うと分かりにくくなりそう。

*1:SpreadsheetSearch.javaの拡張子を.groovyに変更してGroovy Projectにつっこんでも動く。

エンジニアの未来サミット 0905 エンジニア・サバイバルの感想

  • 【第一部】おしえて!アルファギーク ―エンジニアが幸せになる方法
    • 面白かった。
  • 【第二部】弾 vs. 個性派エンジニア ―サバイバル討論
    • 楽しかった。

第一部でひがさんが言っていた、「会社で評価されなかった=自分には今の仕事が向いていない」という考えに違和感があった。
向いているか向いていないかの判断は自分でするのが当たり前だという考えがあったからだと思う。
ただ、向いているか向いていないか、自分自身でも分からなくなった場合の一つの判断基準として参考になった。