Hatena::ブログ(Diary)

おもしろWEBサービス開発日記 このページをアンテナに追加 RSSフィード

Google
WWW を検索 このブログ を検索

2011-11-16

社内Scala勉強会その2

そういえば4月に退職してから何をしてたか全然書いてなかったですが、半年ほど自宅警備員を続けた後にフリーランスエンジニアとして活動を始めています。今はとある会社に常駐しつつ Rails 3.1.1 を使って開発をしています。

ところで2週間ほど前から、ふとしたきっかけで社内のエンジニアの方々と週一ペースの Scala 勉強会を始めることになりました。教材は twitter が公開しているScala Schoolです。僕が先週やった分のまとめ担当になったので、まとめを書いていこうと思います!先週は Scala School - Basics continued を一通りみんなで眺めました。

バックナンバー

初回の勉強会の内容を @satococoa さんにまとめていただきました。

社内Scala勉強会始めました - 223 Software

apply methods

オブジェクトメソッド的に使えるシンタックスシュガーを提供してくれる。後述するような、 Companion Object をファクトリとして使う時に利用するのが主な使われ方?

scala> class Bar {
     |   def apply() = 0
     | }
defined class Bar

scala> val bar = new Bar
bar: Bar = Bar@47711479

scala> bar()
res8: Int = 0

Objects

定義したクラスの一つのインスタンスを保持するものらしい。singleton instance のようなもの?

object Timer {
  var count = 0

  def currentCount(): Long = {
    count += 1
    count
  }
}

実行するたびにインクリメントされたcountが返る。

scala> Timer.currentCount()
res0: Long = 1

scala> Timer.currentCount()
res1: Long = 2

scala> Timer.currentCount()
res2: Long = 3

クラスとオブジェクトは同じ名前を持てて、その場合 "Companion Object" と呼ばれる。Companion Object は主に下記のような感じで定義されることが多いらしい。

class Bar(foo: String)

object Bar {
  def apply(foo: String) = new Bar(foo)
}

こうすると、Bar() で Barクラスのインスタンスが返る!べんり。

Functions are Objects

関数は trait らしい。例えば引数を一つ取る関数は "Function1" という名前の trait を mixin したインスタンス。"Function1" は apply() を定義していて、オブジェクト関数っぽく呼べる。

scala> object addOne extends Function1[Int, Int] {
      |   def apply(m: Int): Int = m + 1
      | }
defined module AddOne

scala> addOne(1)
res2: Int = 2

上記のコードは

def addOne(m: Int): Int = m + 1

と等価っていう認識で良いんだろうか。それとも「def キーワード使わなくても同じ事が出来るよ」ってこと?→コップ本読んだけど等価って言う認識でよさそう。

extends Function1[Int, Int] は extends (Int => Int) に省略可能らしいです。でもそもそも[Int, Int]が何なのか説明がないのでよくわかりません…Int を引数にとって Int を返すってことかな。

Packages

package は java っぽい感じ。object を使うと static function っぽく書ける。

package com.twitter.example

object colorHolder {
  val BLUE = "Blue"
  val RED = "Red"
}

上記のように定義すると、パッケージの外から下記のように呼び出せる。

println("the color is: " + com.twitter.example.colorHolder.BLUE)

Pattern Matching

Ruby で言う case when ですね。どうもcase whenとは違うらしい…!!今度調べます。

times match {
  case 1 => "one"
  case 2 => "two"
  case _ => "some other number"
}

scala はパターンマッチング中に if を使うことで、複雑な条件をスマートに分岐できるっぽい。

times match {
  case i if i == 1 => "one"
  case i if i == 2 => "two"
  case _ => "some other number"
}

パターンマッチング用の変数は自動で "i" になるみたい。上記のコードでは "i" を使っていますが、変数名に特に縛りは無いみたいです。x とか別の変数名にしても普通に動作しました。

上記の例だと if を使うメリットは特にないけど、もっと複雑な条件で恩恵を受けられそうな文法。

Matching on class members

Calculator というクラスのメンバを調べて場合分けするのを普通に書くと下記のようになる。

def calcType(calc: Calculator) = calc match {
  case calc.brand == "hp" && calc.model == "20B" => "financial"
  case calc.brand == "hp" && calc.model == "48G" => "scientific"
  case calc.brand == "hp" && calc.model == "30B" => "business"
  case _ => "unknown"
}

後述する case class というものを使うともっとスマートに書けるらしい。

case classes

case class で宣言して、メソッド呼び出しのような形で case classインスタンスを作る。

scala> case class Calculator(brand: String, model: String)
defined class Calculator

scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)

case class は自動で同値判定用のメソッドと toString メソッドを持ってる。あと他の普通のクラスと同じようにメソッドを持たせることもできる。

case classes with pattern matching

case class を使ってパターンマッチングをしてみると下記のようになる。確かに見やすくなってる!

val hp20b = Calculator("hp", "20B")
val hp30b = Calculator("hp", "30B")

def calcType(calc: Calculator) = calc match {
  case Calculator("hp", "20B") => "financial"
  case Calculator("hp", "48G") => "scientific"
  case Calculator("hp", "30B") => "business"
  case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}

case の一番下は下記のようにも書ける

case Calculator(_, _) => "Calculator of unknown type"

Calculatorクラスのインスタンス以外もマッチングさせたかったら下記のように書ける

case _ => "Calculator of unknown type"

マッチした値に別の名前を付けて使うことも出来る。下記はマッチした Caluculator インスタンスに c という名前を付けて、戻り値となる文字列中で使ってる。

case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)

Exceptions

例外の書き方はjavaと同じ。

try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
} finally {
  remoteCalculatorService.close()
}

javaと違うのは、式として使うことも出来るということ。下記のような書き方はRubyでも時々使いますね。

val result: Int = try {
  remoteCalculatorService.add(1, 2)
} catch {
  case e: ServerIsDownException => {
    log.error(e, "the remote calculator service is unavailble. should have kept your trustry HP.")
    0
  }
} finally {
  remoteCalculatorService.close()
}

雑感

Scala Schoolを使って勉強会を2回やってみて、この教材はおそらくScalaに詳しい人が近くに居る想定で作られているんだろうなと感じました。少なくともscalaを知らない初心者が一人でやるものじゃなさそう。勉強会ドリブンじゃなくて一人だったら続かなかっただろうなー。

なんとか最後までやりきって、簡単なwebアプリを作って公開できるくらい Scala をマスターできたらいいなと思います。

2011-10-18

Ruby1.9.3ではKernel#randが少し便利になる

5から9までの整数をランダムで返すような処理を書くとき、1.9.2までは

rand(5) + 5

もしくは

r = Random.new
r.rand(5..9)

のようにする必要があります。でも1.9.3だと

rand(5..9)

のようにできるようになっています。少しのことですけど嬉しい改善点ですね。

参考

Be Prepared for Ruby 1.9.3 and 1.9.4: What’s New and What’s Different

2011-10-06

ローカルgemのrdocみるなら gem server より yard server --gems

復習用エントリ。そもそもローカルのgemのrdoc見る機会も少なくなってるというのもあり完全に忘れてた。gem server よりも yard server --gems で出力する rdoc の方が見やすいですね。

参考

Route 477 - ローカルのgemのドキュメントを見る簡単な方法(2010秋)

2011-10-04

zsh のコマンド pushd と popd について

復習エントリ。zsh のコマンド pushd と popd について。

setopt auto_pushd

として cd -[TAB] で直近の cd 履歴が見れて移動できるのは知ってたけど popd は忘れてた。

上に行くcd作った - hitode909のダイアリー

cd 系コマンドは、上記エントリの up コマンドを時々思い出したように使っていただけ。popd をもっと積極的に活用して縦横無尽にディレクトリを渡り歩きたいので

alias pd=popd

としてみた。これで pd とすると一つ前のディレクトリに戻れる。今度は忘れないように習慣づけたい!

参考

404 Blog Not Found:tips - 君はpushd|popdを知っているか?

2011-09-28

sprockets の README 意訳

Rails3.1 から同梱されるようになった sprockets の README の意訳です。

sstephenson/sprockets - GitHub

はじめに

Sprockets は assets ファイルのコンパイルと配信を行うための Rubyライブラリです。JavaScriptCSS ファイルの依存関係を宣言でき、さらにプリプロセッサを使うことで CoffeeScript, Sass, SCSS, LESS 等の言語を assets の記述に利用出来ます。

インストール

いわゆる普通のやり方でインストール可能です。

gem install sprockets

Gemfileなら

gem 'sprockets', '~> 2.0'

Sprockets Environment

assets ファイルにアクセスして配信するなら、 Sprockets::Environment クラスのインスタンスが必要です。Rails 3.1 以降なら、YourApp::Application.assets でSprockets::Environment のインスタンスアクセスできます。Rack ベースのアプリなら、config.ru の中でインスタンスを作りましょう。

Sprockets::Environment は assets ファイルの取得と配信、ロードパスの操作、プロセッサの登録用のメソッドを持っています。それはまた URL にマウントすることが出来て静的ファイルを配信する Rack アプリでもあります。

ロードパス

ここで言うロードパスとは、Sprockets が assets ファイルを探すときに使うディレクトリの順序づけられたリストのことです。

最も単純なケースでは、Sprockets のロードパスはアプリの assets ファイルを含む単一のディレクトリから構成されるでしょう。Sprockets の environment がマウントされたら、environment はこのディレクトリの assets をまるで public root にある静的ファイルのように配信します。

ロードパスによって複数ディレクトリ内のソースファイルを管理して、これらのディレクトリを単一の仮想ファイルシステムにすることができます。さらにアプリの外のディレクトリを指定することもできます。つまりこれは、Rubyライブラリに同梱されている JavaScript, CSS, 画像を簡単にアプリインポートすることが出来ると言うことを意味します。

ロードパスの操作方法

ロードパスにディレクトリを加えたい場合、 append_path と prepend_path メソッドが使えます。ロードパス内では最初のパスが後のパスよりも優先されます。通常、パスを追加するときにはデフォルトのパスの後に追加して、既存の assets を上書きしたいときのみデフォルトのパスの前に追加するようなやり方が一般的です。

environment = Sprockets::Environment.new
environment.append_path 'app/assets/javascripts'
environment.append_path 'lib/assets/javascripts'
environment.append_path 'vendor/assets/jquery'

assets へのアクセス

environment にロードパスをセットしたら、Rackサーバのように environment をマウントして、HTTP経由で assets をリクエストすることが出来るようになります。また、アプリの中からassetsにアクセスすることも出来ます。

論理パス

Sprockets 中の assets は常に論理パスで参照されます。論理パスとは、ロードパス中に含まれるディレクトリからの相対パスのことを指します。例えば、ロードパスに app/assets/javascripts を含めていたとすると、下記のようになります。

asset論理パス
app/assets/javascripts/application.jsapplication.js
app/assets/javascripts/models/project.jsmodels/project.js

このような方法でロードパス中の全てのディレクトリがマージされて、論理パス用の仮想的なファイルシステムが作られます。

HTTP越しにassetsを配信する

environment をマウントすると、マウントポイント配下の論理パス越しに全ての assets にアクセスすることが出来ます。例えば、environment を /assets にマウントして /assets/application.js にリクエストを飛ばすと、Sprockets はロードパス中の application.js を探して配信します。

Rails 3.1 以降では、Sprockets の environment は自動で /assets にマウントされます。もし(Rails以外の) Rack アプリで Sprockets を使うなら、手動で environment をマウントする必要があります。config.ru 中で、map メソッドを使うのがよいでしょう。

require 'sprockets'map '/assets' do
  environment = Sprockets::Environment.new
  environment.append_path 'app/assets/javascripts'
  environment.append_path 'app/assets/stylesheets'
  run environment
end

map '/' do
  run YourRackApp
end

assets にアクセスするには

find_asset メソッド([]がエイリアス)を使うことで、Sprocket の environment から asset を受け取ることが出来ます。論理パスを引数として渡すと、Sprockets::BundledAsset インスタンスを返します。

environment['application.js']
# => #<Sprockets::BundledAsset ...>
Sprockets::BundledAsset のインスタンスメソッド
to_s
asset の内容を返します
length
asset のバイト数を返します
mtime
asset の最終修正日時を返します
pathname
asset のフルパスを返します。

エンジンを使う

asset のソースは SCSS や CoffeeScript 等の他の言語で書くことが出来ます。その場合 Sprockets によって自動で CSSJavaScriptコンパイルされます。これらの言語用のコンパイラはエンジンと呼ばれます。

使用されるエンジンは asset のファイル名に追加された拡張子で判断されます。例えば、SCSS で書かれた CSS ファイルは例えば layout.css.scss のようになるでしょう。一方 CoffeeScript で書かれた JavaScript ファイルは dialog.js.coffee のようになるでしょう。

Sass と SCSS を使う

Sass は CSS にネストや変数やmixinやセレクタ継承を加えた言語です。もし sass gem が使えるなら、CSS assets を 書くのに Sass が使えます。

Sprockets は Sass と SCSS 両方をサポートしています。Sass を使いたい場合は . css.sass、SCSS を使いたい場合は .css.scss のように拡張子を指定します。

LESS を使う

LESS コンパイラJavaScript で書かれています。less gemRuby で書かれた V8 JavaScript ランタイムである therubyracer に依存しています。LESS を使いたい場合は .css.less のように拡張子を指定します。

CoffeeScript を使う

coffee-script gem が使えるなら、CoffeeScript を使うことが出来ます。CoffeeScript コンパイラJavaScript で書かれているため、ExecJS がサポートしているランタイムが必要です。CoffeeScript が使いたい場合は .js.coffee のように拡張子を指定します。

EJS と Eco で JavaScript テンプレートを扱う

Sprockets は クライアントサイドでのレンダリングを扱うJavaScript テンプレートをサポートしています。JavaScript テンプレートは特別な拡張子 .jst をもち、JavaScript関数コンパイルされます。

JavaScript テンプレートをロードしたとき、グローバルの JST オブジェクトを通して、JavaScript テンプレート関数アクセスすることが出来ます。テンプレート関数は、文字列テンプレートを描画するためのものです。戻り値となる文字列DOM に挿入できます。

<!-- templates/hello.jst.ejs --><div>Hello, <span><%= name %></span>!</div>

// application.js
//= require templates/hello
$("#hello").html(JST["templates/hello"]({ name: "Sam" }));

Sprockets は二つの JavaScript テンプレート言語をサポートしています。JavaScript に組み込むための EJSCoffeeScript に組み込むための Eco です。両方の言語とも<% … %> でテンプレート内に埋め込む事が出来ます。

もし ejs gem が使える状態なら、EJS テンプレートを使うことが出来ます。EJS を使いたい場合は.jst.ejsのように拡張子を指定します。

eco gem が使える状態なら、Eco テンプレートを使うことが出来ます。Eco を使いたい場合は .jst.eco のように拡張子を指定します。eco gemCoffeeScript コンパイラに依存しているので、前述の CoffeeScript エンジンを使う際の注意点と同じ内容が適用されます。

ERB で Ruby を実行する

Sprockets は ERB エンジンを提供しています。.erb を CSS または JavaScript asset のファイル名の後に付けると、ERBエンジンを有効に出来ます。

注意:Sprockets が複数のエンジンを使う場合、拡張子の右側から順番に適用していきます。ERB を処理してから CoffeeScriptコンパイルしたい場合は .js.coffee.erb のように拡張子を指定します。

assets は Sprockets::Context インスタンスコンテキストで評価されます。

このあたりちょっと省略してます

文字列埋め込みのシンタックス

asset 中で Rubyアクセスしたいけれど <%= %> が使えないときには、.str 拡張子を使うと #{ … } が使えます。

依存関係を解決する

各ソースファイルの頭で特別なコメント風シンタックスを使うことにより、asset ファイルを順序づけて結合した *asset bundles* を作ることが出来ます。

Sprockets は assets ファイル中のディレクティブと呼ばれるコメントを解析し、依存関係のグラフを作成します。もし依存関係のある asset ファイルをリクエストした場合、ファイルの頭で指定されている依存ファイルが include されます。

ディレクティブプロセッサ

Sprockets は CSSJavaScript ソースファイルでディレクティブプロセッサを走らせます。ディレクティブプロセッサはファイル頭の "=" で始まるコメントを見ます。"=" の後の最初の単語がディレクティブ名になり、続く文字列引数として取り扱われます。Sprockets はコードが始まったらディレクティブを探すことをやめます。

サポートしているコメントの種類

/* 複数行のコメント形式 (CSS, SCSS, JavaScript) 
*= require foo
 */

// 一行コメント形式 (SCSS, JavaScript)
//= require foo

# 一行コメント形式 (CoffeeScript)
#= require foo

Sprockets のディレクティブ

下記のディレクティブが使えます。それぞれパスを引数としてとります。指定できるパスは論理パスまたは相対パスです。相対パスは "./" で始まり、現在のファイルの場所からの位置を指定します。

require ディレクティブ
require path

path に指定されたファイルを挿入します。同じファイルが複数回 require されても有効なのは一回だけです。

include ディレクティブ
include path

require と似た働きをしますが、複数回 include されたときに、複数回内容が挿入される部分が異なります。

require_directory ディレクティブ
require_directory path

ディレクトリ中のファイルをアルファベット順で全部 require します

require_tree ディレクティブ
require_tree path 

require_directory と似た働きをしますが、path から再帰的にディレクトリを辿って require していく部分が異なります。

require_self ディレクティブ
require_self

require_self で指定した後の require や include の前に現在のファイルを挿入するように Sprockets に伝えます。

depend_on ディレクティブ
depend_on path

bundle 中に含まない path に依存していることを宣言します。依存しているファイルが変更されたときにキャッシュを expire したいときに便利です。