2012年5月9日水曜日

Play framework2.0のテンプレートエンジン部分意訳

Play framework2.0のThe template engineを意訳しました。

私は英語が苦手なタイプの人間なので間違っている部分は多々あると思います。
例のごとく、もし私の訳が間違っていても責任は負えませんのであしからず・・・
正確なものを読みたい方は原典をどうぞ。

http://www.playframework.org/documentation/2.0/ScalaTemplates

なお、日本Playframeworkユーザー会さんの方で翻訳作業が進行中のようですので、そちらを待たれるという手もあります。

それではどうぞ!

Play2.0のテンプレートエンジンはScalaがベースです

APS.NETのRazorにインスパイアされました。
コンパクトで簡単でどんなテキストエディタでも編集できます。
新しい言語を覚える必要はありません!

テンプレートはScalaだからコンパイルされます。
つまり、ブラウザで通常のプログラムと同じようにテンプレートのエラーを確認できます。

overview

Play2.0のテンプレートは、ちょっとしたScalaのコードを含んだシンプルなテキストファイルです。
HTMLやXML、CSVといった普通のテキストベースのフォーマットを生成できます。
テンプレートシステムは、デザイナが簡単に扱えるようにして、HTMLを快適に感じるようにしました。

テンプレートはコンパイルされて、普通のScalaの関数になります。
もし、「views/Application/index.scala.html」というテンプレートを作った場合、「views.html.Application.index」という関数が生成されます。

上記のディレクトリにファイルを作ったと仮定して、以下はシンプルなテンプレートの例です。

@(customer: Customer, orders: Seq[Order])
<h1>Welcome @customer.name!</h1>
<ul>
@orders.map { order =>
 
<li>@order.title</li>
}
</ul>

上記のテンプレートは、ただのテンプレートとしてだけではなく、どんなScalaコードからでも呼び出せます。
以下がサンプルです

val html = views.html.Application.index(customer, orders)

※注意(公式にない私的メモ)
Applicationというディレクトリがサンプルにあるけど、なんかこれはデフォルトコントローラのApplication.scalaと混同してしまって分かりにくかった。
viewsディレクトリの下に、「hoge/piyo.scala.html」というファイルを作った場合は、「views.html.hoge.piyo()」になります。
views.htmlは決め打ちで、後は通常のパッケージみたいに.区切りでディレクトリ階層を書いて行って、最後にテンプレートの先頭のファイル名(scala.htmlは不要)を書く感じ

@という魔法

Scalaテンプレートで、@を特殊な文字として扱います。
この@マークがある場所は、Scalaステートメントが始まる場所です。
@で始まるScalaステートメントをを態々閉じる必要はありません。
フレームワークがうまいことやってくれます。

Hello @customer.name!
       ^^^^^^^^^^^^^^^^^^^^^^^
        Scala code

テンプレートエンジンはあなたのコードを解析して、コードの終わりを自動的に検知してくれます。
ただし、もし複数のトークンを使うようなステートメントは明示的に括弧を使ってね。

Hello @(customer.firstName + customer.lastName)!
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                    Scala Code

さらに、普通のScalaコードのように中括弧を使ってステートメントを書くこともできます。

Hello @{val name = customer.firstName + customer.lastName; name}!
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                             Scala Code

そう!@は特殊な文字なのです。
時々@をエスケープしたいときもあるでしょう。
その時は@@を使ってください

My email is bob@@example.com


テンプレートパラメータ

テンプレートはシンプルな機能なので、引数が必要です。
テンプレートファイルの先頭行に宣言する必要があります。

@(customer: models.Customer, orders: Seq[models.Order])

さらにデフォルト引数も使えます。

@(title: String = "Home")

あと、いくつかのパラメータグループも使えます。

@(title:String)(body: => Html)

そしてimplictパラメータも使えます。

@(title: String)(body: => Html)(implicit request: play.api.mvc.Request)


イテレータ

Scalaのfor分は当然使えます。
ただし、以下の場合だとテンプレートコンパイラはコードブロックの上にyieldを置くことに注意してください。

<ul>
@for(p <- products) {
 
<li>@p.name ($@p.price)</li>

}
</ul>

恐らく分かるでしょうが、for分は典型的なmapのシンタックスシュガーになっています。


<ul>
@products.map { p =>
 
<li>@p.name ($@p.price)</li>

}
</ul>


ifブロック

ifブロックは特別なものではありません。
普通のScalaのifと同じように使い、以下のようになります。

@if(items.isEmpty) {
 
<h1>Nothing to display</h1>
} else {
 
<h1>@items.size items!</h1>
}


パターンマッチング

テンプレートの中でパターンマッチングを使うこともできます。

@connected match {
   
  case models.Admin(name) => {
    Connected as admin (@name)
  }

  case models.User(name) => {
    Connected as @name
  }
   
}


再利用可能なブロックの宣言

再利用可能なブロックを利用できます。

@display(product: models.Product) = {
  @product.name ($@product.price)
}

<ul>
@products.map { p =>
  @display(product = p)
}
</ul>

さらに、再利用可能なScalaによるコードブロックを宣言することもできます

@title(text: String) = @{
  text.split(' ').map(_.capitalize).mkString(" ")
}

<h1>@title("hello world")</h1>

Note:Scalaによるコードブロックをテンプレートの中で宣言するのは時々は有効な手段です。
しかし、複雑なロジックを記述するのに、テンプレートはベストな場所ではないということを覚えておいてください。
多くの場合、複雑なコードは純粋なScalaソースファイルに外出ししておいた方が良いです。
(views/パッケージの下にそういったソースファイルを格納することもできます。)

※注意(公式にない私的メモ)
つまり普通にテンプレートの中で関数を宣言できるということ。

PlayFramework2.0の決まりによって、再使用可能なコードはimplictという名前で定義されます。

@implicitFieldConstructor = @{ MyFieldConstructor() }


再利用可能な値の宣言

definingヘルパーを使って、スコープ内で再利用できる値を定義できます。

@defining(user.firstName + " " + user.lastName) { fullName =>
 
    Hello @fullName

}


ステートメントのインポート

テンプレートの先頭でインポートしたいものはどんなものでもインポートすることが来ます。
(もしくはサブテンプレート)

@(customer: models.Customer, orders: Seq[models.Order])

@import utils._

...


コメント

サーバサイドのみのコメントを@* *@を使って書くことが出来ます。

@*********************
 * This is a comment *
 *********************@  

テンプレートの最初の行にコメントを書くことで、Scala API docとして扱うことが出来ます

@*************************************
 * Home page.                        *
 *                                   *
 * @param msg The message to display *
 *************************************@
@(msg: String)

<h1>@msg</h1>


エスケープ

デフォルトでは、動的なコンテンツはテンプレートの種類に応じてエスケープされます。(HTMLとかXML)
もしエスケープさせずにそのまま出力したい場合には、出力したい生データをテンプレートのコンテントタイプでラップしてください。

  <p>@Html(article.content) <p> 


以上で、公式のテンプレートエンジン部分の意訳は終了!
PlayFramework1.2の頃に翻訳されたサイトを見ていましたが、チュートリアルが分かりやすいという点が本当に素晴らしいですね。
英語は苦手ですが、難しい表現もないようなので英語の勉強にももってこいなのでは?

2012年4月25日水曜日

PostgreSQLで複数レコードを一つのカラムにまとめる方法

PostgreSQLで、ARRAY関数を使って複数レコードを一つのカラムにまとめて取得する方法です。
この日本語だけだと、正直一体何を言っているの分からないので、実際の業務に絡めてみました。

まず、CDを管理するシステムを運用していると仮定します。
そして、各CDは必ずどこかのレーベルから発売されています。
さらに、各CDには複数のジャンルがあります。
各CDと各ジャンルを紐付けるために、それぞれのIDを格納したテーブルを使用します。

*******************************************************
各テーブルの情報
*******************************************************

・CDテーブル
 cd_id | label_id |     cd_name    
-------+----------+-----------------
     1 |        1 | I am Man part 1
     2 |        1 | I am Man part 2
     3 |        2 | hoge the CD

・レーベルテーブル
 label_id | label_name
----------+------------
        1 | label_A
        2 | label_B

・ジャンルテーブル
 genre_id | genre_name
----------+------------
        1 | Rock
        2 | Blues

・CDとジャンルを紐つけるテーブル
 genre_id | cd_id
----------+-------
        1 |     1
        1 |     2
        2 |     2
        2 |     3

ある日、営業にこんなことを言われました。

「全CDデータのCSV作ってよ。」
「カラムは、["CDのID", "CD名", "レーベル名", "ジャンル名"]でお願いね。」
「あ、各CDのジャンルは一行に纏めてね。とりあえず&区切りとかでお願い。」

よくありますね。こういうの。
まぁこれくらいなら簡単に取得できそうです。
さっそく全データをSELECTだ!

*見やすいようにSQL文に改行を入れています
postgres=# SELECT c.cd_id, c.cd_name, l.label_name, g.genre_name
                  FROM cd c, label l, genre_reference gr, genre g
                  WHERE c.label_id = l.label_id
                          AND c.cd_id = gr.cd_id
                          AND gr.genre_id = g.genre_id;

 cd_id |     cd_name     | label_name | genre_name
-------+-----------------+------------+------------
     1 | I am Man part 1 | label_A    | Rock
     2 | I am Man part 2 | label_A    | Rock
     2 | I am Man part 2 | label_A    | Blues
     3 | hoge the CD     | label_B    | Blues
(4 rows)

ぱっと見て分かりますが、「I am Man part2」が2レコードありますね。
これはジャンルが2つ設定されているためです。
ということは、各CDを1行のCSV行にするためには、何らかの方法で各CDの情報をマージする必要があります。
ジャンルを省いた状態でプログラムでデータを取得して、再度全レコードに対してジャンル情報を問い合わせたり・・・
極端に難しい訳ではなさそうですが、面倒くさいですよね。
余計なSQLの発行にもなるし・・・

そこで、PostgreSQLのARRAY関数の出番です。
SQLを以下のようの書き換えて実行してみます。

*見やすいようにSQL文に改行を入れています
postgres=# SELECT c.cd_id, c.cd_name, l.label_name,
                          ARRAY (
                                  SELECT genre_name
                                  FROM genre g, genre_reference gr
                                  WHERE c.cd_id = gr.cd_id AND g.genre_id = gr.genre_id
                          ) as genre_name
                  FROM cd c, label l
                  WHERE c.label_id = l.label_id
                  ORDER BY cd_id;

 cd_id |     cd_name     | label_name |  genre_name
-------+-----------------+------------+--------------
     1 | I am Man part 1 | label_A    | {Rock}
     2 | I am Man part 2 | label_A    | {Rock,Blues}
     3 | hoge the CD     | label_B    | {Blues}

genre_nameが{}で囲まれて一つのレコードにまとめられました。
これがARRAY関数の機能です。
ジャンルに関連する部分を、SELECT内でARRAY関数を使ってサブクエリっぽくまとめてあげるだけで実現できます。
これで、各CD情報は完全に1行にまとめられました。
あとはプログラムで取得してCSVにするなり、vi等でゴニョゴニョするなり自由自在です。

ちなみにJavaだとjava.sql.Array型として取得できます。

java.sql.Array workCategories = this.resultSet.getArray("genre_name");
String[] categories = (String[]) workCategories.getArray();

参考
*******************************************************
DDL
*******************************************************

-- CDマスタ
CREATE TABLE cd( cd_id int, label_id int, cd_name varchar(64) );

-- レーベルマスタ
CREATE TABLE label( label_id int, label_name varchar(64) );

-- ジャンルマスタ
CREATE TABLE genre( genre_id int, genre_name varchar(64) );

-- CDとジャンルの関連を管理するテーブル
CREATE TABLE genre_reference ( genre_id int, cd_id int );


*******************************************************
テストデータ投入
*******************************************************
-- レーベルマスタのデータ
INSERT INTO label (label_id, label_name) VALUES(1, 'label_A');
INSERT INTO label (label_id, label_name) VALUES(2, 'label_B');

-- CDマスタのデータ
INSERT INTO cd (cd_id, label_id, cd_name) VALUES(1, 1, 'I am Man part 1');
INSERT INTO cd (cd_id, label_id, cd_name) VALUES(2, 1, 'I am Man part 2');
INSERT INTO cd (cd_id, label_id, cd_name) VALUES(3, 2, 'hoge the CD');

-- ジャンルのマスタ
INSERT INTO genre(genre_id, genre_name) VALUES(1, 'Rock');
INSERT INTO genre(genre_id, genre_name) VALUES(2, 'Blues');

-- CDとジャンルの紐付け
INSERT INTO genre_reference(genre_id, cd_id) VALUES(1,1);
INSERT INTO genre_reference(genre_id, cd_id) VALUES(1,2);
INSERT INTO genre_reference(genre_id, cd_id) VALUES(2,2);
INSERT INTO genre_reference(genre_id, cd_id) VALUES(2,3);
INSERT INTO genre_reference(genre_id, cd_id) VALUES(1,4);


*******************************************************
以下テーブル構成
*******************************************************
              Table "public.cd"
  Column  |         Type          | Modifiers
----------+-----------------------+-----------
 cd_id    | integer               |
 label_id | integer               |
 cd_name  | character varying(64) |


              Table "public.label"
   Column   |         Type          | Modifiers
------------+-----------------------+-----------
 label_id   | integer               |
 label_name | character varying(64) |


              Table "public.genre"
   Column   |         Type          | Modifiers
------------+-----------------------+-----------
 genre_id   | integer               |
 genre_name | character varying(64) |


 Table "public.genre_reference"
  Column  |  Type   | Modifiers
----------+---------+-----------
 genre_id | integer |
 cd_id    | integer |

2012年3月8日木曜日

UbuntuのPuttyで日本語を入力する方法

UbuntuのソフトウェアセンターからPuttyをインストールして楽ちんだな~と思いながら使っていると、問題が・・・
まず、日本語入力ができない!これは大問題です。次に、WindowsのPuttyだと出来ていたドラッグした部分が自動的にコピーされて、右クリックでペースト。これもできませんでした。
設定か?それともそういうものなのか?と思って色々調べていましたがPuttyだけで解決する方法が見つかりませんでした。

そこで、Puttyをインストールしたら一緒に付いてくるplinkというputtyのコマンドラインツールを試してみると無事日本語入力が出きるようになりました。
plinkとは、ターミナルから直接Puttyを利用するためのコマンドラインツールです。
そのため、Puttyのウインドウではなくて、標準のターミナルでリモートに接続できます。
なので、文字列のコピーは標準のターミナルと同じ手順になります。

では、実際にどうやって使うかというと、基本はplinkに接続先ホスト名やユーザ名等を指定して実行する形になります。

plink [ユーザ名]@[ホスト] -P [ポート番号] -i [SSHの秘密鍵(必要なら)]

・・・日本語が入力出来るようになるのはうれしいけど毎回接続情報入力するのは面倒くさい・・・
しかしさすがPutty専用のコマンドラインツール!
loadオプションに、Puttyに保存されているセッション名を渡してあげると、その情報を使ってリモートに接続する事が出来ます。
しかもセッション名に日本語を使っていても問題ありませんでした。

例えば、「開発マシーン!」というセッション名でリモートサーバ情報を保存している場合、plinkを以下のように実行します。

plink -load 開発マシーン!

これだけで、Puttyで接続した時とまったく同じ情報でリモートに接続できます。

ただし、plinkだとPuttyのように接続先によって背景色を変えるなどが出来ないようです。
この部分だけ不満ですが実用上問題ないのでま、いっか~という状況です。

実際にログインした際には以下のようになります。

k-kuwana@VirtualBox:~$ plink -load "[セッション名]"
Using username "[接続するユーザ名]".
Passphrase for key "[鍵名]": //鍵を使用していたらその鍵名が表示されて、パスフレーズの入力が求められます
Last login: Wed Mar  7 14:56:08 2012 from xxx.xxx.xxx.xxx
[接続ユーザ名@接続ホスト名 ~]$

今後は、Putty Configuration(Puttyを起動したら表示される画面)で、サーバ情報だけ保存して、実際の接続はすべてplinkで行おうと思っています。

plinkのヘルプは以下の通りです。
k-kuwana@VirtualBox:~$ plink
PuTTY Link: command-line connection utility
Release 0.61
Usage: plink [options] [user@]host [command]
       ("host" can also be a PuTTY saved session name)
Options:
  -V        print version information and exit
  -pgpfp    print PGP key fingerprints and exit
  -v        show verbose messages
  -load sessname  Load settings from saved session
  -ssh -telnet -rlogin -raw -serial
            force use of a particular protocol
  -P port   connect to specified port
  -l user   connect with specified username
  -batch    disable all interactive prompts
The following options only apply to SSH connections:
  -pw passw login with specified password
  -D [listen-IP:]listen-port
            Dynamic SOCKS-based port forwarding
  -L [listen-IP:]listen-port:host:port
            Forward local port to remote address
  -R [listen-IP:]listen-port:host:port
            Forward remote port to local address
  -X -x     enable / disable X11 forwarding
  -A -a     enable / disable agent forwarding
  -t -T     enable / disable pty allocation
  -1 -2     force use of particular protocol version
  -4 -6     force use of IPv4 or IPv6
  -C        enable compression
  -i key    private key file for authentication
  -noagent  disable use of Pageant
  -agent    enable use of Pageant
  -m file   read remote command(s) from file
  -s        remote command is an SSH subsystem (SSH-2 only)
  -N        don't start a shell/command (SSH-2 only)
  -nc host:port
            open tunnel in place of session (SSH-2 only)
  -sercfg configuration-string (e.g. 19200,8,n,1,X)
            Specify the serial configuration (serial only)

2012年3月4日日曜日

Ubuntuのターミナルのパス表記が長い場合の対処方法

Ubuntu11.10だと、標準ではターミナルのパス表記(という言い方が正しいかどうか分かりませんが・・・)がカレントディレクトリまでのパスまで表記されてしまい、階層が深くなると非常に見づらくなってしまいます。
こんな感じ。

k-kuwana@dev:~/usr/work/scala/lernen$ pwd
/home/k-kuwana/usr/work/scala/lernen

そこで、$HOME/.bashrcの55行目付近(else節)にある「\w」という所を「\W」に変更してあげると、カレントディレクトリのみ表示されるようになってスッキリします。

$HOME/.bashrc変更前

52 if [ "$color_prompt" = yes ]; then 53 PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 54 else 55 PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ ' 56 fi

$HOME/.bashrc変更後

52 if [ "$color_prompt" = yes ]; then 53 PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 54 else 55 PS1='${debian_chroot:+($debian_chroot)}\u@\h:\W\$ ' 56 fi


OSを再起動するか、ターミナルを起動して「source ~/.bashrc」を実行すれば上記の修正が反映されます。

2012年3月3日土曜日

PHP5.4のビルトインサーバ起動メモ

PHP5.4のビルトインサーバを使ってみたのでまとめてみました
なお、Windows7のVirtualBox上で動かしているUbuntu11.10で動作確認しました。
1.まずはPHP5.4をインストール
configureする時にapxsとか気にするな!cliさえあればWEBサーバ使えるから!
開発機ではビルトインサーバ上でサクサク作って本番機はApacheとかね!
ちなみにUbuntu11.10で動作確認しました。


2.インストールが終わったら、とりあえず以下にパスを通しとけ!
パスを通しとけば何かと便利なのでやっぱり通しておきましょう
PHPインストールディレクトリ/bin/

ちなみに、makeだけしとけば、make installしなくても、sapi/cliにパスを通せばOK。
どっちでもお好みで。


3.ビルトインサーバ起動!
これを待ってました!Apacheのインストールとか設定とか・・・もううんざり!
PHP5.4からは簡易WEBサーバが標準搭載されました。
実行するのはこんなに簡単!

・どこでもいいから、phpinfo()を書いたindex.phpを作成
・同じ場所で以下を実効
php -S localhost:8080
・ブラウザでhttp://localhost:8080/index.phpにアクセス!


4.DocumentRootはここだよ!
上述のコマンドだと、カレントディレクトリがDocumentRootになるとのこと。
DocumentRootを指定するには以下のコマンドでOK
php -S localhost:8080 -t [DocumentRootのフルパス]


5.php.iniはここだよ!
これまた開発時にいろいろな環境でphp.iniを切り替えたいというときに便利!
起動時に以下のように使用するphp.iniを指定できます。
php -S localhost:8080 -c [php.iniのフルパス]

php.iniのパスが間違っていたりしても、エラーにはならずに単純に読み込まれずにビルトインサーバが起動する。
ちなみに、php.ini-developmentを使用すると、ビルトインサーバが吐き出すログ(コンソール)に色が付いてテンション上がる。


6.まとめ
ドキュメントルート、php.iniの場所を指定するとこんな感じ
php -S localhost:8080 -t /home/k-kuwana/myDocumentRoot -c /home/k-kuwana/php.ini


7.Apacheのmod_rewriteとかっぽいのはどうするの?
そういうのはPHPスクリプトで制御するらしい。
書き方はまだ分からないけど、とりあえずビルトインサーバ起動時に以下のように指定してあげる?
php -S localhost:8080 builtin-server.php

2012年2月7日火曜日

Scalaの関数とメソッドの宣言方法のまとめ

ちなみに、Scalaでメソッドと呼ばれるものは、defキーワードで宣言されたもののことです。
サンプルとして、2つの引数をとり、それらを掛け合わせたものを返す関数とメソッドを作ってみます。

もっともオーソドックスな関数

まず関数の宣言方法は以下です。

scala> val multiply:(Int, Int) => Int = (x:Int, y:Int) => { x * y } multiply: (Int, Int) => Int = <function2> # 実行 scala> multiply(5,10) res8: Int = 50

(Int, Int) => Int
この部分が関数の型の宣言です。
第1引数にInt、第2引数にIntをとり、最終的にIntを返す関数という宣言になります。

(x:Int, y:Int) => { x * y }
この部分が実際の関数の宣言です。
なお、=>{...}の部分は、通常コードブロックと呼ばれます。
xとyという変数で値を受け取って、x * yを実行します。
Scalaでは最後に評価された式が戻り値となりますので、x*yの実行結果(Intどうしの掛け算なので当然Int型)が帰ります。

なお、実はこの関数は無名関数です。
multiplyというのはあくまで変数名であり、この関数自体には名前がありません。

「(Int, Int) => Int 」という型の変数に、イコール(=)を使って、関数「 (x:Int, y:Int) => { x * y }」を代入しているということになります。


もっともオーソドックスなメソッド

次はメソッドで同じものを作ってみます。

scala> def multiply(x:Int, y:Int):Int = { x * y } multiply: (x: Int, y: Int)Int # 実行 scala> multiply(5,10) res9: Int = 50

def multiply(x:Int, y:Int):Int
この部分がメソッドの宣言です。
第1引数にInt、第2引数にIntをとり、最終的にIntを返すメソッドという宣言になります。

= { x * y }
この部分が実際のメソッド本体ですです。
xとyという変数で値を受け取って、x * yを実行します。


関数の省略記法

幾つかパターンはありますがとりあえず以下の2パターンが良く使われると思います。

# コードブロックの中括弧{}を省略 scala> val multiply:(Int, Int) => Int = (x:Int, y:Int) => x * y multiply: (Int, Int) => Int = <function2> # 思い切って関数の型を省略 scala> val multiply = (x:Int, y:Int) => x * y multiply: (Int, Int) => Int = <function2> # 引数なしの関数はこれ scala> val multiply:() => Int = () => 10 * 5 multiply: () => Int = <function0> # 関数の型を省略 scala> val multiply = () => 10 * 5 multiply: () => Int = <function0>


メソッドの省略記法

結構パターンがあります。この省略記法を覚えるのには苦労させられます。

# コードブロックの中括弧{}を省略 scala> def multiply(x:Int, y:Int):Int = x * y multiply: (x: Int, y: Int)Int # 戻り値の型を省略 scala> def multiply(x:Int, y:Int) = x * y multiply: (x: Int, y: Int)Int # 引数も戻り値の型も中括弧も全て省略。クラスのゲッターなどに使える scala> def getName = "Yamada Tarou" getName: java.lang.String # これでも同じ scala> def getName() = "Yamada Tarou" getName: ()java.lang.String # 戻り値の型と中括弧を省略したパターン。クラスのセッターなどに使える scala> def setName(newName:String) = name = newName setName: (newName: String)Unit # 戻り値が無い(Unit)の場合は、コードブロックの前のイコールを書かなくても良い scala> def myPrint { println("Hello World") } myPrint: Unit # もちろん書いても良い scala> def myPrint = { println("Hello World") } myPrint: Unit # これも同じ scala> def myPrint:Unit = { println("Hello World") } myPrint: Unit # ただしこれはNG scala> def myPrint:Unit { println("Hello World") }
まだまだパターンはありますが、とりあえずこれでメソッドの省略記法のメジャーどころはつぶせたかなと思います。


関数の宣言に関する注意

ちゃんと関数として宣言せずに、とりあえずコードブロックで囲めばいいんでしょ?というノリでコーディングすると意図しない動作になってしまいます。

# 以下のような宣言をすると、myPrintにはUnitが入ってしまう scala> val myPrint = { println("Hello World") } Hello World myPrint: Unit = () # なので、何回myPrintを実行しても何も起こらない scala> myPrint() # 意図した関数として宣言するには以下のようにする scala> val myPrint:()=>Unit = () => { println("Hello World") } myPrint: () => Unit = <function0> scala> myPrint() Hello World scala> myPrint() Hello World
また、関数はオブジェクトなので、別の変数に格納することもできます。

# まずは関数定義 scala> val myPrint = () => {println("Hello World")} myPrint: () => Unit = <function0> # 括弧無しで呼び出すと関数本体が返される scala> myPrint res1: () => Unit = <function0> # 別の変数に代入 scala> val myPrint2 = myPrint myPrint2: () => Unit = <function0> scala> myPrint2() Hello World


メソッドの宣言に関する注意

Scalaでは、メソッドに戻り値があっても戻り値の型は省略できますが、メソッド宣言とコードブロックの間のイコールは省略できません。
厳密に言うと省略は出来るのですが、イコールを書略してしまうと値を返してくれないメソッドになってしまいます。

# コードブロックで文字列が評価されてStringが返りそうだけど・・・ scala> def getString { "Helo World" } getString: Unit # 実行すると何も帰ってきません scala> getString
これは、イコールを省略するとメソッドが自動的にUnitになってしまうためです。
イコールを付ければ期待した動作になります。

scala> def getString = { "Helo World" } getString: java.lang.String scala> getString res24: java.lang.String = Helo World
次に、関数の場合は括弧()なしで実行すると関数自体が返されましたが、メソッドは動作が異なります。
メソッドの宣言で、引数リストの括弧()を省略した場合には呼び出し方法が制限されます。

scala> def myPrint { println("Hello World") } myPrint: Unit # こちらはOK scala> myPrint Hello World # 括弧を付けるとエラー・・・ scala> myPrint() <console>:9: error: Unit does not take parameters myPrint() ^
引数なしであっても、引数リストを省略せずに空の括弧()を付けておけばどちらでも実行できます。

scala> def myPrint() { println("Hello World") } myPrint: ()Unit scala> myPrint() Hello World scala> myPrint Hello World

2012年2月2日木曜日

bzr+ssh://で外部のBazaarリポジトリにアクセスする方法(Linux)


以下のコマンドでOK

bzr log bzr+ssh://{接続先ユーザ名}@{接続先ホスト名}:{接続先のSSHのポート}{接続先の共有リポジトリへのフルパス}

なお、接続先のサーバにはSSHですでに接続できていることが前提。
接続先のBazaarに特別な設定などは特に必要ありません。

接続先の情報が以下の場合、

・接続先:example.com
・接続先ユーザ名:hoge
・接続先のSSHのポート:1234
・接続先の共有リポジトリの場所:/home/hoge/myrepo

こうなります。

bzr log bzr+ssh://hoge@example.com:1234/home/hoge/myrepo

Linux系なら「~/.bazaar/authentication.conf」にポートとかの設定を書いておけるよ、と公式に書いているの、なぜか設定が反映されず。
自分の環境では、接続先のSSHのポートは22番以外なので、正しいポートをauthentication.confに書いていたのになぜか22番でアクセスしてタイムアウトしていました。
ということで、直接URLにポート番号を指定することで回避できました。

このあたりの情報が公式以外にあまりないんですけどBazaarユーザって少ないんですかね?